magi-code 0.98.1

Repository-aware CLI coding agent for terminal work
Documentation
use crate::sessions::SessionEventKind;
use serde_json::{Value, json};
use std::collections::{BTreeMap, BTreeSet};

/// Host-owned evidence, shared by live execution and interrupted-batch replay.
#[derive(Default)]
pub(crate) struct CodeModeAudit {
    actions: BTreeMap<u64, (String, Option<bool>)>,
    changed_paths: BTreeSet<String>,
    changed_paths_incomplete: bool,
    warnings: Vec<String>,
    warning_count: usize,
    unpersisted_execution_effects: crate::sessions::effects::ExecutionEffects,
    pub(crate) contexts: Vec<Value>,
}

impl CodeModeAudit {
    pub(crate) fn observe(&mut self, kind: SessionEventKind, payload: &Value) {
        let sequence = payload["sequence"].as_u64().unwrap_or_default();
        match kind {
            SessionEventKind::CodeModeToolCall => {
                self.actions.insert(
                    sequence,
                    (
                        payload["name"]
                            .as_str()
                            .unwrap_or("unknown")
                            .chars()
                            .take(256)
                            .collect(),
                        None,
                    ),
                );
            }
            SessionEventKind::CodeModeToolResult => {
                if let Some(action) = self.actions.get_mut(&sequence) {
                    action.1 = payload["result"]["success"].as_bool();
                }
                if let Some(paths) = payload["changed_paths"]
                    .as_array()
                    .or_else(|| payload["execution_effects"]["paths"].as_array())
                {
                    self.changed_paths_incomplete |=
                        payload["execution_effects"]["changed_path_count"]
                            .as_u64()
                            .is_some_and(|count| count > paths.len() as u64);
                    for path in paths.iter().filter_map(Value::as_str) {
                        let original = path;
                        let mut path = crate::output::sanitize_display_text(path);
                        self.changed_paths_incomplete |= path != original;
                        self.changed_paths_incomplete |= path.len() > 512;
                        let mut end = path.len().min(512);
                        while !path.is_char_boundary(end) {
                            end -= 1;
                        }
                        path.truncate(end);
                        if self.changed_paths.len() < 1000 {
                            self.changed_paths.insert(path);
                        } else if !self.changed_paths.contains(&path) {
                            self.changed_paths_incomplete = true;
                        }
                    }
                }
            }
            SessionEventKind::CodeModeContext => self.contexts.push(payload.clone()),
            SessionEventKind::CodeModeWarning => {
                if let Some(warning) = payload["warning"].as_str() {
                    self.warning_count = self.warning_count.saturating_add(1);
                    if self.warnings.len() < 32 {
                        let safe = crate::output::sanitize_display_text(warning);
                        let mut end = safe.len().min(512);
                        while !safe.is_char_boundary(end) {
                            end -= 1;
                        }
                        self.warnings.push(safe[..end].to_owned());
                    }
                }
            }
            _ => {}
        }
    }

    pub(crate) fn observe_unpersisted_result(
        &mut self,
        payload: &Value,
        definitely_not_written: bool,
    ) {
        if definitely_not_written {
            self.unpersisted_execution_effects
                .observe_tool_result(Some(SessionEventKind::CodeModeToolResult), payload);
        } else {
            // The inner record may survive: flag uncertainty instead of counting it twice.
            self.unpersisted_execution_effects.mark_incomplete();
        }
    }

    pub(crate) fn summary(&self) -> Value {
        // Keep failures and unknown outcomes visible even after a long successful prefix.
        let mut actions: Vec<_> = self.actions.iter().collect();
        actions.sort_by_key(|(sequence, (_, success))| (success == &Some(true), **sequence));
        actions.truncate(100);
        actions.sort_by_key(|(sequence, _)| **sequence);
        json!({
            "inner_calls": self.actions.len(),
            "successful": self.actions.values().filter(|(_, success)| *success == Some(true)).count(),
            "recoverable_failures": self.actions.values().filter(|(_, success)| *success == Some(false)).count(),
            "unknown_outcomes": self.actions.values().filter(|(_, success)| success.is_none()).count(),
            "running_calls": 0,
            "actions": actions.into_iter().map(|(sequence, (name, success))| json!({"sequence": sequence,"tool":name,"success":success})).collect::<Vec<_>>(),
            "actions_omitted": self.actions.len().saturating_sub(100),
            "changed_paths": self.changed_paths.iter().take(50).collect::<Vec<_>>(),
            "changed_path_count": self.changed_paths.len(),
            "changed_paths_incomplete": self.changed_paths_incomplete,
            "unpersisted_execution_effects": self.unpersisted_execution_effects,
            "warnings": self.warnings,
            "warning_count": self.warning_count,
            "warnings_omitted": self.warning_count.saturating_sub(self.warnings.len()),
            "effects": "Completed actions remain applied. Missing results have unknown outcomes. No rollback or automatic retry. Shell and external effects may be untracked."
        })
    }
}

/// Exact parent attachments within each recorded turn; ambiguous IDs yield no facts.
pub(crate) fn recorded_summaries(
    events: &[crate::sessions::SessionEvent],
) -> BTreeMap<usize, Value> {
    let mut parents = BTreeMap::<&str, Option<usize>>::new();
    let mut audits = BTreeMap::<usize, CodeModeAudit>::new();
    for (index, event) in events.iter().enumerate() {
        match event.kind() {
            Some(SessionEventKind::UserInput) => parents.clear(),
            Some(SessionEventKind::ToolCall)
                if event.payload["name"] == crate::code_mode::TOOL_NAME =>
            {
                if let Some(id) = event.payload["id"].as_str()
                    && let Some(previous) = parents.insert(id, Some(index))
                {
                    if let Some(previous) = previous {
                        audits.remove(&previous);
                    }
                    parents.insert(id, None);
                }
            }
            Some(
                kind @ (SessionEventKind::CodeModeToolCall
                | SessionEventKind::CodeModeToolResult
                | SessionEventKind::CodeModeWarning),
            ) => {
                if let Some(Some(parent)) = event.payload["parent_call_id"]
                    .as_str()
                    .and_then(|id| parents.get(id))
                {
                    audits
                        .entry(*parent)
                        .or_default()
                        .observe(kind, &event.payload);
                }
            }
            _ => {}
        }
    }
    audits
        .into_iter()
        .map(|(index, audit)| (index, audit.summary()))
        .collect()
}