codewhale-tui 0.9.8

Terminal UI for open-source and open-weight coding models
Documentation
use super::CommandResult;
use crate::commands::traits::{CommandInfo, RegisterCommand};
use crate::localization::MessageId;
use crate::tui::app::App;
use crate::tui::session_picker::SessionPickerView;
use std::path::PathBuf;
pub(in crate::commands) const COMMAND_INFO: CommandInfo = CommandInfo {
    name: "resume",
    aliases: &["r"],
    usage: "/resume [session_id|path/to/export.json]",
    description_id: MessageId::CmdResumeDescription,
};
pub(in crate::commands) struct ResumeCmd;
impl RegisterCommand for ResumeCmd {
    fn info() -> &'static CommandInfo {
        &COMMAND_INFO
    }
    fn execute(app: &mut App, arg: Option<&str>) -> CommandResult {
        resume(app, arg)
    }
}
fn resume(app: &mut App, arg: Option<&str>) -> CommandResult {
    if app.session_transition_blocked() {
        return CommandResult::error(
            "Cannot resume while runtime work is active. Wait for the turn to finish, or cancel it first.",
        );
    }
    let Some(raw) = arg.map(str::trim).filter(|s| !s.is_empty()) else {
        app.view_stack
            .push(SessionPickerView::new(&app.workspace, app.ui_locale));
        return CommandResult::ok();
    };
    let path = PathBuf::from(raw);
    if path.is_file() || raw.ends_with(".json") && std::path::Path::new(raw).exists() {
        return import_foreign(app, &path);
    }
    let ws_path = app.workspace.join(raw);
    if ws_path.is_file() {
        return import_foreign(app, &ws_path);
    }
    let manager = match crate::session_manager::SessionManager::default_location() {
        Ok(m) => m,
        Err(e) => return CommandResult::error(format!("could not open sessions directory: {e}")),
    };
    let session = manager
        .load_session(raw)
        .or_else(|_| manager.load_session_by_prefix(raw));
    match session {
        Ok(sess) => {
            let path = manager
                .sessions_dir()
                .join(format!("{}.json", sess.metadata.id));
            if path.exists() {
                return CommandResult::action(crate::tui::app::AppAction::LoadSession(path));
            }
            CommandResult::message(format!(
                "Resuming session {} ({})",
                crate::session_manager::truncate_id(&sess.metadata.id),
                sess.metadata.title
            ))
        }
        Err(e) => {
            if let Ok(container) = crate::session_tree::SessionImportContainer::from_json(raw) {
                return import_container(app, container);
            }
            CommandResult::error(format!(
                "Cannot resume '{raw}': {e}\nUse `/resume` without args to pick, or pass a session id, or a path to an exported session JSON."
            ))
        }
    }
}
fn import_foreign(app: &mut App, path: &PathBuf) -> CommandResult {
    let content = match std::fs::read_to_string(path) {
        Ok(c) => c,
        Err(e) => {
            return CommandResult::error(format!(
                "failed to read import file {}: {e}",
                path.display()
            ));
        }
    };
    if let Ok(container) = crate::session_tree::SessionImportContainer::from_json(&content) {
        return import_container(app, container);
    }
    if let Ok(foreign) = serde_json::from_str::<crate::session_manager::SavedSession>(&content) {
        let container = foreign.export_container("foreign");
        return import_container(app, container);
    }
    CommandResult::error(format!(
        "File {} is not a recognized session export",
        path.display()
    ))
}
fn import_container(
    app: &mut App,
    container: crate::session_tree::SessionImportContainer,
) -> CommandResult {
    let manager = match crate::session_manager::SessionManager::default_location() {
        Ok(m) => m,
        Err(e) => return CommandResult::error(format!("could not open sessions directory: {e}")),
    };
    let model = app.model.clone();
    let workspace = app.workspace.clone();
    let imported =
        match crate::session_manager::SavedSession::import_foreign(container, workspace, model) {
            Ok(s) => s,
            Err(e) => return CommandResult::error(format!("foreign import failed: {e}")),
        };
    let new_id = imported.metadata.id.clone();
    if let Err(e) = manager.save_session(&imported) {
        return CommandResult::error(format!("imported session could not be saved: {e}"));
    }
    app.current_session_id = Some(new_id.clone());
    app.current_session_metadata = Some(imported.metadata.clone());
    app.api_messages = imported.messages.clone();
    app.view_stack.push(SessionPickerView::new_selecting(
        &app.workspace,
        app.ui_locale,
        &new_id,
    ));
    CommandResult::message(format!(
        "Imported foreign session as {} ({} entries, leaf {})",
        crate::session_manager::truncate_id(&new_id),
        imported
            .journal
            .as_ref()
            .map(|j| j.entries.len())
            .unwrap_or(0),
        imported.leaf_id.as_deref().unwrap_or("(none)")
    ))
}