use anyhow::Result; use app_test_support::McpProcess; use app_test_support::to_response; use codex_app_server_protocol::JSONRPCResponse; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ThreadArchiveParams; use codex_app_server_protocol::ThreadArchiveResponse; use codex_app_server_protocol::ThreadStartParams; use codex_app_server_protocol::ThreadStartResponse; use codex_app_server_protocol::ThreadUnarchiveParams; use codex_app_server_protocol::ThreadUnarchiveResponse; use codex_core::find_archived_thread_path_by_id_str; use codex_core::find_thread_path_by_id_str; use std::path::Path; use tempfile::TempDir; use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); #[tokio::test] async fn thread_unarchive_moves_rollout_back_into_sessions_directory() -> Result<()> { let codex_home = TempDir::new()?; create_config_toml(codex_home.path())?; let mut mcp = McpProcess::new(codex_home.path()).await?; timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let start_id = mcp .send_thread_start_request(ThreadStartParams { model: Some("mock-model".to_string()), ..Default::default() }) .await?; let start_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(start_id)), ) .await??; let ThreadStartResponse { thread, .. } = to_response::(start_resp)?; let rollout_path = find_thread_path_by_id_str(codex_home.path(), &thread.id) .await? .expect("expected rollout path for thread id to exist"); let archive_id = mcp .send_thread_archive_request(ThreadArchiveParams { thread_id: thread.id.clone(), }) .await?; let archive_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(archive_id)), ) .await??; let _: ThreadArchiveResponse = to_response::(archive_resp)?; let archived_path = find_archived_thread_path_by_id_str(codex_home.path(), &thread.id) .await? .expect("expected archived rollout path for thread id to exist"); let archived_path_display = archived_path.display(); assert!( archived_path.exists(), "expected {archived_path_display} to exist" ); let unarchive_id = mcp .send_thread_unarchive_request(ThreadUnarchiveParams { thread_id: thread.id.clone(), }) .await?; let unarchive_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(unarchive_id)), ) .await??; let _: ThreadUnarchiveResponse = to_response::(unarchive_resp)?; let rollout_path_display = rollout_path.display(); assert!( rollout_path.exists(), "expected rollout path {rollout_path_display} to be restored" ); assert!( !archived_path.exists(), "expected archived rollout path {archived_path_display} to be moved" ); Ok(()) } fn create_config_toml(codex_home: &Path) -> std::io::Result<()> { let config_toml = codex_home.join("config.toml"); std::fs::write(config_toml, config_contents()) } fn config_contents() -> &'static str { r#"model = "mock-model" approval_policy = "never" sandbox_mode = "read-only" "# }