magi-code 0.77.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;

pub(super) fn test_area() -> ratatui::layout::Rect {
    ratatui::layout::Rect::new(0, 0, 100, 30)
}

// Poll the owned completion result, not status text or a fixed sleep duration.
pub(super) fn reap_owned_worker(
    app: &mut MissionControlApp,
    ui_state: &mut state::MissionControlState,
    reap: impl Fn(&mut MissionControlApp, &mut state::MissionControlState) -> bool,
) {
    let deadline = Instant::now() + Duration::from_secs(5);
    while !reap(app, ui_state) {
        assert!(Instant::now() < deadline, "owned worker did not finish");
        thread::sleep(Duration::from_millis(1));
    }
}

pub(super) fn reap_provider_worker(
    app: &mut MissionControlApp,
    ui_state: &mut state::MissionControlState,
    receiver: &Receiver<TuiEvent>,
) {
    let deadline = Instant::now() + Duration::from_secs(5);
    loop {
        while let Ok(event) = receiver.try_recv() {
            let mut result = DrainResult::default();
            apply_control_event_to_state(ui_state, event, &mut result);
            for (provider_id, provider_result) in result.custom_provider_finished {
                if provider_result.is_ok() {
                    app.refresh_runtime_after_custom_provider(&provider_id, ui_state);
                }
            }
        }
        if app.finish_worker_if_ready(ui_state) {
            return;
        }
        assert!(Instant::now() < deadline, "provider worker did not finish");
        thread::yield_now();
    }
}

pub(super) fn reap_model_selection_worker(
    app: &mut MissionControlApp,
    ui_state: &mut state::MissionControlState,
    receiver: &Receiver<TuiEvent>,
) {
    let deadline = Instant::now() + Duration::from_secs(5);
    loop {
        // Reap before draining: a finished worker may have queued its final
        // event since the previous poll. Drain that event before returning.
        let worker_reaped = app.reap_model_selection_workers(ui_state);
        let mut drain = DrainResult::default();
        while let Ok(event) = receiver.try_recv() {
            apply_control_event_to_state(ui_state, event, &mut drain);
        }
        let completion_seen = !drain.model_selection_finished.is_empty();
        app.handle_model_selection_drain(ui_state, &drain);
        if worker_reaped {
            return;
        }
        if completion_seen {
            // Cleanup also reports command errors, not just thread failures.
            // Match the delivered errors rather than rejecting expected input
            // validation failures when the event arrives before thread exit.
            let expected_errors: Vec<_> = drain
                .model_selection_finished
                .iter()
                .filter_map(|finished| {
                    finished.result.as_ref().err().map(|error| {
                        format!(
                            "model selection worker {} failed: {}",
                            finished.request_id, error
                        )
                    })
                })
                .collect();
            let errors = app.join_model_selection_workers_for_cleanup();
            assert_eq!(errors, expected_errors, "model selection worker cleanup");
            return;
        }
        assert!(
            Instant::now() < deadline,
            "model selection worker did not finish"
        );
        thread::yield_now();
    }
}

pub(super) fn reap_rewind_worker(
    app: &mut MissionControlApp,
    ui_state: &mut state::MissionControlState,
    receiver: &Receiver<TuiEvent>,
) {
    let deadline = Instant::now() + Duration::from_secs(5);
    loop {
        let mut drain = DrainResult::default();
        while let Ok(event) = receiver.try_recv() {
            apply_control_event_to_state(ui_state, event, &mut drain);
        }
        let completion_seen = !drain.rewind_finished.is_empty();
        app.handle_rewind_drain(ui_state, &drain);
        let worker_reaped = app.reap_rewind_workers(ui_state);
        if completion_seen {
            let errors = app.join_rewind_workers_for_cleanup();
            assert!(errors.is_empty(), "rewind worker cleanup: {errors:?}");
            return;
        }
        if worker_reaped {
            return;
        }
        assert!(Instant::now() < deadline, "rewind worker did not finish");
        thread::yield_now();
    }
}

pub(super) fn reap_export_worker(
    app: &mut MissionControlApp,
    ui_state: &mut state::MissionControlState,
    receiver: &Receiver<TuiEvent>,
) {
    let deadline = Instant::now() + Duration::from_secs(5);
    loop {
        let mut drain = DrainResult::default();
        while let Ok(event) = receiver.try_recv() {
            apply_control_event_to_state(ui_state, event, &mut drain);
        }
        let completion_seen = !drain.export_finished.is_empty();
        app.handle_export_drain(ui_state, &drain);
        let worker_reaped = app.reap_export_workers(ui_state);
        if completion_seen {
            let errors = app.join_export_workers_for_cleanup();
            assert!(errors.is_empty(), "export worker cleanup: {errors:?}");
            return;
        }
        if worker_reaped {
            return;
        }
        assert!(Instant::now() < deadline, "export worker did not finish");
        thread::yield_now();
    }
}

pub(super) fn herdr_statuses(
    lines: &std::sync::Arc<std::sync::Mutex<Vec<String>>>,
) -> Vec<(String, String)> {
    lines
        .lock()
        .unwrap()
        .iter()
        .map(|line| {
            let value: serde_json::Value = serde_json::from_str(line).unwrap();
            (
                value["params"]["state"].as_str().unwrap().to_string(),
                value["params"]["message"].as_str().unwrap().to_string(),
            )
        })
        .collect()
}

pub(super) fn test_paths(root: &std::path::Path) -> crate::config::McPaths {
    let mut paths =
        crate::config::McPaths::from_root_and_project_dir(root.join("mc"), root.join("project"));
    paths.user_agents = root.join("mc/agents");
    paths
}

