magi-code 0.97.5

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;
use crate::output::tool_card::{self, TaskActivityFacts, TaskFacts};

fn subagent_card_total_from_fields(node: &crate::tui::activity::ActivityNode) -> Option<usize> {
    node.metadata.fields.iter().find_map(|(key, value)| {
        (key == "tasks")
            .then(|| value.trim().parse::<usize>().ok())
            .flatten()
    })
}

pub(super) fn subagent_children(
    state: &MissionControlState,
    activity_id: Option<&ActivityId>,
    entry_index: usize,
) -> Vec<TranscriptCardChild> {
    let from_activity = activity_id
        .and_then(|id| state.children.get(id))
        .map(|children| {
            children
                .iter()
                .filter_map(|child_id| {
                    let node = state.nodes.get(child_id)?;
                    let task = node.transcript_subagent.as_ref();
                    let live_tail = task_live_tail(state, child_id);
                    let shared = tool_card::project_task(&TaskFacts {
                        label: &node.metadata.label,
                        intent: task.map(|task| task.intent.as_str()),
                        status: Some(node.status),
                        fields: &node.metadata.fields,
                        failure: (node.status == ActivityStatus::Failed)
                            .then_some(node.preview.as_str()),
                        usage: node.usage,
                        live_tail: &live_tail,
                    });
                    Some(TranscriptCardChild {
                        label: shared.label,
                        status: tool_adapters::terminal_status(shared.status),
                        metadata: Vec::new(),
                        body: shared.body,
                        task_id: task.map(|task| task.task_id.clone()).unwrap_or_default(),
                        intent: shared.intent,
                        identity: shared.identity,
                        agent: shared.agent,
                        model_line: shared.model_line,
                        subagent_display: node.subagent_display.clone(),
                        usage: shared.usage,
                        last_known_cache_percent: node.last_known_cache_percent,
                        last_known_latest_cache_percent: node.last_known_latest_cache_percent,
                        live_tail: shared.live_tail,
                        activity_id: Some(child_id.clone()),
                    })
                })
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();
    if !from_activity.is_empty() {
        return from_activity;
    }

    state
        .transcript
        .get(entry_index)
        .and_then(TranscriptEntry::tool_data)
        .map(|data| {
            data.children
                .iter()
                .map(|child| {
                    let shared = tool_card::project_task(&TaskFacts {
                        label: &child.label,
                        intent: Some(&child.intent),
                        status: child.status,
                        fields: &[],
                        failure: None,
                        usage: None,
                        live_tail: &[],
                    });
                    TranscriptCardChild {
                        label: shared.label,
                        status: tool_adapters::terminal_status(shared.status),
                        metadata: Vec::new(),
                        body: shared.body,
                        task_id: child.task_id.clone(),
                        intent: shared.intent,
                        identity: shared.identity,
                        agent: shared.agent,
                        model_line: shared.model_line,
                        subagent_display: Default::default(),
                        usage: shared.usage,
                        last_known_cache_percent: None,
                        last_known_latest_cache_percent: None,
                        live_tail: shared.live_tail,
                        activity_id: None,
                    }
                })
                .collect()
        })
        .unwrap_or_default()
}

fn task_live_tail(state: &MissionControlState, task_id: &ActivityId) -> Vec<String> {
    let mut events = state
        .nodes
        .values()
        .filter(|node| node.id == *task_id || activity_is_descendant_of(state, &node.id, task_id))
        .collect::<Vec<_>>();
    events.sort_by(|left, right| {
        right
            .own_update_sequence
            .cmp(&left.own_update_sequence)
            .then_with(|| right.id.as_str().cmp(left.id.as_str()))
    });

    let mut lines = Vec::new();
    for node in events {
        for line in subagent_card_activity_lines(node) {
            lines.push(line);
            if lines.len() == state.subagent_card_rows() {
                lines.reverse();
                return lines;
            }
        }
    }
    lines.reverse();
    lines
}

fn activity_is_descendant_of(
    state: &MissionControlState,
    id: &ActivityId,
    ancestor: &ActivityId,
) -> bool {
    let mut parent = state.nodes.get(id).and_then(|node| node.parent_id.as_ref());
    let mut remaining = state.nodes.len();
    while let Some(parent_id) = parent {
        if parent_id == ancestor {
            return true;
        }
        if remaining == 0 {
            break;
        }
        remaining -= 1;
        parent = state
            .nodes
            .get(parent_id)
            .and_then(|node| node.parent_id.as_ref());
    }
    false
}

fn subagent_card_activity_lines(node: &crate::tui::activity::ActivityNode) -> Vec<String> {
    tool_card::project_task_activity(&TaskActivityFacts {
        kind: &node.kind,
        status: node.status,
        label: &node.metadata.label,
        permitted_preview: &node.preview,
        tool_detail: node.tool_detail.as_ref(),
        reasoning_summary: node
            .transcript_reasoning
            .as_ref()
            .map(|summary| (summary.count, summary.latest.as_deref())),
    })
}

pub(super) fn subagent_card_summary_from_children(
    state: &MissionControlState,
    batch_id: Option<&ActivityId>,
    children: &[TranscriptCardChild],
    node: Option<&crate::tui::activity::ActivityNode>,
) -> SubagentCardSummary {
    let (done, failed, running) =
        tool_card::task_counts(children.iter().map(|child| match child.status {
            TranscriptCardStatus::Success => tool_card::CardStatus::Success,
            TranscriptCardStatus::Canceled => tool_card::CardStatus::Canceled,
            TranscriptCardStatus::Failed => tool_card::CardStatus::Failed,
            TranscriptCardStatus::Running => tool_card::CardStatus::Running,
            TranscriptCardStatus::None => tool_card::CardStatus::None,
        }));
    let total = children
        .len()
        .max(node.and_then(subagent_card_total_from_fields).unwrap_or(0));
    let selection = batch_id.and_then(|id| state.subagent_card_selection.get(id));
    let selected = batch_id.and_then(|batch_id| {
        let pinned = selection
            .and_then(|selection| selection.task_id.as_ref())
            .and_then(|task_id| {
                children
                    .iter()
                    .find(|child| child.activity_id.as_ref() == Some(task_id))
            });
        pinned.or_else(|| children.first()).and_then(|child| {
            child
                .activity_id
                .clone()
                .or_else(|| (!child.task_id.is_empty()).then(|| batch_id.child(&child.task_id)))
        })
    });

    SubagentCardSummary {
        total: total.max(done + failed + running),
        done,
        failed,
        running,
        selected_task_id: selected
            .as_ref()
            .map(|id| id.as_str().rsplit('/').next().unwrap_or("").to_string()),
        batch_id: batch_id.cloned(),
    }
}