Repository navigation
feat(desktop): pair with nearby phones over mDNS - #63679
Brendonovich wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
I couldn't run this end to end: it needs the Electron app, a phone and a system mDNS responder. I read the whole diff. The 59 pairing tests pass and tsc -b is clean in packages/gui-plugins.
Public API: the change adds the nearby_pairing flag to ConfigFlag.registry, which also adds the config key experimental.nearby_pairing (backwards compatible). It extends the internal GUI pairing IPC contract and adds the macOS NSLocalNetworkUsageDescription and NSBonjourServices entries. The server API is unchanged.
Concerns, most important first:
- Generated files committed by mistake. The PR adds roughly 2,000 lines of generated Astro output under
packages/web/.astro/andpackages/posts/.astro/that have nothing to do with this feature. Please drop them. - An idle connection can block pairing (inline comment on
nearby.ts). - The new config key does nothing (inline comment on
flag.ts). - Beta rollout on Windows. The flag is on by default in beta, so every beta user opens a listener on all interfaces at startup. The Windows
dnsapipath, and the firewall prompt it may raise, haven't been tried on real hardware yet. It's worth checking that before this ships to beta, or keeping the flag off on Windows until then.
|
|
||
| const pair = (socket: Socket) => { | ||
| const device = socket.remoteAddress ?? "unknown" | ||
| const admission = gate.admit(device, state.active) |
There was a problem hiding this comment.
The gate gives a connection the single slot (state.active) as soon as the TCP connection opens, before hello/commit. The prompt and its snooze buttons only appear once exchange.code resolves.
So a device on the network can just connect and stay silent, holding the slot for up to two 10 s steps. If it reconnects every 3 s, it keeps getting replaced into the slot indefinitely. Meanwhile every real phone is refused silently, and the user never sees a toast or the snooze offers.
Consider admitting a connection only once its code is ready, or at least not refusing other devices while the active one hasn't reached the code yet.
Related: after a replace, state.prompt keeps the old id and code until the new code arrives. During that window, pressing Pair on the visible toast does nothing.
| "Watch Slack threads, GitHub pull requests, issues, commits and Actions runs, and the workspace's MCP servers' events through OpenCode Console, and wake sessions when they change.", | ||
| mcp_events: | ||
| "Enable experimental MCP Events, a draft MCP extension: declare support to MCP servers and read the events of servers that offer them.", | ||
| nearby_pairing: |
There was a problem hiding this comment.
Adding this to registry also adds an experimental.nearby_pairing config key through experimental.ts. That key does nothing, because main never reads config. Users who set it will think they've turned the feature on or off. Can the flag live outside the shared registry, or can the schema leave this key out, until main can read it from config?
There was a problem hiding this comment.
Thanks for removing the generated .astro files. They're no longer in the diff, which settles the main point from my last review. This push only deletes files, so there was nothing new to run. The other two points are still in their open threads: a silent connection can hold the only pairing slot, and the experimental.nearby_pairing config key has no effect. The one from my summary is also still open: the Windows path should be checked on real hardware, or the flag kept off on Windows in beta until it is.
Public API: unchanged from my last review. The PR adds the nearby_pairing flag, and with it the experimental.nearby_pairing config key (backwards compatible).
Summary
Lets the OpenCode mobile app pair with a desktop on the same network without scanning a QR code. The desktop announces itself over mDNS (
_opencode._tcp), the phone lists it, and tapping it runs a key exchange. Both screens show the same 6-digit code, and only after the user confirms on both devices does the desktop send a pairing code, encrypted for that phone. The phone then redeems it the same way as a scanned QR code. The server doesn't change.Guarded by the new
nearby_pairingflag. Unlike other flags it's on in local, dev and beta builds, so beta testers can try it with the mobile app; stable releases keep it off until it graduates. The desktop main process has no Location config, so it reads the flag fromOPENCODE_FLAGSand the build channel only;experimental.nearby_pairingin config doesn't reach it.How it works
exchange.ts, whose header comment is the wire spec): X25519 with a commit/reveal step so an attacker in the middle can't search for matching codes, HKDF-derived keys for each direction, then AES-256-GCM sealed frames with counter nonces.gate.ts): one request at a time; a device can replace its own request at most every 3 s; Snooze This Device (1 h) appears after 3+ requests from one address in 2 min, and Snooze All for 15 Minutes after 3+ addresses. Devices are told apart by network address, not the name the phone sends.mdns*.ts): uses the system's own mDNS service. macOS runsdns-sd -R. Linux talks to Avahi over D-Bus (dbus-next). Windows usesDnsServiceRegisterindnsapi.dll, through a hidden PowerShell process. If the system service is missing or refuses, it falls back tobonjour-service. Using the system service avoids the host-name clashes (macbook-2,-3, …) that a second responder causes.The iOS side is in anomalyco/opencode-ios, branch
nearby-pairing, behind an off-by-default experiment.Testing
bun run checkpasses, the 59 pairing tests pass (exchange with fixed test vectors shared with the Swift version, gate, mDNS backend choice and fallback), and the desktop production build bundles the new code.macOS: paired the dev desktop end to end with the iOS app on a phone and in the simulator. After porting onto the refactored pairing plugin, rechecked with a mock phone: discovery starts through
dns-sdand the toast shows the code.Linux: Avahi path checked in a Debian container: publish, withdraw on stop, the "Roadmap & Existing Issues #2" rename on a duplicate name, and the fallback when Avahi is gone. Not yet tried on a real Linux machine on the same network as a phone.
Windows: the PowerShell script parses and compiles under PowerShell 7 on Linux, but it hasn't run on Windows yet. It still needs a check on real hardware, including what the firewall asks about the pairing port.
Settings › Pairing: the Discoverable nearby row renders on the refactored page, and toggling it stops and restarts the announcement.
A screen recording of the desktop and simulator flow is available on request (Brendan has it).
Added by the opencode bot (AI-generated).
Public API: adds the nearby_pairing flag and the experimental.nearby_pairing config key (backwards compatible)