Skip to content

Commit 1db4e05

Browse files
committed
feat(remote): implement Orbit WS transport and provider rename
1 parent 93334d6 commit 1db4e05

13 files changed

Lines changed: 429 additions & 112 deletions

File tree

docs/mobile-ios-cloudflare-blueprint.md

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,22 @@ This document is the canonical implementation plan for shipping CodexMonitor on
1919
## Current State (Important)
2020

2121
- Tauri app is desktop-first with `#[cfg_attr(mobile, tauri::mobile_entry_point)]` already present in `src-tauri/src/lib.rs`.
22-
- `src-tauri/src/remote_backend.rs` currently uses raw TCP `host:port` and optional token auth.
22+
- `remote_backend` has been refactored into pluggable transport modules:
23+
- `src-tauri/src/remote_backend/mod.rs`
24+
- `src-tauri/src/remote_backend/protocol.rs`
25+
- `src-tauri/src/remote_backend/transport.rs`
26+
- `src-tauri/src/remote_backend/tcp_transport.rs`
27+
- `src-tauri/src/remote_backend/orbit_ws_transport.rs`
28+
- Current transport behavior:
29+
- TCP transport remains intact (existing remote path preserved).
30+
- Orbit WS transport is implemented for connect/read/write + request/response routing.
31+
- Reconnect/backoff and replay/resync hardening are still pending.
32+
- Remote provider settings baseline is implemented:
33+
- `remoteBackendProvider`: `"tcp" | "orbit"`
34+
- `remoteBackendHost`, `remoteBackendToken`
35+
- `orbitDeploymentMode`, `orbitWsUrl`, `orbitAuthUrl`
36+
- `orbitRunnerName`, `orbitAutoStartRunner`
37+
- `orbitUseAccess`, `orbitAccessClientId`, `orbitAccessClientSecretRef`
2338
- Remote notification forwarding currently handles only:
2439
- `app-server-event`
2540
- `terminal-output`
@@ -89,7 +104,7 @@ Advanced setup for users who want full control.
89104

90105
Target: keep existing `call_remote(...)` callsites while replacing transport internals.
91106

92-
Proposed structure:
107+
Implemented structure:
93108

94109
- `src-tauri/src/remote_backend/mod.rs`
95110
- `src-tauri/src/remote_backend/protocol.rs`
@@ -105,21 +120,33 @@ Proposed structure:
105120
- `close()`
106121
- `status()`
107122

108-
## 2) Add Orbit configuration to settings model
123+
Current status:
124+
125+
- Done: transport split + provider routing + Orbit WS connect/read/write path.
126+
- Pending: reconnect strategy, backoff policy, replay/resync contract integration.
127+
128+
## 2) Add bridge configuration to settings model
109129

110130
Extend `AppSettings` in `src-tauri/src/types.rs` and UI types in `src/types.ts`.
111131

112-
Add section:
132+
Implemented baseline fields:
113133

114-
- `remoteBridgeProvider`: `"tcp" | "orbit"`
115-
- `orbitDeploymentMode`: `"hosted" | "self_hosted"`
116-
- `orbitWsUrl` (required for self-host; optional/derived for hosted)
117-
- `orbitAuthUrl` (required for self-host; optional/derived for hosted)
134+
- `remoteBackendProvider`: `"tcp" | "orbit"`
135+
- `remoteBackendHost`
136+
- `remoteBackendToken`
137+
- `orbitDeploymentMode`
138+
- `orbitWsUrl`
139+
- `orbitAuthUrl`
118140
- `orbitRunnerName`
119-
- `orbitAutoStartRunner` (bool)
120-
- `orbitUseAccess` (bool, self-host optional)
121-
- `orbitAccessClientId` (non-secret allowed)
122-
- `orbitAccessClientSecretRef` (secret reference only)
141+
- `orbitAutoStartRunner`
142+
- `orbitUseAccess`
143+
- `orbitAccessClientId`
144+
- `orbitAccessClientSecretRef`
145+
146+
Planned next (not yet implemented in settings model):
147+
148+
- deployment/auth/pairing metadata required for full hosted/self-host Orbit UX
149+
- secure-storage integration for secret material lifecycle (set/reset/rotation)
123150

124151
Keep secrets out of plain `settings.json` where possible.
125152

@@ -446,7 +473,7 @@ cargo test
446473

447474
1. Milestone A: iOS compile baseline + mobile-safe stubs.
448475
2. Milestone B: Orbit integration baseline (hosted + self-host config paths).
449-
3. Milestone C: `remote_backend` transport refactor + Orbit transport + runner Orbit mode.
476+
3. Milestone C: `remote_backend` transport refactor + Orbit WS transport + runner Orbit mode.
450477
4. Milestone D: daemon parity closure for mobile scope (excluding terminal/dictation).
451478
5. Milestone E: Settings UX/service manager + pairing UX.
452479
6. Milestone F: full E2E validation and TestFlight beta.

