Codex Handbook
app-server/tests/suite/v2/remote_control.rs 1091 lines
use std::ffi::OsStr;use std::ffi::OsString;use std::io::ErrorKind;use std::time::Duration;use anyhow::Context;use anyhow::Result;use app_test_support::ChatGptAuthFixture;use app_test_support::DEFAULT_CLIENT_NAME;use app_test_support::TestAppServer;use app_test_support::to_response;use app_test_support::write_chatgpt_auth;use app_test_support::write_mock_responses_config_toml_with_chatgpt_base_url;use codex_app_server::AppServerRuntimeOptions;use codex_app_server::AppServerTransport;use codex_app_server::AppServerWebsocketAuthSettings;use codex_app_server::PluginStartupTasks;use codex_app_server::RemoteControlStartupMode;use codex_app_server::run_main_with_transport_options;use codex_app_server_protocol::JSONRPCError;use codex_app_server_protocol::JSONRPCResponse;use codex_app_server_protocol::RemoteControlClient;use codex_app_server_protocol::RemoteControlClientsListOrder;use codex_app_server_protocol::RemoteControlClientsListParams;use codex_app_server_protocol::RemoteControlClientsListResponse;use codex_app_server_protocol::RemoteControlClientsRevokeParams;use codex_app_server_protocol::RemoteControlClientsRevokeResponse;use codex_app_server_protocol::RemoteControlConnectionStatus;use codex_app_server_protocol::RemoteControlDisableResponse;use codex_app_server_protocol::RemoteControlEnableResponse;use codex_app_server_protocol::RemoteControlPairingStartParams;use codex_app_server_protocol::RemoteControlPairingStartResponse;use codex_app_server_protocol::RemoteControlPairingStatusParams;use codex_app_server_protocol::RemoteControlPairingStatusResponse;use codex_app_server_protocol::RemoteControlStatusChangedNotification;use codex_app_server_protocol::RemoteControlStatusReadResponse;use codex_app_server_protocol::RequestId;use codex_arg0::Arg0DispatchPaths;use codex_config::LoaderOverrides;use codex_config::types::AuthCredentialsStoreMode;use codex_protocol::protocol::SessionSource;use codex_state::RemoteControlEnrollmentRecord;use codex_state::StateRuntime;use codex_utils_cli::CliConfigOverrides;use pretty_assertions::assert_eq;use serial_test::serial;use tempfile::TempDir;use tokio::io::AsyncBufReadExt;use tokio::io::AsyncReadExt;use tokio::io::AsyncWriteExt;use tokio::io::BufReader;use tokio::net::TcpListener;use tokio::net::TcpStream;use tokio::sync::oneshot;use tokio::task::JoinHandle;use tokio::time::timeout;const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);const STARTUP_TIMEOUT: Duration = Duration::from_secs(30);const REMOTE_CONTROL_DISABLED_BY_REQUIREMENTS_MESSAGE: &str =    "remote control is disabled by managed requirements";struct EnvVarGuard {    key: &'static str,    original: Option<OsString>,}impl EnvVarGuard {    fn set(key: &'static str, value: &OsStr) -> Self {        let original = std::env::var_os(key);        unsafe {            std::env::set_var(key, value);        }        Self { key, original }    }}impl Drop for EnvVarGuard {    fn drop(&mut self) {        unsafe {            match &self.original {                Some(value) => std::env::set_var(self.key, value),                None => std::env::remove_var(self.key),            }        }    }}async fn remote_control_preference(    state_db: &StateRuntime,    websocket_url: &str,) -> Result<Option<bool>> {    Ok(state_db        .get_remote_control_enrollment(websocket_url, "account_id", Some(DEFAULT_CLIENT_NAME))        .await?        .context("enrollment should exist")?        .remote_control_enabled)}async fn wait_for_response(mcp: &mut TestAppServer, request_id: i64) -> Result<JSONRPCResponse> {    timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await?}async fn assert_remote_control_disabled_by_requirements(    mcp: &mut TestAppServer,    request_id: i64,) -> Result<()> {    let JSONRPCError { error, .. } = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_error_message(RequestId::Integer(request_id)),    )    .await??;    assert_eq!(error.code, -32600);    assert_eq!(        error.message,        REMOTE_CONTROL_DISABLED_BY_REQUIREMENTS_MESSAGE    );    Ok(())}#[tokio::test]async fn managed_requirements_reject_all_remote_control_rpcs() -> Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join("requirements.toml"),        "allow_remote_control = false\n",    )?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let notification = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_notification_message("remoteControl/status/changed"),    )    .await??;    let status: RemoteControlStatusChangedNotification = serde_json::from_value(        notification            .params            .context("remote-control status notification should include params")?,    )?;    assert_eq!(status.status, RemoteControlConnectionStatus::Disabled);    assert_eq!(status.environment_id, None);    let request_ids = [        mcp.send_remote_control_enable_request().await?,        mcp.send_remote_control_disable_request().await?,        mcp.send_remote_control_status_read_request().await?,        mcp.send_remote_control_pairing_start_request(RemoteControlPairingStartParams {            manual_code: false,        })        .await?,        mcp.send_remote_control_pairing_status_request(RemoteControlPairingStatusParams {            pairing_code: Some("pairing-code".to_string()),            manual_pairing_code: None,        })        .await?,        mcp.send_remote_control_clients_list_request(RemoteControlClientsListParams {            environment_id: "environment-id".to_string(),            cursor: None,            limit: None,            order: None,        })        .await?,        mcp.send_remote_control_clients_revoke_request(RemoteControlClientsRevokeParams {            environment_id: "environment-id".to_string(),            client_id: "client-id".to_string(),        })        .await?,    ];    for request_id in request_ids {        assert_remote_control_disabled_by_requirements(&mut mcp, request_id).await?;    }    Ok(())}#[tokio::test]async fn managed_requirements_allow_remote_control_true_does_not_enable_or_block_it() -> Result<()>{    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join("requirements.toml"),        "allow_remote_control = true\n",    )?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let request_id = mcp.send_remote_control_status_read_request().await?;    let response = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    let received: RemoteControlStatusReadResponse = to_response(response)?;    assert_eq!(received.status, RemoteControlConnectionStatus::Disabled);    Ok(())}#[tokio::test]#[serial]async fn explicit_remote_control_startup_fails_when_disabled_by_requirements() -> Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join("requirements.toml"),        "allow_remote_control = false\n",    )?;    let managed_config_path = codex_home.path().join("managed_config.toml");    let socket_path = codex_home.path().join("app-server.sock");    let transport =        AppServerTransport::from_listen_url(&format!("unix://{}", socket_path.display()))?;    let _codex_home_guard = EnvVarGuard::set("CODEX_HOME", codex_home.path().as_os_str());    let result = timeout(        STARTUP_TIMEOUT,        run_main_with_transport_options(            Arg0DispatchPaths {                codex_self_exe: Some(std::env::current_exe()?),                codex_linux_sandbox_exe: None,                main_execve_wrapper_exe: None,            },            CliConfigOverrides::default(),            LoaderOverrides::with_managed_config_path_for_tests(managed_config_path),            /*strict_config*/ false,            /*default_analytics_enabled*/ false,            transport,            SessionSource::VSCode,            AppServerWebsocketAuthSettings::default(),            AppServerRuntimeOptions {                plugin_startup_tasks: PluginStartupTasks::Skip,                remote_control_startup_mode: RemoteControlStartupMode::EnabledEphemeral,                install_shutdown_signal_handler: false,            },        ),    )    .await?;    let err = result.expect_err("managed requirements should reject explicit remote control");    assert_eq!(err.kind(), ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        REMOTE_CONTROL_DISABLED_BY_REQUIREMENTS_MESSAGE    );    assert!(!socket_path.exists());    Ok(())}#[tokio::test]async fn listen_off_honors_persisted_remote_control_enable() -> Result<()> {    let codex_home = TempDir::new()?;    let listener = configured_remote_control_listener(codex_home.path()).await?;    let websocket_url = format!(        "ws://{}/backend-api/wham/remote/control/server",        listener.local_addr()?    );    let state_db =        StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?;    state_db        .upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord {            websocket_url,            account_id: "account_id".to_string(),            app_server_client_name: None,            server_id: "server-id".to_string(),            environment_id: "environment-id".to_string(),            server_name: "server-name".to_string(),            remote_control_enabled: Some(true),        })        .await?;    let _app_server = TestAppServer::new_with_args(codex_home.path(), &["--listen", "off"]).await?;    timeout(STARTUP_TIMEOUT, listener.accept()).await??;    Ok(())}#[tokio::test]async fn listen_off_ignores_persisted_enable_when_disabled_by_requirements() -> Result<()> {    let codex_home = TempDir::new()?;    let listener = configured_remote_control_listener(codex_home.path()).await?;    std::fs::write(        codex_home.path().join("requirements.toml"),        "allow_remote_control = false\n",    )?;    let websocket_url = format!(        "ws://{}/backend-api/wham/remote/control/server",        listener.local_addr()?    );    let state_db =        StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?;    state_db        .upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord {            websocket_url: websocket_url.clone(),            account_id: "account_id".to_string(),            app_server_client_name: None,            server_id: "server-id".to_string(),            environment_id: "environment-id".to_string(),            server_name: "server-name".to_string(),            remote_control_enabled: Some(true),        })        .await?;    let mut app_server =        TestAppServer::new_with_args(codex_home.path(), &["--listen", "off"]).await?;    let status = timeout(STARTUP_TIMEOUT, app_server.wait_for_exit()).await??;    assert!(!status.success());    timeout(Duration::from_millis(100), listener.accept())        .await        .expect_err("managed requirements should prevent a remote-control connection");    assert_eq!(        state_db            .get_remote_control_enrollment(                &websocket_url,                "account_id",                /*app_server_client_name*/ None            )            .await?            .context("enrollment should remain persisted")?            .remote_control_enabled,        Some(true)    );    Ok(())}#[tokio::test]async fn listen_off_exits_without_persisted_remote_control_enable() -> Result<()> {    for persisted_preference in [None, Some(false)] {        let codex_home = TempDir::new()?;        let listener = configured_remote_control_listener(codex_home.path()).await?;        if let Some(remote_control_enabled) = persisted_preference {            let websocket_url = format!(                "ws://{}/backend-api/wham/remote/control/server",                listener.local_addr()?            );            let state_db =                StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string())                    .await?;            state_db                .upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord {                    websocket_url,                    account_id: "account_id".to_string(),                    app_server_client_name: None,                    server_id: "server-id".to_string(),                    environment_id: "environment-id".to_string(),                    server_name: "server-name".to_string(),                    remote_control_enabled: Some(remote_control_enabled),                })                .await?;        }        let mut app_server =            TestAppServer::new_with_args(codex_home.path(), &["--listen", "off"]).await?;        let status = timeout(STARTUP_TIMEOUT, app_server.wait_for_exit()).await??;        assert!(!status.success());    }    Ok(())}#[tokio::test]async fn remote_control_disable_returns_disabled_status() -> Result<()> {    let codex_home = TempDir::new()?;    let _listener = configured_remote_control_listener(codex_home.path()).await?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let request_id = mcp.send_remote_control_disable_request().await?;    let response: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    let received: RemoteControlDisableResponse = to_response(response)?;    assert_eq!(received.status, RemoteControlConnectionStatus::Disabled);    assert!(!received.server_name.is_empty());    assert_eq!(received.environment_id, None);    assert!(!received.installation_id.is_empty());    Ok(())}#[tokio::test]async fn remote_control_status_read_returns_disabled_status() -> Result<()> {    let codex_home = TempDir::new()?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let request_id = mcp.send_remote_control_status_read_request().await?;    let response: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    let received: RemoteControlStatusReadResponse = to_response(response)?;    assert_eq!(received.status, RemoteControlConnectionStatus::Disabled);    assert!(!received.server_name.is_empty());    assert_eq!(received.environment_id, None);    assert!(!received.installation_id.is_empty());    Ok(())}#[tokio::test]async fn remote_control_enable_returns_connecting_status() -> Result<()> {    let codex_home = TempDir::new()?;    let mut backend = BlockingRemoteControlBackend::start(codex_home.path()).await?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let request_id = mcp.send_remote_control_enable_request().await?;    assert_eq!(        timeout(DEFAULT_TIMEOUT, backend.wait_for_enroll_request()).await??,        "POST /backend-api/wham/remote/control/server/enroll HTTP/1.1"    );    timeout(        Duration::from_millis(100),        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await    .expect_err("enable response should wait for enrollment");    backend.complete_enrollment()?;    let response: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    let received: RemoteControlEnableResponse = to_response(response)?;    assert_eq!(received.status, RemoteControlConnectionStatus::Connecting);    assert!(!received.server_name.is_empty());    assert_eq!(received.environment_id.as_deref(), Some("environment-id"));    assert!(!received.installation_id.is_empty());    Ok(())}#[tokio::test]async fn disable_waits_for_in_flight_durable_enable() -> Result<()> {    let codex_home = TempDir::new()?;    let mut backend = BlockingRemoteControlBackend::start(codex_home.path()).await?;    let websocket_url = backend.websocket_url().to_string();    let state_db =        StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    mcp.send_remote_control_enable_request().await?;    timeout(DEFAULT_TIMEOUT, backend.wait_for_enroll_request()).await??;    let disable_request_id = mcp.send_remote_control_disable_request().await?;    timeout(        Duration::from_millis(100),        mcp.read_stream_until_response_message(RequestId::Integer(disable_request_id)),    )    .await    .expect_err("disable response should wait for the in-flight enable");    backend.complete_enrollment()?;    let response = wait_for_response(&mut mcp, disable_request_id).await?;    let received: RemoteControlDisableResponse = to_response(response)?;    assert_eq!(received.status, RemoteControlConnectionStatus::Disabled);    assert_eq!(        remote_control_preference(&state_db, &websocket_url).await?,        Some(false)    );    Ok(())}#[tokio::test]async fn rpc_updates_durable_preference_but_ephemeral_does_not() -> Result<()> {    let codex_home = TempDir::new()?;    let mut backend = BlockingRemoteControlBackend::start(codex_home.path()).await?;    let websocket_url = backend.websocket_url().to_string();    let state_db =        StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let request_id = mcp.send_remote_control_enable_request().await?;    assert_eq!(        timeout(DEFAULT_TIMEOUT, backend.wait_for_enroll_request()).await??,        "POST /backend-api/wham/remote/control/server/enroll HTTP/1.1"    );    backend.complete_enrollment()?;    wait_for_response(&mut mcp, request_id).await?;    assert_eq!(        remote_control_preference(&state_db, &websocket_url).await?,        Some(true)    );    let request_id = mcp.send_remote_control_ephemeral_disable_request().await?;    wait_for_response(&mut mcp, request_id).await?;    assert_eq!(        remote_control_preference(&state_db, &websocket_url).await?,        Some(true)    );    let request_id = mcp.send_remote_control_disable_request().await?;    wait_for_response(&mut mcp, request_id).await?;    assert_eq!(        remote_control_preference(&state_db, &websocket_url).await?,        Some(false)    );    let request_id = mcp.send_remote_control_enable_request().await?;    wait_for_response(&mut mcp, request_id).await?;    assert_eq!(        remote_control_preference(&state_db, &websocket_url).await?,        Some(true)    );    let request_id = mcp.send_remote_control_disable_request().await?;    wait_for_response(&mut mcp, request_id).await?;    assert_eq!(        remote_control_preference(&state_db, &websocket_url).await?,        Some(false)    );    let request_id = mcp.send_remote_control_ephemeral_enable_request().await?;    wait_for_response(&mut mcp, request_id).await?;    assert_eq!(        remote_control_preference(&state_db, &websocket_url).await?,        Some(false)    );    Ok(())}#[tokio::test]async fn remote_control_status_read_returns_connecting_status_after_enable() -> Result<()> {    let codex_home = TempDir::new()?;    let mut backend = BlockingRemoteControlBackend::start(codex_home.path()).await?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let request_id = mcp.send_remote_control_enable_request().await?;    let enroll_request = timeout(DEFAULT_TIMEOUT, backend.wait_for_enroll_request()).await??;    assert_eq!(        enroll_request,        "POST /backend-api/wham/remote/control/server/enroll HTTP/1.1"    );    backend.complete_enrollment()?;    let _: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    let request_id = mcp.send_remote_control_status_read_request().await?;    let response: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    let received: RemoteControlStatusReadResponse = to_response(response)?;    assert_eq!(received.status, RemoteControlConnectionStatus::Connecting);    assert!(!received.server_name.is_empty());    assert_eq!(received.environment_id.as_deref(), Some("environment-id"));    assert!(!received.installation_id.is_empty());    Ok(())}#[tokio::test]async fn remote_control_pairing_start_returns_pairing_artifacts() -> Result<()> {    let codex_home = TempDir::new()?;    let mut backend = PairingRemoteControlBackend::start(codex_home.path()).await?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let request_id = mcp.send_remote_control_enable_request().await?;    let _: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    assert_eq!(        timeout(DEFAULT_TIMEOUT, backend.wait_for_enroll_request()).await??,        "POST /backend-api/wham/remote/control/server/enroll HTTP/1.1"    );    timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_matching_notification(            "remoteControl/status/changed enrolled",            |notification| {                notification.method == "remoteControl/status/changed"                    && notification                        .params                        .as_ref()                        .and_then(|params| params.get("environmentId"))                        .and_then(serde_json::Value::as_str)                        == Some("environment-id")            },        ),    )    .await??;    let request_id = mcp        .send_remote_control_pairing_start_request(RemoteControlPairingStartParams {            manual_code: true,        })        .await?;    let response: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    assert_eq!(response.result.get("serverId"), None);    let received: RemoteControlPairingStartResponse = to_response(response)?;    assert_eq!(        received,        RemoteControlPairingStartResponse {            pairing_code: "pairing-code".to_string(),            manual_pairing_code: Some("ABCD-EFGH".to_string()),            environment_id: "environment-id".to_string(),            expires_at: 33_336_362_096,        }    );    let request_id = mcp        .send_remote_control_pairing_status_request(RemoteControlPairingStatusParams {            pairing_code: Some("pairing-code".to_string()),            manual_pairing_code: None,        })        .await?;    let response: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    assert_eq!(response.result.get("serverId"), None);    let received: RemoteControlPairingStatusResponse = to_response(response)?;    assert_eq!(        received,        RemoteControlPairingStatusResponse { claimed: true }    );    let request_id = mcp        .send_remote_control_pairing_status_request(RemoteControlPairingStatusParams {            pairing_code: None,            manual_pairing_code: Some("ABCD-EFGH".to_string()),        })        .await?;    let response: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    assert_eq!(response.result.get("serverId"), None);    let received: RemoteControlPairingStatusResponse = to_response(response)?;    assert_eq!(        received,        RemoteControlPairingStatusResponse { claimed: true }    );    Ok(())}#[tokio::test]async fn pairing_start_works_after_ephemeral_enable() -> Result<()> {    let codex_home = TempDir::new()?;    let mut backend = PairingRemoteControlBackend::start(codex_home.path()).await?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let request_id = mcp.send_remote_control_ephemeral_enable_request().await?;    wait_for_response(&mut mcp, request_id).await?;    let request_id = mcp        .send_remote_control_pairing_start_request(RemoteControlPairingStartParams {            manual_code: true,        })        .await?;    let response: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    assert_eq!(        timeout(DEFAULT_TIMEOUT, backend.wait_for_enroll_request()).await??,        "POST /backend-api/wham/remote/control/server/enroll HTTP/1.1"    );    assert_eq!(response.result.get("serverId"), None);    let received: RemoteControlPairingStartResponse = to_response(response)?;    assert_eq!(        received,        RemoteControlPairingStartResponse {            pairing_code: "pairing-code".to_string(),            manual_pairing_code: Some("ABCD-EFGH".to_string()),            environment_id: "environment-id".to_string(),            expires_at: 33_336_362_096,        }    );    Ok(())}#[tokio::test]async fn remote_control_client_management_works_while_disabled() -> Result<()> {    let codex_home = TempDir::new()?;    let mut backend = ClientManagementRemoteControlBackend::start(codex_home.path()).await?;    let mut mcp = TestAppServer::new(codex_home.path()).await?;    timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;    let request_id = mcp        .send_remote_control_clients_list_request(RemoteControlClientsListParams {            environment_id: "environment-id".to_string(),            cursor: Some("cursor-id".to_string()),            limit: Some(10),            order: Some(RemoteControlClientsListOrder::Desc),        })        .await?;    let response: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    let received: RemoteControlClientsListResponse = to_response(response)?;    assert_eq!(        received,        RemoteControlClientsListResponse {            data: vec![RemoteControlClient {                client_id: "client-id".to_string(),                display_name: Some("Anton Phone".to_string()),                device_type: Some("phone".to_string()),                platform: Some("ios".to_string()),                os_version: Some("19.0".to_string()),                device_model: Some("iPhone".to_string()),                app_version: Some("1.2.3".to_string()),                last_seen_at: Some(1_772_694_000),            }],            next_cursor: Some("next-cursor".to_string()),        }    );    let request_id = mcp        .send_remote_control_clients_revoke_request(RemoteControlClientsRevokeParams {            environment_id: "environment-id".to_string(),            client_id: "client-id".to_string(),        })        .await?;    let response: JSONRPCResponse = timeout(        DEFAULT_TIMEOUT,        mcp.read_stream_until_response_message(RequestId::Integer(request_id)),    )    .await??;    let received: RemoteControlClientsRevokeResponse = to_response(response)?;    assert_eq!(received, RemoteControlClientsRevokeResponse {});    assert_eq!(        timeout(DEFAULT_TIMEOUT, backend.wait_for_requests()).await??,        vec![            "GET /backend-api/wham/remote/control/environments/environment-id/clients?cursor=cursor-id&limit=10&order=desc HTTP/1.1".to_string(),            "DELETE /backend-api/wham/remote/control/environments/environment-id/clients/client-id HTTP/1.1".to_string(),        ]    );    Ok(())}struct BlockingRemoteControlBackend {    enroll_request_rx: Option<oneshot::Receiver<Result<String>>>,    enroll_response_tx: Option<oneshot::Sender<()>>,    websocket_url: String,    server_task: JoinHandle<()>,}struct ClientManagementRemoteControlBackend {    requests_rx: Option<oneshot::Receiver<Result<Vec<String>>>>,    server_task: JoinHandle<()>,}impl ClientManagementRemoteControlBackend {    async fn start(codex_home: &std::path::Path) -> Result<Self> {        let listener = configured_remote_control_listener(codex_home).await?;        let (requests_tx, requests_rx) = oneshot::channel();        let server_task = tokio::spawn(async move {            let result = async {                let list_request = read_http_request(&listener).await?;                let list_request_line = list_request.request_line;                respond_with_json(                    list_request.reader.into_inner(),                    serde_json::json!({                        "items": [{                            "client_id": "client-id",                            "account_user_id": "user-id",                            "enrollment_status": "enrolled_device_key",                            "display_name": "Anton Phone",                            "device_type": "phone",                            "platform": "ios",                            "os_version": "19.0",                            "device_model": "iPhone",                            "app_version": "1.2.3",                            "last_seen_at": "2026-03-05T07:00:00Z",                            "last_seen_city": "San Francisco",                        }],                        "cursor": "next-cursor",                    }),                )                .await?;                let revoke_request = read_http_request(&listener).await?;                let revoke_request_line = revoke_request.request_line;                respond_with_status(revoke_request.reader.into_inner(), "204 No Content", "")                    .await?;                Ok(vec![list_request_line, revoke_request_line])            }            .await;            let _ = requests_tx.send(result);        });        Ok(Self {            requests_rx: Some(requests_rx),            server_task,        })    }    async fn wait_for_requests(&mut self) -> Result<Vec<String>> {        self.requests_rx            .take()            .context("requests should only be awaited once")?            .await?    }}impl BlockingRemoteControlBackend {    async fn start(codex_home: &std::path::Path) -> Result<Self> {        let listener = configured_remote_control_listener(codex_home).await?;        let websocket_url = format!(            "ws://{}/backend-api/wham/remote/control/server",            listener.local_addr()?        );        let (enroll_request_tx, enroll_request_rx) = oneshot::channel();        let (enroll_response_tx, enroll_response_rx) = oneshot::channel();        let server_task = tokio::spawn(async move {            match read_enroll_request(&listener).await {                Ok((request_line, reader)) => {                    let _ = enroll_request_tx.send(Ok(request_line));                    if enroll_response_rx.await.is_err() {                        return;                    }                    if respond_with_json(                        reader.into_inner(),                        serde_json::json!({                            "server_id": "server-id",                            "environment_id": "environment-id",                            "remote_control_token": "remote-control-token",                            "expires_at": "3026-05-22T12:34:56Z",                        }),                    )                    .await                    .is_err()                    {                        return;                    }                    let Ok(_websocket) = listener.accept().await else {                        return;                    };                    std::future::pending::<()>().await;                }                Err(err) => {                    let _ = enroll_request_tx.send(Err(err));                }            }        });        Ok(Self {            enroll_request_rx: Some(enroll_request_rx),            enroll_response_tx: Some(enroll_response_tx),            websocket_url,            server_task,        })    }    async fn wait_for_enroll_request(&mut self) -> Result<String> {        let rx = self            .enroll_request_rx            .take()            .context("enroll request should only be awaited once")?;        rx.await?    }    fn complete_enrollment(&mut self) -> Result<()> {        self.enroll_response_tx            .take()            .context("enrollment should only complete once")?            .send(())            .map_err(|()| anyhow::anyhow!("enrollment response receiver dropped"))    }    fn websocket_url(&self) -> &str {        &self.websocket_url    }}struct PairingRemoteControlBackend {    enroll_request_rx: Option<oneshot::Receiver<Result<String>>>,    server_task: JoinHandle<()>,}impl PairingRemoteControlBackend {    async fn start(codex_home: &std::path::Path) -> Result<Self> {        let listener = configured_remote_control_listener(codex_home).await?;        let (enroll_request_tx, enroll_request_rx) = oneshot::channel();        let server_task = tokio::spawn(async move {            let mut enroll_request_tx = Some(enroll_request_tx);            let result = async {                let enroll_request = read_http_request(&listener).await?;                if let Some(enroll_request_tx) = enroll_request_tx.take() {                    let _ = enroll_request_tx.send(Ok(enroll_request.request_line.clone()));                }                respond_with_json(                    enroll_request.reader.into_inner(),                    serde_json::json!({                        "server_id": "server-id",                        "environment_id": "environment-id",                        "remote_control_token": "remote-control-token",                        "expires_at": "3026-05-22T12:34:56Z",                    }),                )                .await?;                let request_after_enroll = read_http_request(&listener).await?;                let pair_http_request = if request_after_enroll.request_line.starts_with("GET ") {                    read_http_request(&listener).await?                } else {                    request_after_enroll                };                respond_with_json(                    pair_http_request.reader.into_inner(),                    serde_json::json!({                        "pairing_code": "pairing-code",                        "manual_pairing_code": "ABCD-EFGH",                        "server_id": "server-id",                        "environment_id": "environment-id",                        "expires_at": "3026-05-22T12:34:56Z",                    }),                )                .await?;                for expected_body in [                    serde_json::json!({ "pairing_code": "pairing-code" }),                    serde_json::json!({ "manual_pairing_code": "ABCD-EFGH" }),                ] {                    let status_http_request = read_http_request(&listener).await?;                    assert_eq!(                        status_http_request.request_line,                        "POST /backend-api/wham/remote/control/server/pair/status HTTP/1.1"                    );                    assert_eq!(                        serde_json::from_str::<serde_json::Value>(&status_http_request.body)?,                        expected_body                    );                    respond_with_json(                        status_http_request.reader.into_inner(),                        serde_json::json!({ "claimed": true }),                    )                    .await?;                }                std::future::pending::<()>().await;                Ok::<(), anyhow::Error>(())            }            .await;            if let Err(err) = result {                let err = err.to_string();                if let Some(enroll_request_tx) = enroll_request_tx {                    let _ = enroll_request_tx.send(Err(anyhow::anyhow!(err)));                }            }        });        Ok(Self {            enroll_request_rx: Some(enroll_request_rx),            server_task,        })    }    async fn wait_for_enroll_request(&mut self) -> Result<String> {        self.enroll_request_rx            .take()            .context("enroll request should only be awaited once")?            .await?    }}impl Drop for PairingRemoteControlBackend {    fn drop(&mut self) {        self.server_task.abort();    }}impl Drop for BlockingRemoteControlBackend {    fn drop(&mut self) {        self.server_task.abort();    }}impl Drop for ClientManagementRemoteControlBackend {    fn drop(&mut self) {        self.server_task.abort();    }}struct HttpRequest {    request_line: String,    body: String,    reader: BufReader<TcpStream>,}async fn configured_remote_control_listener(codex_home: &std::path::Path) -> Result<TcpListener> {    let listener = TcpListener::bind("127.0.0.1:0").await?;    let remote_control_url = format!("http://{}/backend-api/", listener.local_addr()?);    write_mock_responses_config_toml_with_chatgpt_base_url(        codex_home,        &remote_control_url,        &remote_control_url,    )?;    write_chatgpt_auth(        codex_home,        ChatGptAuthFixture::new("chatgpt-token")            .account_id("account_id")            .chatgpt_account_id("account_id"),        AuthCredentialsStoreMode::File,    )?;    Ok(listener)}async fn read_enroll_request(listener: &TcpListener) -> Result<(String, BufReader<TcpStream>)> {    let request = read_http_request(listener).await?;    Ok((request.request_line, request.reader))}async fn read_http_request(listener: &TcpListener) -> Result<HttpRequest> {    let (stream, _) = listener.accept().await?;    let mut reader = BufReader::new(stream);    let mut request_line = String::new();    reader.read_line(&mut request_line).await?;    let mut content_length = 0;    loop {        let mut line = String::new();        reader.read_line(&mut line).await?;        if line == "\r\n" {            break;        }        if let Some(value) = line            .trim_end()            .strip_prefix("content-length:")            .or_else(|| line.trim_end().strip_prefix("Content-Length:"))        {            content_length = value.trim().parse::<usize>()?;        }    }    let mut body = vec![0; content_length];    if content_length > 0 {        reader.read_exact(&mut body).await?;    }    Ok(HttpRequest {        request_line: request_line.trim_end().to_string(),        body: String::from_utf8(body)?,        reader,    })}async fn respond_with_json(stream: TcpStream, body: serde_json::Value) -> Result<()> {    let body = body.to_string();    let mut stream = stream;    stream        .write_all(            format!(                "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}",                body.len()            )            .as_bytes(),        )        .await?;    Ok(())}async fn respond_with_status(mut stream: TcpStream, status: &str, body: &str) -> Result<()> {    stream        .write_all(            format!(                "HTTP/1.1 {status}\r\ncontent-type: text/plain\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}",                body.len()            )            .as_bytes(),        )        .await?;    Ok(())}