From c3fb476e4a7e0844e05e0f1437d13fd3b2b82f89 Mon Sep 17 00:00:00 2001 From: slkzgm Date: Mon, 19 Jan 2026 15:50:43 +0100 Subject: [PATCH 1/2] feat(remote): proxy workspace management and normalize WSL paths --- src-tauri/src/codex.rs | 6 +++++ src-tauri/src/remote_backend.rs | 34 +++++++++++++++++++++++ src-tauri/src/workspaces.rs | 48 +++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/src-tauri/src/codex.rs b/src-tauri/src/codex.rs index 21211396b..44e2327f2 100644 --- a/src-tauri/src/codex.rs +++ b/src-tauri/src/codex.rs @@ -254,6 +254,12 @@ pub(crate) async fn send_user_message( app: AppHandle, ) -> Result { if remote_backend::is_remote_mode(&*state).await { + let images = images.map(|paths| { + paths + .into_iter() + .map(remote_backend::normalize_path_for_remote) + .collect::>() + }); return remote_backend::call_remote( &*state, app, diff --git a/src-tauri/src/remote_backend.rs b/src-tauri/src/remote_backend.rs index a46f6b839..a844f0fff 100644 --- a/src-tauri/src/remote_backend.rs +++ b/src-tauri/src/remote_backend.rs @@ -16,6 +16,40 @@ const DISCONNECTED_MESSAGE: &str = "remote backend disconnected"; type PendingMap = HashMap>>; +pub(crate) fn normalize_path_for_remote(path: String) -> String { + let trimmed = path.trim(); + if trimmed.is_empty() { + return path; + } + + if let Some(normalized) = normalize_wsl_unc_path(trimmed) { + return normalized; + } + + path +} + +fn normalize_wsl_unc_path(path: &str) -> Option { + let lower = path.to_ascii_lowercase(); + let (prefix_len, raw) = if lower.starts_with("\\\\wsl$\\") { + (7, path) + } else if lower.starts_with("\\\\wsl.localhost\\") { + (16, path) + } else { + return None; + }; + + let remainder = raw.get(prefix_len..)?; + let mut segments = remainder.split('\\').filter(|segment| !segment.is_empty()); + segments.next()?; + let joined = segments.collect::>().join("/"); + Some(if joined.is_empty() { + "/".to_string() + } else { + format!("/{joined}") + }) +} + #[derive(Clone)] pub(crate) struct RemoteBackend { inner: Arc, diff --git a/src-tauri/src/workspaces.rs b/src-tauri/src/workspaces.rs index 89dcfe085..56c2a7408 100644 --- a/src-tauri/src/workspaces.rs +++ b/src-tauri/src/workspaces.rs @@ -288,6 +288,7 @@ pub(crate) async fn add_workspace( app: AppHandle, ) -> Result { if remote_backend::is_remote_mode(&*state).await { + let path = remote_backend::normalize_path_for_remote(path); let response = remote_backend::call_remote( &*state, app, @@ -487,6 +488,17 @@ pub(crate) async fn add_worktree( state: State<'_, AppState>, app: AppHandle, ) -> Result { + if remote_backend::is_remote_mode(&*state).await { + let response = remote_backend::call_remote( + &*state, + app, + "add_worktree", + json!({ "parentId": parent_id, "branch": branch }), + ) + .await?; + return serde_json::from_value(response).map_err(|err| err.to_string()); + } + let branch = branch.trim(); if branch.is_empty() { return Err("Branch name is required.".to_string()); @@ -580,7 +592,13 @@ pub(crate) async fn add_worktree( pub(crate) async fn remove_workspace( id: String, state: State<'_, AppState>, + app: AppHandle, ) -> Result<(), String> { + if remote_backend::is_remote_mode(&*state).await { + remote_backend::call_remote(&*state, app, "remove_workspace", json!({ "id": id })).await?; + return Ok(()); + } + let (entry, child_worktrees) = { let workspaces = state.workspaces.lock().await; let entry = workspaces @@ -637,7 +655,13 @@ pub(crate) async fn remove_workspace( pub(crate) async fn remove_worktree( id: String, state: State<'_, AppState>, + app: AppHandle, ) -> Result<(), String> { + if remote_backend::is_remote_mode(&*state).await { + remote_backend::call_remote(&*state, app, "remove_worktree", json!({ "id": id })).await?; + return Ok(()); + } + let (entry, parent) = { let workspaces = state.workspaces.lock().await; let entry = workspaces @@ -816,7 +840,19 @@ pub(crate) async fn update_workspace_settings( id: String, settings: WorkspaceSettings, state: State<'_, AppState>, + app: AppHandle, ) -> Result { + if remote_backend::is_remote_mode(&*state).await { + let response = remote_backend::call_remote( + &*state, + app, + "update_workspace_settings", + json!({ "id": id, "settings": settings }), + ) + .await?; + return serde_json::from_value(response).map_err(|err| err.to_string()); + } + let (entry_snapshot, list) = { let mut workspaces = state.workspaces.lock().await; let entry_snapshot = apply_workspace_settings_update(&mut workspaces, &id, settings)?; @@ -844,7 +880,19 @@ pub(crate) async fn update_workspace_codex_bin( id: String, codex_bin: Option, state: State<'_, AppState>, + app: AppHandle, ) -> Result { + if remote_backend::is_remote_mode(&*state).await { + let response = remote_backend::call_remote( + &*state, + app, + "update_workspace_codex_bin", + json!({ "id": id, "codex_bin": codex_bin }), + ) + .await?; + return serde_json::from_value(response).map_err(|err| err.to_string()); + } + let (entry_snapshot, list) = { let mut workspaces = state.workspaces.lock().await; let entry_snapshot = match workspaces.get_mut(&id) { From 9e1973f0d8138daa697145ceef50949eb4d49131 Mon Sep 17 00:00:00 2001 From: slkzgm Date: Thu, 22 Jan 2026 11:34:25 +0100 Subject: [PATCH 2/2] fix(remote): normalize codex_bin UNC paths --- src-tauri/src/workspaces.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src-tauri/src/workspaces.rs b/src-tauri/src/workspaces.rs index 56c2a7408..4b1ccf7d5 100644 --- a/src-tauri/src/workspaces.rs +++ b/src-tauri/src/workspaces.rs @@ -289,6 +289,7 @@ pub(crate) async fn add_workspace( ) -> Result { if remote_backend::is_remote_mode(&*state).await { let path = remote_backend::normalize_path_for_remote(path); + let codex_bin = codex_bin.map(remote_backend::normalize_path_for_remote); let response = remote_backend::call_remote( &*state, app, @@ -883,6 +884,7 @@ pub(crate) async fn update_workspace_codex_bin( app: AppHandle, ) -> Result { if remote_backend::is_remote_mode(&*state).await { + let codex_bin = codex_bin.map(remote_backend::normalize_path_for_remote); let response = remote_backend::call_remote( &*state, app,