magi-code 0.96.1

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

pub(super) fn humanize_protection_metadata(
    detail: Option<&crate::output::ToolActivityDetail>,
) -> Vec<String> {
    detail
        .and_then(|detail| {
            crate::output::tool_display::humanize_protection_summary(&detail.metadata)
        })
        .map(|summary| vec![format!("Jev: {summary}")])
        .unwrap_or_default()
}
pub(super) fn prompt_injection_protection_metadata(
    detail: Option<&crate::output::ToolActivityDetail>,
) -> Option<String> {
    detail
        .and_then(|detail| {
            crate::output::tool_display::prompt_injection_protection_summary(&detail.metadata)
        })
        .map(|summary| format!("Jev: {summary}"))
}

pub(super) fn bash_jev_metadata(detail: Option<&crate::output::ToolActivityDetail>) -> Vec<String> {
    let Some(detail) = detail else {
        return Vec::new();
    };
    [
        crate::output::tool_display::humanize_protection_summary(&detail.metadata),
        crate::output::tool_display::bash_protection_summary(&detail.metadata),
        crate::output::tool_display::bash_file_tracking_summary(&detail.metadata),
    ]
    .into_iter()
    .flatten()
    .map(|summary| format!("Jev: {summary}"))
    .collect()
}

pub(super) fn attempted_change_preview(value: Option<&serde_json::Value>) -> Vec<String> {
    let Some(text) = value.and_then(serde_json::Value::as_str) else {
        return Vec::new();
    };
    let mut lines = text
        .lines()
        .take(TOOL_PREVIEW_LINES)
        .map(|line| {
            truncate_preview_line(
                &transcript::sanitize_preview(line),
                PREVIEW_LINE_MAX_DISPLAY_WIDTH,
            )
        })
        .collect::<Vec<_>>();
    if text.lines().count() > TOOL_PREVIEW_LINES {
        lines.push(format!(
            "… change preview limited to {TOOL_PREVIEW_LINES} lines"
        ));
    }
    lines
}
pub(super) fn bounded_preview_lines(text: &str) -> String {
    let lines = text
        .lines()
        .map(|line| {
            truncate_preview_line(
                &transcript::sanitize_preview(line),
                PREVIEW_LINE_MAX_DISPLAY_WIDTH,
            )
        })
        .collect::<Vec<_>>();
    if lines.len() <= TOOL_PREVIEW_LINES {
        return lines.join("\n");
    }
    format!(
        "{}\n… response preview limited to {TOOL_PREVIEW_LINES} lines",
        lines[..TOOL_PREVIEW_LINES].join("\n")
    )
}

pub(in crate::tui::transcript_cards::projection) fn bounded_error_line(text: &str) -> String {
    let line = text
        .lines()
        .find(|line| !line.trim().is_empty())
        .unwrap_or("operation failed");
    truncate_preview_line(
        &transcript::sanitize_preview(line),
        PREVIEW_LINE_MAX_DISPLAY_WIDTH,
    )
}

pub(super) fn tool_was_canceled(detail: Option<&crate::output::ToolActivityDetail>) -> bool {
    detail.is_some_and(|detail| {
        detail
            .output
            .lines()
            .map(str::trim)
            .any(|line| line == "prompt canceled")
    })
}

pub(super) fn plural<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str {
    if count == 1 { singular } else { plural }
}

pub(super) fn generic_tool_card(
    state: &MissionControlState,
    id: TranscriptCardId,
    link: Option<TranscriptActivityLink>,
    body: &str,
) -> TranscriptCard {
    let activity = link
        .as_ref()
        .and_then(|link| state.nodes.get(&link.activity_id).map(|node| (link, node)));
    let tool_capability = link
        .as_ref()
        .and_then(|link| ToolCapability::from_dispatch_name(&link.tool_name));
    let role = if tool_capability == Some(ToolCapability::Subagents)
        || (link.is_none()
            && transcript_tool_name(state, id.entry_index)
                == Some(ToolCapability::Subagents.canonical_name()))
    {
        TranscriptCardRole::SubagentBatch
    } else {
        TranscriptCardRole::Tool
    };
    let bash_mode_response = is_bash_mode_response_card(state, id.entry_index, tool_capability);
    let title = if bash_mode_response {
        BASH_MODE_RESPONSE_TITLE.to_string()
    } else {
        tool_title(
            link.as_ref().map(|link| link.tool_name.as_str()),
            transcript_tool_name(state, id.entry_index),
            role,
        )
    };
    let mut status = transcript_tool_status(state, id.entry_index);
    let metadata = Vec::new();
    let mut preview = String::new();
    let children = if role == TranscriptCardRole::SubagentBatch {
        subagent_aggregation::subagent_children(
            state,
            link.as_ref().map(|link| &link.activity_id),
            id.entry_index,
        )
    } else {
        Vec::new()
    };
    if let Some((_, node)) = activity {
        status = transcript_status_from_activity(node.status);
        // Subagent card panel: aggregate progress lives in `subagent_card`, per-agent intent in
        // `children`. The raw tool-output metadata/preview is dropped for
        // SubagentBatch; Tool cards keep their preview but never carry a
        // metadata line (unchanged behavior).
        preview = if bash_mode_response {
            full_activity_preview(&node.preview)
        } else if role != TranscriptCardRole::SubagentBatch {
            bounded_lines(&node.preview, TOOL_PREVIEW_LINES)
        } else {
            String::new()
        };
    }
    let body =
        normalize_inline_separators(&transcript::sanitize_preview(body.trim_end_matches('\n')));
    if preview.is_empty() && role != TranscriptCardRole::SubagentBatch {
        preview = body.clone();
    }
    let subagent_card = if role == TranscriptCardRole::SubagentBatch {
        Some(subagent_aggregation::subagent_card_summary_from_children(
            state,
            link.as_ref().map(|link| &link.activity_id),
            &children,
            activity.map(|(_, node)| node),
        ))
    } else {
        None
    };
    TranscriptCard {
        id,
        role,
        status,
        title: transcript::sanitize_preview(&title),
        metadata,
        body: preview,
        body_lines: Vec::new(),
        children,
        activity_rows: Vec::new(),
        subagent_card,
    }
}

// These spans describe already-sanitized adapter text, not source previews.
pub(super) fn split_role_line(
    text: &str,
    split: usize,
    prefix: DisplayRole,
    suffix: DisplayRole,
) -> DisplayLine {
    tool_display_line(vec![
        DisplaySpan::new(&text[..split], prefix),
        DisplaySpan::new(&text[split..], suffix),
    ])
}

pub(super) fn tool_display_line(spans: Vec<DisplaySpan>) -> DisplayLine {
    use unicode_segmentation::UnicodeSegmentation;
    let line = DisplayLine { spans, table: None };
    let text = line.plain_text();
    let mut boundary = 0;
    for span in line.spans.iter().take(line.spans.len().saturating_sub(1)) {
        boundary += span.text.len();
        if boundary != text.len()
            && !text
                .grapheme_indices(true)
                .any(|(index, _)| index == boundary)
        {
            // Keep combining sequences intact rather than splitting them between styles.
            return DisplayLine::plain(text);
        }
    }
    line
}