Skip to content

Keep one host record per remote device and show its platform - #451

Merged
Tryanks merged 3 commits into
mainfrom
remote-device-identity
Sep 16, 2026
Merged

Tryanks merged 3 commits into
mainfrom
remote-device-identity

Conversation

@Tryanks

@Tryanks Tryanks commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Behaviour

One device per peer on the host. Every client now owns a persistent, self-generated device id (native apps keep it in mobile.json, the browser in localStorage under tcode.device_id) and sends it as device_id with /pair, /auth/login and the websocket hello. When the host already holds a record for that id, issue_token rotates that record's token and refreshes its details, keeping the record id and first-connection time; the previous token stops validating. Clients without an id (older builds) keep today's behaviour. On a valid hello the host also refreshes the stored name/platform, persisting only on change, so a rename or OS upgrade shows without re-pairing.

Device name · platform. Clients report a platform string next to device_name, and the Connected devices rows (desktop hosting panel and the remote hosting view) render ‹name› · ‹platform›:

  • Android: Settings device name → ro.product.marketname → manufacturer + model, plus Android <release> (e.g. Xiaomi 15 · Android 15 instead of 24129PN74C).
  • iOS: UIDevice.name plus iOS <version>.
  • Desktop: computer name plus macOS <version> (sysctl kern.osproductversion), Windows <major.minor.build> (RtlGetVersion) or the /etc/os-release pretty name.
  • Web: browser family plus the OS from the user agent (e.g. Chrome · macOS).

Client-side saved hosts were already replaced by host_id; that policy now lives in one remember_host helper used by the shell, the --pair command and the web client.

Wire

  • POST /pair: {"code","device_name","device_id"?,"platform"?}
  • POST /auth/login: {"password","device_name","device_id"?,"platform"?}
  • hello: adds device_id and platform
  • hosting state devices[] and DeviceInfo: optional platform; remote.json records gain optional client_id / platform. No protocol version bump; all new fields are optional.

Abstractions

DeviceIdentity in tcode_client::host is the single owner of the pair body and hello line shared by native and web transports. DeviceClaim on the server owns validation of the three device fields for all three entry points. The only manifest change is the windows crate (already in the lock file) for cfg(windows) in tcode-remote.

Tests

  • auth.rs: re-pairing with the same client id yields one record with a rotated token, id and created_unix kept; a hello refresh writes only on change; an older remote.json without the new fields loads.
  • tests/remote.rs: a second /pair with the same device_id does not add a device, the old token is rejected, the hosting-state row is asserted literally with platform, and a hello with a new platform shows in hosting state.
  • client_host.rs: device id minted once and shared by later instances; os-release parsing.
  • client/host.rs: literal pair/hello JSON; id reuse vs. minting.
  • UI: device label with and without platform. Web: auth.test.mjs login body and id persistence.

Checks run

  • cargo fmt --all --check, cargo clippy --workspace --all-targets --locked -- -D warnings, cargo test --workspace --locked: pass.
  • cargo machete: clean.
  • RUSTFLAGS='-D warnings' checks: tcode-web (wasm32), tcode-ios (aarch64-apple-ios-sim), tcode-android (arm64-v8a via cargo ndk): pass. tcode-remote checked for x86_64-pc-windows-gnu and x86_64-unknown-linux-gnu.
  • node --test crates/web/static/auth.test.mjs: pass.

Not verified: no on-device Android/iOS run (Java compiled against android.jar only, Swift not built); Windows and Linux version strings are compile-checked only. The Android name depends on the phone's Settings device name or ro.product.marketname.

Claude Code echoes the launch id (`claude-opus-5[1m]`) in its `init`
message while the API reports the bare id, so every Opus 5 / Fable 5
session opened with a spurious "model changed" divider.

- Only append the manifest's context suffix when the selected window
  exceeds the model's default; 1M-default slugs launch bare. Smaller
  selections keep compacting early via `autoCompactWindow`.
- Resolve manifest aliases (`opus`, `claude-opus-5.0`) to the canonical
  slug before launch, matching upstream t3code.
- Treat a `[1m]` launch id and its bare form as the same served model
  when deciding whether to draw the divider. Only the fixed `[1m]`
  suffix is ignored.
Claude Code strips model suffixes with /\[1m\]$/i and its canonical-name
normalizer already accepts [2m], so the served-model comparison now
ignores that explicit, case-insensitive list. Arbitrary [...] suffixes
are still treated as a real model change.
Clients now send a persistent, self-generated device id with /pair,
/auth/login and the websocket hello. A host that already holds a record
for that id rotates its token and refreshes the name instead of adding a
second device, so repeated pairings no longer pile up credentials.

Clients also report their operating system: Android sends the Settings
device name (or marketing name) plus "Android <release>", iOS the device
name plus "iOS <version>", desktop the computer name plus the OS version,
and the browser its family plus the OS from the user agent. Connected
device rows read "Xiaomi 15 · Android 15" instead of a bare model code.
@Tryanks
Tryanks merged commit e07dfa5 into main Sep 16, 2026
6 checks passed
@Tryanks
Tryanks deleted the remote-device-identity branch September 16, 2026 11:13
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