robustness: vision/depth render off the event loop (deepcopy snapshot), chat auto-reconnect + friendly errors, demo catches connection failures

This commit is contained in:
opencode
2026-08-08 12:33:30 +03:00
parent 346d918118
commit f4cb04a47f
5 changed files with 54 additions and 13 deletions
@@ -95,3 +95,4 @@
[ 183147ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8000/:40
[ 188179ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8000/:40
[ 192874ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8000/:40
[ 1348483ms] [ERROR] WebSocket connection to 'ws://127.0.0.1:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:8000/:40
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

+14 -5
View File
@@ -11,6 +11,7 @@ from __future__ import annotations
import asyncio
import base64
import copy
import io
import math
from typing import Any
@@ -206,11 +207,18 @@ class RoomBridge(Bridge):
)
async def vision() -> VisionOutput:
tick = self._tick()
img, _ = renderer.render(world)
buf = io.BytesIO()
img.save(buf, format="PNG")
# Render off the event loop so frames never stall the bridge under
# load; deepcopy keeps the snapshot consistent with this tool call.
snapshot = copy.deepcopy(world)
img, _ = await asyncio.to_thread(renderer.render, snapshot)
def _encode() -> str:
buf = io.BytesIO()
img.save(buf, format="PNG")
return base64.b64encode(buf.getvalue()).decode("ascii")
return VisionOutput(
png_b64=base64.b64encode(buf.getvalue()).decode("ascii"),
png_b64=await asyncio.to_thread(_encode),
width=VISION_WIDTH,
height=VISION_HEIGHT,
tick=tick,
@@ -222,7 +230,8 @@ class RoomBridge(Bridge):
)
async def depth(max_depth: float = 10.0) -> DepthOutput:
tick = self._tick()
_, grid = renderer.render(world, max_depth=max_depth)
snapshot = copy.deepcopy(world)
_, grid = await asyncio.to_thread(renderer.render, snapshot, max_depth)
return DepthOutput(
width=DEPTH_WIDTH,
height=DEPTH_HEIGHT,
+28 -6
View File
@@ -191,12 +191,34 @@ async def chat_loop(client: AICCClient, manifest, args: argparse.Namespace) -> i
async def run(args: argparse.Namespace) -> int:
transport = WebSocketClientTransport(args.url)
async with AICCClient(transport) as client:
manifest = await client.handshake()
print(f"[handshake] session {manifest.session_id} world {manifest.world.name}")
print(f"[handshake] tools: {[t.id for t in manifest.tools]}")
return await chat_loop(client, manifest, args)
"""Run the chat session, reconnecting to the bridge if the connection dies."""
attempts = 0
while True:
try:
transport = WebSocketClientTransport(args.url)
async with AICCClient(transport) as client:
manifest = await client.handshake()
print(
f"[handshake] session {manifest.session_id} world {manifest.world.name}"
)
print(f"[handshake] tools: {[t.id for t in manifest.tools]}")
return await chat_loop(client, manifest, args)
except asyncio.CancelledError:
raise
except KeyboardInterrupt:
raise
except Exception as exc: # noqa: BLE001 - connection lost: reconnect
attempts += 1
print(f"\n[chat] connection lost ({type(exc).__name__}: {exc})")
if attempts >= 3:
print(
"[chat] giving up after 3 attempts — is the bridge running? (scripts/run_bridge.sh)"
)
return 1
print(
"[chat] reconnecting in 2 seconds… (the room keeps its state on the bridge)"
)
await asyncio.sleep(2.0)
def main() -> int:
+11 -2
View File
@@ -152,7 +152,9 @@ async def run_llm_agent(
log=lambda role, msg: print(f"[{role}] {msg}"),
)
try:
return await run_llm_agent_loop(controller, max_steps, log=print_log, recorder=recorder)
return await run_llm_agent_loop(
controller, max_steps, log=print_log, recorder=recorder
)
except RuntimeError as exc:
return {"steps": 0, "tool_calls": 0, "result": str(exc), "interacted": False}
@@ -398,7 +400,14 @@ def main() -> int:
"into this directory, plus demo.gif animation and demo_summary.png",
)
args = parser.parse_args()
summary = asyncio.run(run_demo(args))
try:
summary = asyncio.run(run_demo(args))
except KeyboardInterrupt:
return 2
except Exception as exc: # noqa: BLE001 - friendly failure instead of a traceback
print(f"\n[demo] failed: {type(exc).__name__}: {exc}")
print("[demo] is the bridge running? scripts/run_bridge.sh")
return 1
return 0 if summary.get("interacted") else 1