Skip to content

Simplify one pilot one robot - #6

Merged
arteeh merged 3 commits into
mainfrom
simplify-one-pilot-one-robot
Jul 19, 2026
Merged

Simplify one pilot one robot#6
arteeh merged 3 commits into
mainfrom
simplify-one-pilot-one-robot

Conversation

@arteeh

@arteeh arteeh commented Jul 18, 2026

Copy link
Copy Markdown
Member

No description provided.

@arteeh
arteeh merged commit cefe9ea into main Jul 19, 2026
1 check passed
@arteeh
arteeh deleted the simplify-one-pilot-one-robot branch July 19, 2026 00:02

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2ef94afe21

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread client/src/app.js
dataChannelProfile: this.controlConfig.splatBatchDataChannel,
});
this.splatPeer.addEventListener("splatbatch", (event) => this.receiveSplatBatch(event.detail));
const [pilotChannel] = await Promise.all([this.pilotPeer.negotiate(), this.splatPeer.negotiate()]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Negotiate a real Splat Batch data channel

When starting any control session that expects reconstructed splat batches, this now waits on SplatBatchPeer.negotiate(), but that peer only listens for a remote datachannel and never creates an offer-side data channel or transceiver before createOffer(). Such offers don't include an SCTP/application m-line, so the server-created ito.splatBatches channel cannot be negotiated; the promise may complete but no splatbatch events arrive, leaving the visual stream permanently stale and eventually pausing pilot input.

Useful? React with 👍 / 👎.

Comment thread server/ito/app.py
Comment on lines +454 to +455
if was_active:
await _maybe_await(self.adapter.stop_control())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Always finish cleanup when adapter stop fails

In the remote backend, if the driver stops responding or rejects driver.control.stop, RemoteRobotAdapter.stop_control() raises here after control_active is cleared but before reconstruction, live WebRTC paths, and the pilot notification are cleaned up. _handle_control_stop has already sent an OK result to the pilot, so this leaves the UI without control.stopped and can keep runtime resources open; adapter stop should be best-effort and cleanup should continue.

Useful? React with 👍 / 👎.

Comment thread server/ito/app.py
Comment on lines +128 to +129
if candidate.is_dir():
candidate = candidate / "index.html"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recheck directory indexes after appending index.html

For directory requests, the containment check runs before appending index.html; when ITO_CLIENT_DIR is configurable or writable, client_dir/sub/index.html can be a symlink outside the client tree and read_bytes() will follow it because the appended path is never resolved and checked again. This bypasses the traversal guard specifically for directory-index requests.

Useful? React with 👍 / 👎.

Comment thread server/ito/app.py
Comment on lines +405 to 406
if not self.control_active:
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid dropping remote camera offers during start

In the remote-driver backend, a driver can send driver.control.start.result and then immediately send its cameraMedia offer; because the pilot-side start handler only sets self.control_active = True after the awaited request resumes, the driver connection can process the already-buffered offer first and this guard silently discards it. That leaves the remote camera path without an answer and the pilot never receives reconstruction output, so start should either mark a pending/active state before awaiting the driver or queue valid offers during startup.

Useful? React with 👍 / 👎.

Comment thread server/ito/robot.py
"""Latch a robot-local stop until an explicit control start."""

was_active = self.control_active
self.control_active = False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report local emergency stops to the application

When a local integration invokes LocalRobotAdapter.emergency_stop() during active control, this flips only the adapter's state; ItoApplication.control_active remains true, so a later pilot control.start takes the already-active fast path and never calls adapter.start_control() to clear the latch. In that emergency-stop recovery path the UI still appears active but pilot input is ignored until some separate stop lifecycle happens.

Useful? React with 👍 / 👎.

Comment thread server/ito/app.py
session_id=session_id,
return
if not self.control_active:
self._start_reconstruction()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle reconstruction startup failures

When a configured reconstruction processor raises during construction or start() (for example missing GPU/runtime resources), this call happens before the try that converts start failures into control.start.result errors. The exception escapes the WebSocket handler, leaving the pilot request unanswered and the connection likely closed instead of reporting a displayable start failure and cleaning up the partial runtime.

Useful? React with 👍 / 👎.

Comment thread client/src/app.js
Comment on lines 147 to 148
const batch = this.splatScene.applySplatBatch(payload, metadata);
if (batch) this.freshness?.markFresh();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark freshness even while the scene is frozen

After a temporary visual timeout, setVisualPaused(true) freezes the splat scene, and applySplatBatch() returns null while frozen. Because freshness is now only marked when a batch is applied, subsequent valid splat batches cannot trigger the fresh event, so pilot input remains paused forever after the first transient reconstruction gap.

Useful? React with 👍 / 👎.

Comment thread client/src/config.js
if (!location?.host) return "ws://localhost:8765/ws";
const scheme = location.protocol === "https:" ? "wss:" : "ws:";
return `${scheme}//${location.hostname}:8765`;
return `${scheme}//${location.host}/ws`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Migrate saved endpoints to the new /ws path

Changing the default endpoint to /ws only helps fresh installs; the localStorage key is unchanged, so any pilot who previously saved settings keeps a ws://host:8765 URL without the path. The new server treats WebSocket upgrades to / as a static HTTP request and rejects the handshake, so upgraded clients can no longer connect until storage is manually cleared or edited.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant