WIP: Paul/feat/t2 relay#3068
Conversation
Greptile SummaryThis PR adds a WebTransport relay between robot streams and cockpit viewers. The main changes are:
Confidence Score: 4/5The viewer subscription path must enforce handshake completion before merging.
web/relay/registry.ts, web/relay/session.ts
|
| Filename | Overview |
|---|---|
| web/relay/registry.ts | Adds robot registration, subscription snapshots, and selective frame routing; viewer handshake state is not enforced on routing operations. |
| web/relay/session.ts | Adds robot and viewer WebTransport session orchestration, including handshake and transport-specific control loops. |
| dimos/web/relay_bridge/relay_bridge_module.py | Adds lazy stream encoding, local relay management, reconnect supervision, and child-process recovery. |
| dimos/web/relay_bridge/wt_client.py | Extends the WebTransport client with manifest hello messages, relay controls, bounded datagrams, and idempotent close handling. |
| web/shared/protocol.ts | Extends the shared relay protocol with robot, manifest, watch, subscription, and snapshot messages. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Robot[Robot bridge] -->|hello + manifest| RobotSession
RobotSession --> Registry
Viewer[Viewer] -->|hello, watch, sub| ViewerSession
ViewerSession --> Registry
Registry -->|subs snapshot| Robot
Robot -->|data frames| Registry
Registry -->|subscribed frames| Viewer
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart LR
Robot[Robot bridge] -->|hello + manifest| RobotSession
RobotSession --> Registry
Viewer[Viewer] -->|hello, watch, sub| ViewerSession
ViewerSession --> Registry
Registry -->|subs snapshot| Robot
Robot -->|data frames| Registry
Registry -->|subscribed frames| Viewer
Reviews (1): Last reviewed commit: "fixes" | Re-trigger Greptile
| case "watch": { | ||
| const entry = this.#robots.get(msg.robotId); | ||
| if (entry === undefined) { | ||
| reply({ t: "error", code: "unknown_robot", message: `no robot ${msg.robotId}` }); | ||
| break; | ||
| } | ||
| const previous = viewer.watched; | ||
| if (previous !== null && previous !== msg.robotId) { | ||
| viewer.subs.clear(); | ||
| viewer.policies.clear(); | ||
| } | ||
| viewer.watched = msg.robotId; | ||
| if (previous !== null && previous !== msg.robotId) this.#syncSubs(previous); | ||
| this.#syncSubs(msg.robotId); | ||
| reply({ t: "manifest", robotId: msg.robotId, channels: entry.peer.channels }); | ||
| break; |
There was a problem hiding this comment.
A client can send watch and then sub before sending any hello. These branches set watched and subs without checking viewer.greeted; onRobotFrame later routes frames using those fields without a greeted check. The client can therefore trigger a robot subscription snapshot and receive that channel's frames without completing the viewer handshake. The viewer path should require a valid role="viewer" hello before accepting watch/sub/unsub or forwarding frames.
❌ 2 Tests Failed:
View the top 2 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
aef5a79 to
c412753
Compare
…ubs)
hello gains optional robot{id,name,model} + manifest (role=robot);
new robots/watch/manifest/sub/unsub viewer messages and the relay->robot
subs snapshot (idempotent, n-guarded: it rides lossy datagrams).
Nested structural validation mirrored TS<->Python, absent-optional fields
omitted on encode to keep byte parity, golden vectors extended.
session.ts owns per-connection handshakes and transport quirks (the raw-QUIC robot stream loop moves verbatim); registry.ts maps robotId -> session with duplicate-id takeover, per-viewer watch/sub state recomputed into idempotent subs snapshots (datagram + 2 s resend heals loss), and routes frames only to watching+subscribed viewers, preferring manifest delivery over the header's. Client grows hello(robot, manifest), send_control, control_messages(); the debug page auto-watches the first robot, subs its manifest, and reconnects via /api/info polling.
The robot-side bridge module: registers with the relay (id from config / global robot_id / hostname + channel manifest), spawns and supervises the local relay child in --local-relay mode (browser tab, stale-port cleanup, watchdog respawn on child death - a SIGKILL sends no CONNECTION_CLOSE so QUIC idle timeout alone is ~60 s too slow), reconnects with backoff, and encodes color_image (TurboJPEG via Image.to_jpeg_bytes) + odom (pose.json.v1) only while the relay reports subscribed viewers. maxHz gate sits before encode on the transport thread. GlobalConfig grows local_relay/relay_url (used by the module; CLI wiring comes next).
Run-level flags (root auto-flags only parse before the subcommand) merged into global_config before the blueprint import, so vis_module can append RelayBridgeModule.blueprint() at blueprint-import time - one hook activates the cockpit bridge for every robot blueprint. Lazy import with a clear dimos[web] error.
6159287 to
ddd01e3
Compare
Contribution path
Problem
Solution
How to Test
AI assistance
Checklist
uv run pytest, pre-commit) for the files I changed.