memory/decisions.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,31 @@ Type: decision
211211
Event: Remote backend was a single TCP-specific module with no transport-level provider split.
212212
Action: Refactored `src-tauri/src/remote_backend.rs` into `remote_backend/{mod,protocol,transport,tcp_transport,cloudflare_ws_transport}.rs`, added `remoteBackendProvider` + Cloudflare settings fields, kept TCP behavior as default, and added a Cloudflare transport stub that returns a clear not-implemented error.
213213
Rule: Keep remote transport wiring behind `RemoteTransport` and use provider selection in settings so new bridge transports can be added without touching command callsites.
214+
215+
## 2026-02-07 17:58
216+
Context: Cloudflare transport implementation pass
217+
Type: decision
218+
Event: Cloudflare transport stub blocked real remote bridge connectivity testing.
219+
Action: Implemented `cloudflare_ws_transport` with real WebSocket connect/read/write loops via `tokio-tungstenite`, shared incoming dispatch/pending-response handling, URL normalization to `/ws/{sessionId}`, and transport-level disconnect propagation.
220+
Rule: New remote transports should reuse shared dispatch/disconnect helpers and preserve the same request/response semantics as TCP transport.
221+
222+
## 2026-02-07 18:00
223+
Context: Mobile Cloudflare blueprint canonicalization
224+
Type: decision
225+
Event: Blueprint still referenced pre-refactor remote backend structure and outdated settings keys after transport work landed.
226+
Action: Updated `docs/mobile-ios-cloudflare-blueprint.md` to reflect implemented remote backend module split, Cloudflare WS transport status, current provider settings fields, and remaining reconnect/replay hardening work.
227+
Rule: Keep blueprint "Current State" and backend/settings sections synchronized with merged transport architecture before starting new milestone work.
228+
229+
## 2026-02-07 18:09
230+
Context: Orbit-only provider canonicalization
231+
Type: decision
232+
Event: Backend/provider/settings naming still reflected Cloudflare-specific labels after Orbit-only direction was finalized.
233+
Action: Renamed remote transport/provider/settings model to Orbit (`orbit_ws_transport`, `RemoteBackendProvider::Orbit`, `orbit*` settings), retained serde aliases for legacy Cloudflare config keys, and updated the mobile blueprint/todo notes to Orbit-first language.
234+
Rule: Keep runtime/provider naming Orbit-first while preserving narrow backward-compatible aliases only for persisted legacy settings.
235+
236+
## 2026-02-07 18:13
237+
Context: Orbit-only feature branch compatibility policy
238+
Type: preference
239+
Event: User requested removing all unreleased Cloudflare fallback/backport compatibility paths and tests.
240+
Action: Removed provider/url/session legacy compatibility aliases, removed legacy session URL injection logic, and deleted backport-focused tests from remote backend and settings models.
241+
Rule: For this unreleased Orbit workstream, keep settings and transport strictly canonical without backward-compat adapters.

memory/todo.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Open
44
- [ ] 2026-02-07: Implement Orbit-only mobile remote foundation: Orbit transport in `remote_backend`, runner Orbit mode, hosted/self-host settings + pairing UX, and iOS keychain-backed auth storage.
5-
- [ ] 2026-02-07: Implement Cloudflare WebSocket transport internals behind `remoteBackendProvider=cloudflare` (handshake/auth/send/subscribe/reconnect/replay) and wire settings UX for worker/session inputs.
5+
- [ ] 2026-02-07: Harden Orbit WS transport with explicit reconnect/backoff + replay/resync handling for mobile reconnect flows.
66

77
## Done
88
- [x] 2026-02-07: Restored Sentry frontend reporting removed in `83a37da` (`@sentry/react`, `Sentry.init`, captureException callsites, and metrics instrumentation).
@@ -14,3 +14,6 @@
1414
- [x] 2026-02-07: Replace daemon `local_usage_snapshot` temporary implementation by sharing real local usage scanner logic in `shared/local_usage_core.rs`.
1515
- [x] 2026-02-07: Eliminate app/daemon duplication for git/github/prompt/codex utility behavior via shared cores (`git_ui_core`, `prompts_core`, `codex_aux_core`).
1616
- [x] 2026-02-07: Move remaining duplicated workspace-action logic (`add_clone`, `apply_worktree_changes`, `open_workspace_in`, `get_open_app_icon`) into shared core helpers.
17+
- [x] 2026-02-07: Implement Cloudflare WebSocket transport internals behind `remoteBackendProvider=cloudflare` (connect/read/write/pending-response flow) while preserving TCP behavior and callsites.
18+
- [x] 2026-02-07: Migrate remote provider naming/settings from Cloudflare-specific keys to Orbit (`orbit` provider + `orbit*` settings).
19+
- [x] 2026-02-07: Remove unreleased Cloudflare backward-compat aliases and backport tests to keep Orbit implementation canonical-only.

