diff --git a/codex-rs/tui/src/app/session_picker.rs b/codex-rs/tui/src/app/session_picker.rs index 9152ac86cd..cfb34ff43c 100644 --- a/codex-rs/tui/src/app/session_picker.rs +++ b/codex-rs/tui/src/app/session_picker.rs @@ -41,6 +41,8 @@ impl App { return Ok(AppRunControl::Continue); } }; + picker_app_server = picker_app_server + .with_remote_cwd_override(app_server.remote_cwd_override().map(Path::to_path_buf)); picker_app_server.model_provider_override = self.harness_overrides.model_provider.clone(); let selection = crate::resume_picker::run_resume_picker_from_existing_session_with_app_server( diff --git a/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs b/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs index 062efa383d..bca048a4ea 100644 --- a/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs +++ b/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs @@ -926,6 +926,89 @@ async fn make_history_test_app() -> Result<(Box, tempfile::TempDir)> { Ok((app, codex_home)) } +#[tokio::test] +async fn in_app_remote_resume_picker_preserves_remote_cwd_filter() -> Result<()> { + let (mut app, _codex_home) = make_history_test_app().await?; + let remote_root = tempdir()?; + let remote_cwd = remote_root.path().join("project"); + std::fs::create_dir(&remote_cwd)?; + let (app_server, _requests, active_proxy) = + start_recording_remote_app_server(&app.config).await?; + let mut app_server = app_server.with_remote_cwd_override(Some(remote_cwd.clone())); + app.harness_overrides.model_provider = Some(app.config.model_provider_id.clone()); + + let listener = TcpListener::bind("127.0.0.1:0").await?; + app.app_server_target = AppServerTarget::Remote { + endpoint: crate::resolve_remote_addr(&format!("ws://{}", listener.local_addr()?))?, + }; + let (request_tx, request_rx) = oneshot::channel(); + let (release_tx, release_rx) = oneshot::channel::<()>(); + let codex_home = app.config.codex_home.display().to_string(); + let recorder = tokio::spawn(async move { + let (socket, _) = listener.accept().await?; + let mut websocket = accept_async(socket).await?; + while let Some(frame) = websocket.next().await { + let Message::Text(text) = frame? else { + continue; + }; + match serde_json::from_str::(&text)? { + JSONRPCMessage::Request(request) if request.method == "initialize" => { + let response = JSONRPCMessage::Response(JSONRPCResponse { + id: request.id, + result: serde_json::json!({ + "userAgent": "codex-tui-test", + "codexHome": codex_home, + }), + }); + websocket + .send(Message::Text(serde_json::to_string(&response)?.into())) + .await?; + } + JSONRPCMessage::Request(request) if request.method == "thread/list" => { + let _ = request_tx.send(request.params.unwrap_or_default()); + let _ = release_rx.await; + return Ok::<(), color_eyre::Report>(()); + } + _ => {} + } + } + Err(color_eyre::eyre::eyre!( + "picker disconnected before requesting its thread list" + )) + }); + + let mut tui = crate::tui::test_support::make_test_tui()?; + tui.set_alt_screen_enabled(/*enabled*/ false); + tui.pause_events(); + let mut picker = Box::pin(app.open_resume_picker(&mut tui, &mut app_server)); + let params = tokio::time::timeout(std::time::Duration::from_secs(/*secs*/ 5), async { + tokio::select! { + biased; + params = request_rx => params.map_err(|_| { + color_eyre::eyre::eyre!("picker request recorder closed") + }), + result = &mut picker => { + result?; + Err(color_eyre::eyre::eyre!( + "picker exited before requesting its thread list" + )) + } + } + }) + .await??; + drop(picker); + let _ = release_tx.send(()); + recorder.await??; + app_server.shutdown().await?; + active_proxy.await??; + + assert_eq!( + params["cwd"], + serde_json::Value::String(remote_cwd.display().to_string()) + ); + Ok(()) +} + #[tokio::test] async fn delete_current_thread_navigates_only_after_success() -> Result<()> { let endpoint = crate::resolve_remote_addr("ws://127.0.0.1:4500")?;