magi-code 0.77.1

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

struct SessionMaintenanceResult {
    status: String,
    rows: Result<Vec<state::SessionPickerRow>, String>,
}

struct SessionMaintenanceWorker {
    generation: u64,
    pruning: bool,
    handle: super::completion_worker::CompletionWorker<SessionMaintenanceResult>,
}

#[derive(Default)]
pub(super) struct SessionMaintenanceRuntime {
    worker: Option<SessionMaintenanceWorker>,
}

impl SessionMaintenanceRuntime {
    pub(super) fn is_pending(&self) -> bool {
        self.worker.is_some()
    }
    pub(super) fn is_pruning(&self) -> bool {
        self.worker.as_ref().is_some_and(|worker| worker.pruning)
    }

    pub(super) fn cleanup(&mut self) -> Vec<String> {
        let Some(worker) = self.worker.take() else {
            return Vec::new();
        };
        let deadline = Instant::now() + WORKER_EXIT_JOIN_TIMEOUT;
        while !worker.handle.is_finished() && Instant::now() < deadline {
            thread::sleep(Duration::from_millis(5));
        }
        if !worker.handle.is_finished() {
            return vec!["session maintenance did not finish before shutdown".into()];
        }
        match worker.handle.join() {
            Ok(Some(result)) => result.rows.err().into_iter().collect(),
            Ok(None) => Vec::new(),
            Err(error) => vec![error],
        }
    }
}

impl MissionControlApp {
    pub(super) fn start_session_maintenance(
        &mut self,
        prune_arg: Option<String>,
        visible_rows: Option<usize>,
        ui: &mut state::MissionControlState,
    ) {
        if self.session_maintenance.is_pending() {
            ui.status = "session maintenance already in progress".into();
            return;
        }
        let pruning = prune_arg.is_some();
        if pruning
            && (self.pending_session_switch.is_some() || self.active_run || self.worker.is_some())
        {
            ui.status = "cannot prune sessions while a prompt or session switch is running".into();
            return;
        }
        let manager = self.state.session_manager.clone();
        let active = self.state.active_session_id().map(str::to_owned);
        let cwd = self.state.cwd.clone();
        let handle = super::completion_worker::CompletionWorker::spawn(
            "magi-session-maintenance",
            self.events.clone(),
            move || {
                let status = if let Some(arg) = prune_arg {
                    execute_prune_sessions_command(&manager, active.as_deref(), Some(&arg))
                        .map_err(|error| error.to_string())?
                } else {
                    "select session".into()
                };
                // Preserve the prune report even if the following listing fails.
                let rows = session_picker_rows(&manager, active.as_deref(), &cwd)
                    .map_err(|error| format!("failed to list sessions: {error}"));
                Ok(SessionMaintenanceResult { status, rows })
            },
        );
        match handle {
            Ok(handle) => {
                self.session_maintenance.worker = Some(SessionMaintenanceWorker {
                    generation: self.session_generation,
                    pruning,
                    handle,
                });
                if let Some(visible_rows) = visible_rows {
                    ui.open_session_picker(Vec::new(), visible_rows);
                }
                ui.status = if pruning {
                    "pruning sessions…"
                } else {
                    "loading sessions…"
                }
                .into();
            }
            Err(error) => ui.status = format!("failed to start session maintenance: {error}"),
        }
    }

    pub(in crate::tui) fn reap_session_maintenance(
        &mut self,
        ui: &mut state::MissionControlState,
    ) -> bool {
        let Some(worker) = self.session_maintenance.worker.as_mut() else {
            return false;
        };
        let result = worker.handle.take_result();
        let generation = worker.generation;
        let pruning = worker.pruning;
        if result.is_some() {
            // The operation is complete even if the thread is still exiting.
            worker.pruning = false;
        }
        if worker.handle.ready_to_reap()
            && let Some(worker) = self.session_maintenance.worker.take()
        {
            let _ = worker.handle.join();
        }
        let Some(result) = result else {
            return false;
        };
        if generation != self.session_generation {
            return false;
        }
        // Closing the picker must not reopen it when its listing finishes.
        if !pruning && ui.modals.session_picker.is_none() {
            return false;
        }
        match result {
            Ok(result) => {
                ui.status = result.status;
                match result.rows {
                    Ok(rows) => {
                        if let Some(picker) = &mut ui.modals.session_picker {
                            picker.selected = picker.selected.min(rows.len().saturating_sub(1));
                            picker.rows = rows;
                            ui.set_scroll_offset(&ui.scroll_views.session_list, 0);
                            ui.set_scroll_offset(&ui.scroll_views.session_preview, 0);
                            self.request_selected_session_preview(ui);
                        }
                    }
                    Err(error) => ui.status = format!("{}; {error}", ui.status),
                }
            }
            Err(error) => ui.status = error,
        }
        true
    }
}