Codex Handbook
tui/src/external_agent_config_migration_flow.rs 124 lines
use crate::app_server_session::AppServerSession;use crate::app_server_session::EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE;use crate::external_agent_config_migration::ExternalAgentConfigMigrationOutcome;use crate::external_agent_config_migration::run_external_agent_config_migration_prompt;use crate::legacy_core::config::Config;use crate::tui;use codex_app_server_protocol::ExternalAgentConfigDetectParams;pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_FINISHED_MESSAGE: &str =    "Claude Code import finished. Run /import again to check for additional items.";pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_NO_ITEMS_MESSAGE: &str =    "No Claude Code setup was found to import.";pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_REMOTE_UNAVAILABLE_MESSAGE: &str = "Import from Claude Code is unavailable in remote sessions. Start Codex locally and run /import.";pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_DAEMON_UNAVAILABLE_MESSAGE: &str = "Import from Claude Code is unavailable while Codex is connected to the local app-server daemon. Stop the daemon, restart Codex, and run /import.";pub(crate) enum ExternalAgentConfigMigrationFlowOutcome {    Started(String),    NoItems,    Cancelled,}fn external_agent_config_migration_success_message(remaining_item_count: usize) -> String {    let message = "Claude Code import started. You can keep working while it finishes. Imported setup will apply to new chats.";    match remaining_items_handoff(remaining_item_count) {        Some(remaining_items_handoff) => format!("{message} {remaining_items_handoff}"),        None => message.to_string(),    }}fn remaining_items_handoff(remaining_item_count: usize) -> Option<String> {    match remaining_item_count {        0 => None,        1 => Some(            "1 additional item remains. After it finishes, run /import again to review it."                .to_string(),        ),        _ => Some(format!(            "{remaining_item_count} additional items remain. After it finishes, run /import again to review them."        )),    }}pub(crate) async fn handle_external_agent_config_migration_prompt(    tui: &mut tui::Tui,    app_server: &mut AppServerSession,    config: &Config,) -> Result<ExternalAgentConfigMigrationFlowOutcome, String> {    if app_server.uses_remote_workspace() {        return Err(EXTERNAL_AGENT_CONFIG_MIGRATION_REMOTE_UNAVAILABLE_MESSAGE.to_string());    }    if !app_server.uses_embedded_app_server() {        return Err(EXTERNAL_AGENT_CONFIG_MIGRATION_DAEMON_UNAVAILABLE_MESSAGE.to_string());    }    if app_server.external_agent_config_import_in_progress() {        return Err(EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE.to_string());    }    let cwd = config.cwd.to_path_buf();    let detected_items = match app_server        .external_agent_config_detect(ExternalAgentConfigDetectParams {            include_home: true,            cwds: Some(vec![cwd.clone()]),        })        .await    {        Ok(response) => response.items,        Err(err) => {            tracing::warn!(                error = %err,                cwd = %cwd.display(),                "failed to detect external agent config migrations"            );            return Err(format!("Could not check for Claude Code setup: {err}"));        }    };    if detected_items.is_empty() {        return Ok(ExternalAgentConfigMigrationFlowOutcome::NoItems);    }    let mut selected_items = detected_items.clone();    let mut error: Option<String> = None;    loop {        match run_external_agent_config_migration_prompt(            tui,            &detected_items,            &selected_items,            error.as_deref(),        )        .await        {            ExternalAgentConfigMigrationOutcome::Proceed(items) => {                selected_items = items.clone();                match app_server.external_agent_config_import(items).await {                    Ok(()) => {                        let remaining_item_count =                            detected_items.len().saturating_sub(selected_items.len());                        let success_message =                            external_agent_config_migration_success_message(remaining_item_count);                        return Ok(ExternalAgentConfigMigrationFlowOutcome::Started(                            success_message,                        ));                    }                    Err(err) => {                        tracing::warn!(                            error = %err,                            cwd = %cwd.display(),                            "failed to import external agent config migration items"                        );                        error = Some(format!("Import failed: {err}"));                    }                }            }            ExternalAgentConfigMigrationOutcome::Skip => {                return Ok(ExternalAgentConfigMigrationFlowOutcome::Cancelled);            }        }    }}#[cfg(test)]#[path = "external_agent_config_migration_flow_tests.rs"]mod tests;