rho-coding-agent 1.12.2

A lightweight agent harness inspired by Pi
Documentation
use ratatui::DefaultTerminal;

use super::{
    is_tool_entry, recovered_history_tail, session_picker, short_session_id,
    transcript_entries_from_messages, App, CommandInvocation, ComposerMode, Entry,
    InteractiveRuntime, Session, RECOVERED_HISTORY_LINE_LIMIT,
};

impl App {
    pub(super) async fn execute_resume_command(
        &mut self,
        invocation: CommandInvocation,
        terminal: &mut DefaultTerminal,
        agent: &mut InteractiveRuntime,
    ) -> anyhow::Result<()> {
        let session_id = invocation.args.trim();
        if !session_id.is_empty() {
            return self
                .submit_resume_selection(session_id, terminal, agent)
                .await;
        }

        self.open_resume_picker()
    }

    pub(super) fn open_resume_picker(&mut self) -> anyhow::Result<()> {
        match Session::list(&self.info.runtime.cwd) {
            Ok(sessions) if sessions.is_empty() => {
                self.insert_entry(&Entry::Notice(
                    "no saved sessions for this workspace".into(),
                ));
                self.status = "no sessions".into();
            }
            Ok(sessions) => {
                let picker = session_picker::session_picker(
                    sessions,
                    self.info.session.session_id.as_deref(),
                );
                if picker.items.is_empty() {
                    self.insert_entry(&Entry::Notice(
                        "no other saved sessions for this workspace".into(),
                    ));
                    self.status = "no sessions".into();
                    return Ok(());
                }
                self.composer = ComposerMode::Picker(picker);
                self.status = "select session".into();
            }
            Err(err) => {
                self.insert_entry(&Entry::Error(format!("could not list sessions: {err}")));
                self.status = "resume failed".into();
            }
        }
        Ok(())
    }

    pub(super) async fn submit_resume_selection(
        &mut self,
        session_id: &str,
        terminal: &mut DefaultTerminal,
        agent: &mut InteractiveRuntime,
    ) -> anyhow::Result<()> {
        match self.resume_session_by_id(session_id, terminal, agent).await {
            Ok(()) => {
                self.info
                    .services
                    .herdr
                    .report_session(self.info.session.session_id.as_deref())
                    .await;
                Ok(())
            }
            Err(err) => {
                self.composer = ComposerMode::Input;
                self.insert_entry(&Entry::Error(format!("could not resume session: {err}")));
                self.status = "resume failed".into();
                Ok(())
            }
        }
    }

    async fn resume_session_by_id(
        &mut self,
        session_id: &str,
        terminal: &mut DefaultTerminal,
        agent: &mut InteractiveRuntime,
    ) -> anyhow::Result<()> {
        let (session, histories) =
            Session::open_by_id_with_histories(&self.info.runtime.cwd, session_id)?;
        let (agent_id, agent_fingerprint) = agent.agent_identity();
        session.validate_agent_identity(agent_id, agent_fingerprint)?;
        let full_id = session.id().to_string();
        let short_id = short_session_id(&full_id);

        let display_history = histories.display;
        agent.resume(session, histories.model).await?;
        self.info.session.session_id = Some(full_id);
        self.info.session.recovered_messages = display_history.clone();
        self.composer = ComposerMode::Input;
        self.input.clear();
        self.paste_segments.clear();
        self.input_cursor = 0;
        self.command_palette_dismissed = false;
        self.clamp_command_selection();
        self.reset_streams();
        self.running = false;
        self.goal = None;
        self.reset_usage();
        self.current_context = None;
        let entries = transcript_entries_from_messages(&display_history, &self.info.runtime.cwd);
        let width = terminal.size()?.width as usize;
        let (_omitted, visible_entries) = recovered_history_tail(
            &entries,
            width,
            RECOVERED_HISTORY_LINE_LIMIT,
            self.info.runtime.max_tool_output_lines,
        );
        self.transcript = visible_entries;
        self.markdown_images.clear();
        self.mark_markdown_images_dirty_from(0);
        self.history_lines.invalidate_from(0);
        self.last_inserted_was_tool = self.transcript.last().is_some_and(is_tool_entry);
        self.scroll_history_to_bottom();
        self.clamp_history_scroll_for_terminal(terminal)?;
        self.insert_runtime_notices(agent);
        self.insert_entry(&Entry::Notice(format!("resumed session {short_id}")));
        self.status = format!("resumed {short_id}");
        Ok(())
    }
}