pub(super) fn start_sse_server(summary: &'static str) -> String {
    let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    let addr = listener.local_addr().unwrap();
    std::thread::spawn(move || {
        let (mut stream, _) = listener.accept().unwrap();
        let _ = stream.set_read_timeout(Some(std::time::Duration::from_secs(2)));
        let mut buffer = [0_u8; 4096];
        let _ = std::io::Read::read(&mut stream, &mut buffer);
        let payload = format!("data: {{\"delta\":\"{summary}\"}}\n\ndata: [DONE]\n\n");
        let response = format!(
            "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
            payload.len(),
            payload
        );
        let _ = std::io::Write::write_all(&mut stream, response.as_bytes());
    });
    format!("http://{addr}/v1")
}

pub(super) fn write_compaction_override_settings(
    paths: &crate::config::McPaths,
    provider: &str,
    model: &str,
    base_url: &str,
) {
    let settings = crate::config::Settings {
        compaction: crate::config::CompactionSettings {
            provider: Some(provider.to_string()),
            model: Some(model.to_string()),
            ..crate::config::CompactionSettings::default()
        },
        custom_providers: std::collections::BTreeMap::from([(
            provider.to_string(),
            crate::config::make_custom_provider_config("Local", base_url, "").unwrap(),
        )]),
        ..crate::config::Settings::default()
    };
    crate::config::write_settings(paths, &settings).unwrap();
}

pub(super) fn test_app(temp: &tempfile::TempDir, sender: Sender<TuiEvent>) -> MissionControlApp {
    test_app_with_active_session(temp, sender, None)
}

pub(super) fn test_app_with_active_session(
    temp: &tempfile::TempDir,
    sender: Sender<TuiEvent>,
    active_session: Option<Session>,
) -> MissionControlApp {
    let mut app = MissionControlApp::new(
        TuiSessionConfig {
            config: EffectiveConfig {
                provider: Some("openai".to_string()),
                model: Some("test-model".to_string()),
                no_color: false,
                file_autocomplete_respects_gitignore: true,
                custom_providers: std::collections::BTreeMap::new(),
                thinking_level: crate::thinking::ThinkingLevel::Default,
                auth: None,
                paths: test_paths(temp.path()),
            },
            settings: crate::config::Settings::default(),
            appearance: crate::appearance::RuntimeAppearance::default(),
            theme_cli_override: false,
            initial_prompt: None,
            instructions: Vec::new(),
            discovered_skills: SkillDiscovery::default(),
            skills: SkillDiscovery::default(),
            commands: CommandRegistry::mvp(),
            manager: SessionManager::new(temp.path().join("sessions")),
            active_session,
            cwd: temp.path().to_path_buf(),
            herdr_reporter: None,
        },
        sender,
    );
    app.startup_readiness = StartupReadiness::Ready;
    app.subagent_profile_discovery =
        crate::subagents::profiles::discover_subagent_profiles(&app.config.paths.subagents);
    app
}

pub(super) fn write_codex_auth(paths: &crate::config::McPaths) {
    let mut auth = crate::config::Auth::default();
    auth.providers.insert(
        crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
        crate::config::AuthProviderRecord::OAuth {
            access: "access".into(),
            refresh: Some("refresh".into()),
            expires: None,
            account_id: Some("acct".into()),
        },
    );
    crate::config::write_auth(paths, &auth).unwrap();
}

pub(super) fn ready_codex_app(
    temp: &tempfile::TempDir,
    sender: Sender<TuiEvent>,
) -> MissionControlApp {
    let mut app = test_app(temp, sender);
    app.config.provider = Some(crate::providers::OPENAI_CODEX_PROVIDER.to_string());
    app.config.auth = Some(crate::config::ProviderCredential::OAuth {
        access: "access".into(),
        account_id: Some("acct".into()),
    });
    app.state.config = Some(app.config.clone());
    app.state.auth_state = crate::config::AuthState::Ready {
        provider: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
        credential: crate::config::ProviderCredential::OAuth {
            access: "access".into(),
            account_id: Some("acct".into()),
        },
    };
    write_codex_auth(&app.config.paths);
    app.settings = crate::config::read_settings(&app.config.paths).unwrap();
    app
}

pub(super) fn ready_anthropic_app(
    temp: &tempfile::TempDir,
    sender: Sender<TuiEvent>,
) -> MissionControlApp {
    let mut app = test_app(temp, sender);
    let credential = crate::config::ProviderCredential::ApiKey {
        key: "stored-test-key".to_string(),
    };
    app.config.provider = Some(crate::providers::ANTHROPIC_PROVIDER.to_string());
    app.config.model = Some(crate::providers::DEFAULT_ANTHROPIC_MODEL.to_string());
    app.config.auth = Some(credential.clone());
    app.state.config = Some(app.config.clone());
    app.state.auth_state = crate::config::AuthState::Ready {
        provider: crate::providers::ANTHROPIC_PROVIDER.to_string(),
        credential,
    };
    app
}

pub(super) fn apply_fast_mode_completion(
    app: &mut MissionControlApp,
    ui_state: &mut state::MissionControlState,
    receiver: &Receiver<TuiEvent>,
) {
    let event = receiver
        .recv_timeout(Duration::from_secs(1))
        .expect("fast mode persistence completion");
    let mut result = DrainResult::default();
    apply_control_event_to_state(ui_state, event, &mut result);
    assert_eq!(result.fast_mode_persisted.len(), 1);
    assert!(app.handle_fast_mode_persistence_drain(ui_state, &result));
}