src-tauri/Cargo.lock

Lines changed: 66 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ tauri-plugin-process = "2"
3030
serde = { version = "1", features = ["derive"] }
3131
serde_json = "1"
3232
tokio = { version = "1", features = ["fs", "net", "io-util", "process", "rt", "sync", "time"] }
33+
futures-util = "0.3"
34+
tokio-tungstenite = { version = "0.24", features = ["rustls-tls-webpki-roots"] }
3335
uuid = { version = "1", features = ["v4"] }
3436
tauri-plugin-dialog = "2"
3537
git2 = "0.20.3"

src-tauri/src/remote_backend/cloudflare_ws_transport.rs

Lines changed: 0 additions & 26 deletions
This file was deleted.

src-tauri/src/remote_backend/mod.rs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
mod cloudflare_ws_transport;
1+
mod orbit_ws_transport;
22
mod protocol;
33
mod tcp_transport;
44
mod transport;
@@ -13,7 +13,7 @@ use tokio::sync::Mutex;
1313
use crate::state::AppState;
1414
use crate::types::{BackendMode, RemoteBackendProvider};
1515

16-
use self::cloudflare_ws_transport::CloudflareWsTransport;
16+
use self::orbit_ws_transport::OrbitWsTransport;
1717
use self::protocol::{build_request_line, DEFAULT_REMOTE_HOST, DISCONNECTED_MESSAGE};
1818
use self::tcp_transport::TcpTransport;
1919
use self::transport::{PendingMap, RemoteTransport, RemoteTransportConfig, RemoteTransportKind};
@@ -121,7 +121,7 @@ async fn ensure_remote_backend(state: &AppState, app: AppHandle) -> Result<Remot
121121

122122
let transport: Box<dyn RemoteTransport> = match transport_config.kind() {
123123
RemoteTransportKind::Tcp => Box::new(TcpTransport),
124-
RemoteTransportKind::CloudflareWs => Box::new(CloudflareWsTransport),
124+
RemoteTransportKind::OrbitWs => Box::new(OrbitWsTransport),
125125
};
126126
let connection = transport.connect(app, transport_config).await?;
127127

@@ -164,28 +164,37 @@ fn resolve_transport_config(
164164
auth_token: settings.remote_backend_token.clone(),
165165
})
166166
}
167-
RemoteBackendProvider::Cloudflare => {
168-
let worker_url = settings
169-
.cloudflare_worker_url
167+
RemoteBackendProvider::Orbit => {
168+
let ws_url = settings
169+
.orbit_ws_url
170170
.as_ref()
171171
.map(|value| value.trim().to_string())
172172
.filter(|value| !value.is_empty())
173-
.ok_or_else(|| {
174-
"Cloudflare provider requires cloudflareWorkerUrl in app settings.".to_string()
175-
})?;
176-
let session_id = settings
177-
.cloudflare_session_id
178-
.as_ref()
179-
.map(|value| value.trim().to_string())
180-
.filter(|value| !value.is_empty())
181-
.ok_or_else(|| {
182-
"Cloudflare provider requires cloudflareSessionId in app settings.".to_string()
183-
})?;
184-
Ok(RemoteTransportConfig::CloudflareWs {
185-
worker_url,
186-
session_id,
173+
.ok_or_else(|| "Orbit provider requires orbitWsUrl in app settings.".to_string())?;
174+
Ok(RemoteTransportConfig::OrbitWs {
175+
ws_url,
187176
auth_token: settings.remote_backend_token.clone(),
188177
})
189178
}
190179
}
191180
}
181+
182+
#[cfg(test)]
183+
mod tests {
184+
use super::resolve_transport_config;
185+
use crate::remote_backend::transport::RemoteTransportConfig;
186+
use crate::types::{AppSettings, RemoteBackendProvider};
187+
188+
#[test]
189+
fn resolve_orbit_transport_uses_orbit_ws_url() {
190+
let mut settings = AppSettings::default();
191+
settings.remote_backend_provider = RemoteBackendProvider::Orbit;
192+
settings.orbit_ws_url = Some("https://orbit.example/ws/live".to_string());
193+
194+
let config = resolve_transport_config(&settings).expect("transport config");
195+
let RemoteTransportConfig::OrbitWs { ws_url, .. } = config else {
196+
panic!("expected orbit transport config");
197+
};
198+
assert_eq!(ws_url, "https://orbit.example/ws/live");
199+
}
200+
}

0 commit comments

Comments
 (0)