Skip to content

feat(remote): control the daemon from a phone with /remote - #438

Merged
Lokesh7025 merged 3 commits into
RCfrom
remote/phone-control
Sep 25, 2026
Merged

Lokesh7025 merged 3 commits into
RCfrom
remote/phone-control

Conversation

@Lokesh7025

@Lokesh7025 Lokesh7025 commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stacked on #436. Review only the top three commits:

  • feat(remote): start phone pairing from the terminal with /remote
  • feat(web): control the daemon from a phone browser
  • chore(web): license the phone page

The page is hosted by #439.

With this, /remote in the terminal shows a QR code. Scanning it on a phone opens the deployment-test page, which pairs with the daemon on the laptop and controls it end to end encrypted over the hosted relay.

Daemon and terminal

  • remote.pairing.start RPC backed by an optional RemotePairingService. The capability is granted only when the daemon hosts pairing, remote devices can never call it, and failures surface as remote_unavailable without detail.
  • The runtime hosts pairing when AXL_REMOTE_DEPLOYMENT_TEST names a config (written by remote-config.sh in the hosting PR):
    • each pairing opens a daemon E2EE session
    • the device's claim notice arrives over the relay, and the host then reserves and confirms the claim, publishes the Welcome, and registers the device with observe and steer
    • the pair activation hands the endpoint to the existing E2EE bridge
    • a paired session is restored when the daemon restarts
  • /remote prints a QR code and the link. The QR encoder is dependency free (packages/tui/src/qr-code.ts, byte mode, versions 1 to 40, levels L to H). A terminal narrower than the code gets a hint instead of an unscannable code.
  • The link carries its UUIDs as base64url bytes, so a real pairing link (671 characters) fits a version 18 code, 97 columns with the standard quiet zone.
  • Remote devices may call session.resume with steer, so a phone can reopen sessions after the daemon restarts. An --unsafe daemon stays observe-only as before.

Phone page (packages/web/remote.html, pnpm build:remote)

  • pairRemoteBrowserDevice and RemoteBrowserSession in the SDK drive the browser binding: claim, relay notice, Welcome join, pair activation, then sealed requests with results, errors, and live deliveries.
  • The page:
    • moves the link fragment into this browser's storage and clears it from the address bar
    • lists sessions and resumes and subscribes to one, acknowledging the snapshot boundary so live events flow
    • renders the transcript, including failed and stopped turns
    • sends and queues prompts, and stops the running turn
  • ?debug logs relay traffic and call timing, never content.
  • Fixes found live:
    • hosted clients called window.fetch as a method, which browsers reject
    • steer and follow-up requests carried idempotency keys that the protocol forbids; the SDK now uses the protocol's retryable mutation list

The link carries the deployment-test stack's shared credentials in its fragment. That is acceptable only for the test stack; production pairing must not put credentials in a link.

Test plan

  • pnpm lint, pnpm typecheck, boundary and generated-file checks
  • Package tests: protocol 58, sdk 95, daemon 115, runtime 4, tui 215, web 42
    • editing and /quit recover from a stale shutdown status is flaky on RC as well; it passes when rerun
  • The first commit alone typechecks, lints, and passes its package tests
  • QR encoder: every version at every level round-trips through an independent decoder (jsQR, run outside the repo), 160 of 160
    • jsQR's own table has version 23's alignment centre at 74 instead of 78; with that corrected, every case decodes
    • unit tests pin the Reed-Solomon worked example from ISO/IEC 18004 Annex I, the format and version BCH codes, and the function patterns
  • Live against the AWS deployment-test stack, with a phone-sized browser and a real model:
    • /remote returned a link in about 4 s
    • the page paired, listed sessions, and opened one
    • a prompt sent from the page ran on the laptop daemon, and the reply streamed back
    • the phone reconnected and reopened a closed session after the daemon restarted
    • Stop interrupted a long turn (stopReason: aborted)

Follow-ups:

  • each request makes several witness round trips, so opening a session takes several seconds and a turn's first event takes a few more
  • the page shows the reply when the message completes rather than streaming tokens

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 1e348716-c0ca-41c5-ab24-eae3330034e0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
See the Details below.

License Issues

packages/runtime/package.json

PackageVersionLicenseIssue Type
@axl/sdkworkspace:*NullUnknown License
Denied Licenses: SSPL-1.0, BUSL-1.1, Elastic-2.0

OpenSSF Scorecard

PackageVersionScoreDetails
npm/@axl/sdk workspace:* UnknownUnknown

Scanned Files

  • packages/runtime/package.json

The daemon gains a remote.pairing.start RPC served by an optional
RemotePairingService. The capability is granted only when the daemon hosts
one, remote devices can never call it, and failures surface as
remote_unavailable without leaking details.

The runtime hosts it for the AWS deployment test when
AXL_REMOTE_DEPLOYMENT_TEST names a config file. Each pairing opens a daemon
E2EE session; when the device's claim notice arrives over the relay the host
reserves and confirms the claim, publishes the Welcome, registers the device
with observe and steer, and hands the activated endpoint to the E2EE bridge.
A paired session is restored when the daemon restarts.

/remote in the TUI prints the pairing link and a QR code from a
dependency-free encoder (byte mode, every version and error correction
level, checked against an independent decoder). A terminal too narrow for a
scannable code gets a hint instead. The link carries its identities as
base64url bytes, so a pairing link fits a version 18 code.

Remote devices may call session.resume with steer, so a phone can reopen
sessions after the daemon restarts.

Signed-off-by: Lokesh <lokeshselvam7025@gmail.com>
Adds the deployment-test phone page (remote.html, pnpm build:remote) and
the SDK browser adapter behind it.

pairRemoteBrowserDevice publishes the claim, notices the daemon over the
relay, joins the published Welcome, and sends the pair activation.
RemoteBrowserSession seals requests with the browser binding and dispatches
daemon results, errors, and live deliveries. Idempotency keys follow the
protocol's retryable mutation list.

The page stores the link fragment in this browser and removes it from the
address bar, lists sessions, reopens and subscribes to one (acknowledging
the snapshot boundary so live events flow), renders the transcript including
failed and stopped turns, sends and queues prompts, and stops the running
turn. ?debug logs relay traffic and call timing, never content.

Hosted clients now call the global fetch unbound, which browsers require.

Signed-off-by: Lokesh <lokeshselvam7025@gmail.com>
Signed-off-by: Lokesh <lokeshselvam7025@gmail.com>
@Lokesh7025
Lokesh7025 merged commit 48ebcd5 into RC Sep 25, 2026
34 checks passed
@Lokesh7025
Lokesh7025 deleted the remote/phone-control branch September 25, 2026 15:44
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