magi-code 0.96.1

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

pub(super) fn bash_card(
    state: &MissionControlState,
    id: TranscriptCardId,
    link: Option<TranscriptActivityLink>,
) -> TranscriptCard {
    let node = link
        .as_ref()
        .and_then(|link| state.nodes.get(&link.activity_id));
    let detail = node.and_then(|node| node.tool_detail.as_ref());
    let mut status = node
        .map(|node| transcript_status_from_activity(node.status))
        .or_else(|| detail.map(|detail| transcript_status_from_activity(detail.status)))
        .unwrap_or_else(|| transcript_tool_status(state, id.entry_index));
    if status == TranscriptCardStatus::Failed && tool_was_canceled(detail) {
        status = TranscriptCardStatus::Canceled;
    }
    let timed_out = detail
        .and_then(|detail| detail.metadata.get("timed_out"))
        .and_then(serde_json::Value::as_bool)
        .unwrap_or(false);
    let exit_code = detail
        .and_then(|detail| detail.metadata.get("exit_code"))
        .and_then(serde_json::Value::as_i64);
    let command = node
        .and_then(|node| node.transcript_bash_command.clone())
        .unwrap_or_default();
    let title = bash_title(status, timed_out, exit_code);
    let intent = detail
        .and_then(|detail| detail.params.get(crate::tools::contract::arg_key::INTENT))
        .and_then(serde_json::Value::as_str)
        .map(str::trim)
        .map(sanitized_single_line)
        .filter(|intent| !intent.is_empty());
    let mut body_rows = intent.iter().cloned().collect::<Vec<_>>();
    let mut display_lines = if command.is_empty() {
        Vec::new()
    } else {
        body_rows.push(command.clone());
        highlight_code(&command, Some("bash"))
    };
    let status_row = match status {
        TranscriptCardStatus::Running | TranscriptCardStatus::None => None,
        TranscriptCardStatus::Success => {
            let lines = bash_output_line_count(detail);
            Some(format!(
                "{lines} {}",
                if lines == 1 { "line" } else { "lines" }
            ))
        }
        TranscriptCardStatus::Failed if timed_out => {
            let timeout = detail
                .and_then(|detail| detail.params.get("timeout"))
                .and_then(serde_json::Value::as_u64)
                .unwrap_or(30);
            Some(format!("timed out after {timeout}s"))
        }
        TranscriptCardStatus::Failed => Some(
            bash_failure_reason(detail)
                .or_else(|| node.and_then(|node| first_non_empty_line(&node.preview)))
                .unwrap_or_else(|| "command failed".to_string()),
        ),
        TranscriptCardStatus::Canceled => Some("canceled".to_string()),
    };
    if let Some(status_row) = status_row {
        display_lines.push(DisplayLine::plain(&status_row));
        body_rows.push(status_row);
    }
    TranscriptCard {
        id,
        role: TranscriptCardRole::Bash,
        status,
        title,
        metadata: bash_jev_metadata(detail),
        body: body_rows.join("\n"),
        body_lines: display_lines,
        children: Vec::new(),
        activity_rows: Vec::new(),
        subagent_card: None,
    }
}

fn bash_title(status: TranscriptCardStatus, timed_out: bool, exit_code: Option<i64>) -> String {
    if timed_out {
        return "Bash • timed out".to_string();
    }
    if matches!(
        status,
        TranscriptCardStatus::Success | TranscriptCardStatus::Failed
    ) && let Some(exit_code) = exit_code
    {
        format!("Bash • exit {exit_code}")
    } else if status == TranscriptCardStatus::Canceled {
        "Bash • canceled".to_string()
    } else {
        "Bash".to_string()
    }
}

fn bash_output_line_count(detail: Option<&crate::output::ToolActivityDetail>) -> usize {
    ["stdout", "stderr"]
        .iter()
        .filter_map(|key| {
            detail?
                .metadata
                .get(key)
                .and_then(serde_json::Value::as_str)
        })
        .map(|stream| stream.lines().count())
        .sum()
}

fn bash_failure_reason(detail: Option<&crate::output::ToolActivityDetail>) -> Option<String> {
    let detail = detail?;
    detail
        .metadata
        .get("cleanup_warning")
        .and_then(serde_json::Value::as_str)
        .and_then(first_non_empty_line)
        .or_else(|| {
            (detail
                .metadata
                .get("stdout_truncated")
                .and_then(serde_json::Value::as_bool)
                == Some(true)
                || detail
                    .metadata
                    .get("stderr_truncated")
                    .and_then(serde_json::Value::as_bool)
                    == Some(true))
            .then(|| "output truncated".to_string())
        })
        .or_else(|| {
            detail
                .metadata
                .get("stderr")
                .and_then(serde_json::Value::as_str)
                .and_then(first_non_empty_line)
        })
        .or_else(|| bash_error_from_output(detail.output.as_ref()))
}

fn bash_error_from_output(output: &str) -> Option<String> {
    output
        .lines()
        .map(str::trim)
        .find(|line| {
            !line.is_empty()
                && !matches!(*line, "stdout:" | "stderr:")
                && !line.starts_with("[output truncated:")
        })
        .map(|line| normalize_inline_separators(&redact_sensitive_text(line)))
}