Home

mweinbach / agent-coworker

publicmweinbach/agent-coworker
Overview Code History Branches Pull requestsIssuesInsights
main
HomeOverview Code PRsIssues

feat: add direct H3 mobile pairing foundation (#98)

5 months ago

766af09
Authored
Max Weinbach4/28/2026, 1:07:05 PM
* feat: add direct H3 mobile pairing foundation

Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>

* feat: surface direct H3 pairing in desktop

Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>

* feat: switch mobile pairing to H3 tickets

Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>

* refactor: remove retired remodex relay transport

Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>

* fix: address mobile h3 pairing review

* fix: harden mobile h3 pairing lifecycle

* fix: close mobile h3 review gaps

* fix: close mobile h3 transport review gaps

* fix: harden mobile h3 streaming and pins

* fix: harden mobile relay restore and streaming

* fix: harden mobile h3 rpc reconnect

* fix: harden mobile h3 pairing trust

* fix: preserve mobile relay workspace state

* fix: harden mobile h3 stream edge cases

Validate desktop mobile H3 host hints before IPC propagation, close active H3 SSE sinks when the JSON-RPC connection closes, treat intentional Android pinned HTTPS stream disconnects as close events, and bracket IPv6 hosts in mobile pairing URLs.

Tests: bun test apps/desktop/test/server-manager.test.ts test/h3.mobile-http-jsonrpc.test.ts test/mobile.secure-transport-client.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: cancel stale mobile pairing streams

Abort the active mobile H3 event stream before clearing the session for a new QR pairing attempt so failed retries cannot leave stale SSE callbacks alive.

Tests: bun test test/mobile.secure-transport-client.test.ts; bun test apps/desktop/test/server-manager.test.ts test/h3.mobile-http-jsonrpc.test.ts test/mobile.secure-transport-client.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: tighten desktop mobile relay startup

Guard mobile H3 server replacement with a pending-start entry, remove unused direct-H3 workspace cache wiring, and delete orphaned legacy relay types.

Tests: bun test apps/desktop/test/server-manager.test.ts apps/desktop/test/mobile-relay-ipc.test.ts apps/desktop/test/ipc-workspace.test.ts; bun test apps/desktop/test/server-manager.test.ts apps/desktop/test/mobile-relay-ipc.test.ts apps/desktop/test/ipc-workspace.test.ts test/h3.mobile-http-jsonrpc.test.ts test/mobile.secure-transport-client.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: bracket mobile h3 advertised IPv6 urls

Format IPv6 host hints before constructing the server_listening mobile H3 URL so desktop relay state gets a valid direct HTTPS URL.

Tests: bun test test/h3.mobile-http-jsonrpc.test.ts; bun test apps/desktop/test/server-manager.test.ts apps/desktop/test/mobile-relay-ipc.test.ts apps/desktop/test/ipc-workspace.test.ts test/h3.mobile-http-jsonrpc.test.ts test/mobile.secure-transport-client.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: settle pending mobile h3 rpc on close

Reject pending HTTP JSON-RPC waiters when an H3 mobile connection closes and return a prompt 503 response instead of waiting for the 30s timeout.

Tests: bun test test/h3.mobile-http-jsonrpc.test.ts; bun test apps/desktop/test/server-manager.test.ts apps/desktop/test/mobile-relay-ipc.test.ts apps/desktop/test/ipc-workspace.test.ts test/h3.mobile-http-jsonrpc.test.ts test/mobile.secure-transport-client.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: preserve native pinned stream utf8

Decode Android pinned HTTPS streams with a stateful UTF-8 reader and buffer incomplete UTF-8 sequences in the iOS stream delegate before forwarding SSE chunks.

Tests: bun test apps/desktop/test/server-manager.test.ts apps/desktop/test/mobile-relay-ipc.test.ts apps/desktop/test/ipc-workspace.test.ts test/h3.mobile-http-jsonrpc.test.ts test/mobile.secure-transport-client.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

Native build notes: ./gradlew :cowork-pinned-https:compileDebugKotlin is blocked locally by missing Java runtime; xcodebuild with -derivedDataPath /tmp reaches [CP] Check Pods Manifest.lock and fails because the Pods sandbox is out of sync with Podfile.lock.

* fix: sanitize desktop mobile h3 control state

Sanitize workspace IPC start results so renderer callers only receive the WebSocket URL, restart running servers when requested mobile H3 state changes in either direction, and align the internal mobile relay pairing payload type with the required H3 schema.

Tests: bun test apps/desktop/test/server-manager.test.ts apps/desktop/test/ipc-workspace.test.ts apps/desktop/test/mobile-relay-ipc.test.ts test/h3.mobile-http-jsonrpc.test.ts test/mobile.secure-transport-client.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: close stale mobile h3 trust paths

Serialize mobile H3 pairing-store updates so token verification cannot restore a concurrently revoked device, and disable the previous workspace H3 endpoint before the desktop relay bridge switches to another workspace.

Tests: bun test apps/desktop/test/server-manager.test.ts apps/desktop/test/ipc-workspace.test.ts apps/desktop/test/mobile-relay-ipc.test.ts apps/desktop/test/mobile-relay-bridge.test.ts test/h3.mobile-http-jsonrpc.test.ts test/h3.pairing-store.test.ts test/mobile.secure-transport-client.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: recover workspace after mobile h3 stop failure

Recover the workspace server without H3 when stopping mobile access fails during the restart path, matching the rotation recovery behavior and keeping stop non-destructive for the workspace server.

Tests: bun test apps/desktop/test/mobile-relay-bridge.test.ts apps/desktop/test/server-manager.test.ts test/h3.pairing-store.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: protect mobile h3 pairing ticket endpoint

Require the internal mobile H3 admin bearer token before serving the pairing ticket endpoint so network callers cannot fetch the QR ticket secret directly.

Tests: bun test test/h3.mobile-http-jsonrpc.test.ts test/h3.pairing-store.test.ts apps/desktop/test/mobile-relay-bridge.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: clear stale mobile h3 lifecycle state

Recover the previous workspace server without H3 when workspace switching fails before the new mobile endpoint starts, and clear stale start options after rotation recovery so later stop calls do not restart an already recovered non-H3 server.

Tests: bun test apps/desktop/test/mobile-relay-bridge.test.ts apps/desktop/test/server-manager.test.ts test/h3.mobile-http-jsonrpc.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: harden mobile h3 pairing lifecycle

Include IPv6 interface addresses in wildcard mobile H3 host hints, preserve existing mobile H3 state when desktop callers omit the option, report connected bridge state for trusted devices, avoid burning pairing nonces before trust persistence succeeds, and simplify bridge switch recovery state.

Tests: bun test test/server.listening-hints.test.ts apps/desktop/test/mobile-relay-bridge.test.ts apps/desktop/test/server-manager.test.ts test/h3.mobile-http-jsonrpc.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: stabilize mobile relay restore states

Transition the desktop relay bridge back to pairing after trust revocation, clear relay state during app shutdown without spawning a replacement workspace server, and validate mobile SecureStore restore payloads before using persisted trusted desktop/session state.

Tests: bun test apps/desktop/test/mobile-relay-bridge.test.ts apps/desktop/test/electron-shutdown.test.ts test/mobile.secure-transport-client.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: stabilize mobile relay device trust

Persist a stable mobile device id for QR pairing, clear stale forgetTrustedPhone error state on successful retry, and stop tracking active mobile relay options when the workspace server cannot provide an H3 endpoint.

Tests: bun test apps/desktop/test/mobile-relay-bridge.test.ts test/mobile.secure-transport-client.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: rotate pairing after mobile trust revoke

Rotate the mobile H3 endpoint after successfully forgetting trusted phone state so the Remote Access QR advertises a fresh nonce that can be scanned immediately for re-pairing.

Tests: bun test apps/desktop/test/mobile-relay-bridge.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

* fix: close mobile h3 rpc on event disconnect

Close and remove the server-side H3 JSON-RPC connection when the mobile SSE events stream is canceled so subscriptions and pending prompts are reset before the next reconnect.

Tests: bun test test/h3.mobile-http-jsonrpc.test.ts; bun run typecheck; bun run check; bun run docs:check; git diff --check

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>

Parent300c932

74 files changed
  • apps/desktop/electron/ipc/mobileRelay.ts+5−73
  • apps/desktop/electron/ipc/workspace.ts+2−2
  • apps/desktop/electron/main.ts+1−1
  • apps/desktop/electron/services/mobileRelayBridge.ts+202−1116
  • apps/desktop/electron/services/mobileRelayStore.ts+0−176
  • apps/desktop/electron/services/mobileRelayTypes.ts+15−80
  • apps/desktop/electron/services/remodexState.ts+0−480
apps/desktop/electron/services/serverManager.ts
+139
−13
  • apps/desktop/electron/services/shutdown.ts+1−2
  • apps/desktop/src/lib/desktopApi.ts+16−8
  • apps/desktop/src/lib/desktopCommands.ts+1−0
  • apps/desktop/src/lib/desktopSchemas.ts+21−10
  • apps/desktop/src/lib/webAdapter.ts+5−0
  • apps/desktop/src/ui/settings/pages/RemoteAccessPage.tsx+36−32
  • apps/desktop/test/desktop-schemas.test.ts+135−12
  • apps/desktop/test/helpers/mockDesktopCommands.ts+5−0
  • apps/desktop/test/ipc-workspace.test.ts+75−32
  • apps/desktop/test/mobile-relay-bridge.test.ts+299−1641
  • apps/desktop/test/mobile-relay-ipc.test.ts+15−136
  • apps/desktop/test/remodex-state.test.ts+0−269
  • apps/desktop/test/remote-access-page.test.ts+87−104
  • apps/desktop/test/server-manager.test.ts+84−0
  • apps/desktop/test/settings-nav.test.ts+5−0
  • apps/mobile/modules/cowork-pinned-https/android/src/main/java/co/weinbach/cowork/mobile/pinnedhttps/CoworkPinnedHttpsModule.kt+230−0
  • apps/mobile/modules/cowork-pinned-https/expo-module.config.json+9−0
  • apps/mobile/modules/cowork-pinned-https/ios/CoworkPinnedHttpsModule.swift+437−0
  • apps/mobile/modules/cowork-pinned-https/package.json+6−0
  • apps/mobile/modules/cowork-pinned-https/src/index.ts+1−0
  • apps/mobile/modules/remodex-secure-transport/android/src/main/java/expo/modules/remodexsecuretransport/RemodexSecureTransportModule.kt+0−97
  • apps/mobile/modules/remodex-secure-transport/android/src/main/java/expo/modules/remodexsecuretransport/RemodexSecureTransportPackage.kt+0−8
  • apps/mobile/modules/remodex-secure-transport/expo-module.config.json+0−9
  • apps/mobile/modules/remodex-secure-transport/ios/RemodexSecureTransportModule.swift+0−77
  • apps/mobile/modules/remodex-secure-transport/package.json+0−17
  • apps/mobile/modules/remodex-secure-transport/src/RemodexSecureTransport.types.ts+0−41
  • apps/mobile/modules/remodex-secure-transport/src/transportTypes.ts+0−129
  • apps/mobile/package.json+1−0
  • apps/mobile/src/app/(app)/workspace/general.tsx+1−2
  • apps/mobile/src/app/(pairing)/index.tsx+8−8
  • apps/mobile/src/app/(pairing)/scan.tsx+2−2
  • apps/mobile/src/features/pairing/pairingTypes.ts+9−6
  • apps/mobile/src/features/pairing/qrValidation.ts+7−18
  • apps/mobile/src/features/relay/connectionState.ts+1−1
  • apps/mobile/src/features/relay/secureTransportClient.ts+718−107
  • bun.lock+36−3
  • docs/mobile-remote-access.md+25−49
  • docs/quic-pairing.md+57−0
  • docs/websocket-protocol.md+15−0
  • package.json+2−2
  • src/server/index.ts+69−8
  • src/server/jsonrpc/dispatchJsonRpcMessage.ts+3−1
  • src/server/runtime/ServerRuntime.ts+22−1
  • src/server/runtime/SocketSendQueue.ts+22−1
  • src/server/startServer.ts+64−1
  • src/server/startServer/types.ts+9−2
  • src/server/transport/h3/pairing.ts+243−0
  • src/server/transport/h3/server.ts+517−0
  • src/shared/coworkTicket.ts+93−0
  • src/shared/mobileRelaySecurity.ts+0−542
  • src/shared/quicCert.ts+86−0
  • test/h3.mobile-http-jsonrpc.test.ts+167−0
  • test/h3.pairing-store.test.ts+49−0
  • test/mobile.pairing-qrcode.test.ts+21−24
  • test/mobile.pairing-scan-handler.test.ts+9−7
  • test/mobile.relay-security.test.ts+0−197
  • test/mobile.remodex-native-contract.test.ts+0−47
  • test/mobile.secure-transport-client.test.ts+454−0
  • test/mobile.transport-integration.test.ts+0−263
  • test/mobile.transport.relay-role.test.ts+0−21
  • test/mobile.transport.secure-finalize.test.ts+0−374
  • test/package-manifest.test.ts+0−1
  • test/server.listening-hints.test.ts+42−0
  • test/server.test.ts+40−0
  • test/shared.cowork-ticket.test.ts+45−0
  • test/shared.quic-cert.test.ts+24−0