magi-code 0.64.0

Repository-aware CLI coding agent for terminal work
Documentation
use super::{AgentOutputSink, util::safe_error_message};
use crate::{
    output::OutputEvent,
    sessions::{
        Session, SessionEventKind, SessionReadDiagnostic, record_session_event,
        try_record_session_event,
    },
};
use serde_json::Value;
use std::path::Path;

pub(super) struct SessionPersistence<'a> {
    session: Option<&'a Session>,
    cwd: &'a Path,
    warning_emitted: bool,
    incomplete_marker_attempted: bool,
    terminal_status_recorded: bool,
}

impl<'a> SessionPersistence<'a> {
    pub(super) fn new(session: Option<&'a Session>, cwd: &'a Path) -> Self {
        Self {
            session,
            cwd,
            warning_emitted: false,
            incomplete_marker_attempted: false,
            terminal_status_recorded: false,
        }
    }

    pub(super) fn is_degraded(&self) -> bool {
        self.warning_emitted
    }

    pub(super) fn warn_once(
        &mut self,
        output_sink: &mut Option<&mut dyn AgentOutputSink>,
        error: &anyhow::Error,
    ) -> anyhow::Result<()> {
        if !self.incomplete_marker_attempted {
            self.incomplete_marker_attempted = true;
            let _ = try_record_session_event(
                self.session,
                self.cwd,
                SessionEventKind::TurnStatus,
                serde_json::json!({"status":"incomplete"}),
            );
        }
        if self.warning_emitted {
            return Ok(());
        }
        self.warning_emitted = true;
        if let Some(sink) = output_sink.as_deref_mut() {
            sink.output_event(OutputEvent::Diagnostic {
                level: "warning".to_string(),
                message: format!(
                    "session persistence failed; future resume may be incomplete: {}",
                    safe_error_message(error)
                ),
            })?;
        }
        Ok(())
    }

    pub(super) fn try_record(
        &mut self,
        kind: SessionEventKind,
        payload: Value,
        output_sink: &mut Option<&mut dyn AgentOutputSink>,
    ) -> anyhow::Result<()> {
        if let Err(error) = try_record_session_event(self.session, self.cwd, kind, payload) {
            self.warn_once(output_sink, &error)?;
        }
        Ok(())
    }

    pub(super) fn record_terminal_status(
        &mut self,
        status: &str,
        assistant_text: &str,
        output_sink: &mut Option<&mut dyn AgentOutputSink>,
    ) -> anyhow::Result<()> {
        if self.terminal_status_recorded {
            return Ok(());
        }
        self.terminal_status_recorded = true;
        let _ = self.try_record(
            SessionEventKind::TurnStatus,
            serde_json::json!({"status": status, "assistant_text": assistant_text}),
            output_sink,
        );
        Ok(())
    }

    pub(super) fn record_abort_recovery(
        &mut self,
        payload: Value,
        output_sink: &mut Option<&mut dyn AgentOutputSink>,
    ) -> anyhow::Result<()> {
        self.try_record(SessionEventKind::AbortRecovery, payload, output_sink)
    }

    pub(super) fn record_provider_stream_trace(
        &mut self,
        payload: Value,
        output_sink: &mut Option<&mut dyn AgentOutputSink>,
    ) -> anyhow::Result<()> {
        self.try_record(SessionEventKind::ProviderStreamTrace, payload, output_sink)
    }

    pub(super) fn record_required(
        &self,
        kind: SessionEventKind,
        payload: Value,
    ) -> anyhow::Result<()> {
        record_session_event(self.session, self.cwd, kind, payload).map(|_| ())
    }
}

pub(super) fn emit_replay_diagnostics(
    output_sink: &mut Option<&mut dyn AgentOutputSink>,
    diagnostics: &[SessionReadDiagnostic],
) -> anyhow::Result<()> {
    if diagnostics.is_empty() {
        return Ok(());
    }
    if let Some(sink) = output_sink.as_deref_mut() {
        sink.output_event(OutputEvent::Diagnostic {
            level: "warning".to_string(),
            message: format!(
                "session replay skipped {} unreadable JSONL line(s); continuing with valid session events only",
                diagnostics.len()
            ),
        })?;
    }
    Ok(())
}