magi-code 0.63.4

Repository-aware CLI coding agent for terminal work
Documentation
use super::records::{HookDiagnostic, HookPhase};
use crate::{
    config::HookFailurePolicy,
    output::{
        ActivityEvent, ActivityId, ActivityKind, ActivityMetadata, ActivitySender, ActivityStatus,
        redact_sensitive_text,
    },
    providers::ToolCall,
};

#[derive(Clone)]
pub(super) struct HookActivityTarget {
    pub(super) id: ActivityId,
    pub(super) parent_id: Option<ActivityId>,
    pub(super) sender: ActivitySender,
    pub(super) policy: HookFailurePolicy,
    pub(super) label: String,
}

pub(super) fn hook_activity_id(
    parent_id: Option<&ActivityId>,
    phase: HookPhase,
    call: &ToolCall,
    index: usize,
) -> ActivityId {
    let phase = phase.as_str();
    if let Some(parent_id) = parent_id {
        return ActivityId::new(format!("{}/{phase}-hook-{index}", parent_id.as_str()));
    }
    let call_key = if call.id.trim().is_empty() {
        call.name.as_str()
    } else {
        call.id.as_str()
    };
    ActivityId::new(format!("hook/{call_key}/{phase}/{index}"))
}

pub(super) fn emit_hook_activity_started(
    activity: &Option<HookActivityTarget>,
    phase: HookPhase,
    call: &ToolCall,
) {
    if let Some(activity) = activity {
        (activity.sender)(ActivityEvent::Started {
            id: activity.id.clone(),
            parent_id: activity.parent_id.clone(),
            kind: ActivityKind::Hook,
            status: ActivityStatus::Running,
            metadata: hook_activity_metadata(
                phase,
                call,
                &activity.label,
                ActivityStatus::Running,
                None,
                activity.policy,
            ),
        });
    }
}

pub(super) fn emit_hook_activity_success(
    activity: &Option<HookActivityTarget>,
    phase: HookPhase,
    call: &ToolCall,
) {
    if let Some(activity) = activity {
        (activity.sender)(ActivityEvent::Finished {
            id: activity.id.clone(),
            status: ActivityStatus::Success,
            metadata: Some(hook_activity_metadata(
                phase,
                call,
                &activity.label,
                ActivityStatus::Success,
                None,
                activity.policy,
            )),
        });
    }
}

pub(super) fn emit_hook_activity_failed(
    activity: &Option<HookActivityTarget>,
    phase: HookPhase,
    call: &ToolCall,
    diagnostic: &HookDiagnostic,
) {
    if let Some(activity) = activity {
        (activity.sender)(ActivityEvent::Finished {
            id: activity.id.clone(),
            status: ActivityStatus::Failed,
            metadata: Some(hook_activity_metadata(
                phase,
                call,
                &activity.label,
                ActivityStatus::Failed,
                Some(diagnostic.category.as_str()),
                diagnostic.policy,
            )),
        });
    }
}

pub(super) fn hook_activity_metadata(
    phase: HookPhase,
    call: &ToolCall,
    label: &str,
    status: ActivityStatus,
    category: Option<&str>,
    policy: HookFailurePolicy,
) -> ActivityMetadata {
    let label = redact_sensitive_text(label);
    let tool_name = redact_sensitive_text(&call.name);
    let status_text = match status {
        ActivityStatus::Queued => "queued",
        ActivityStatus::Running => "running",
        ActivityStatus::Writing => "writing",
        ActivityStatus::Success => "success",
        ActivityStatus::Failed => "failed",
        ActivityStatus::Canceled => "canceled",
    };
    let mut fields = vec![
        ("phase".to_string(), phase.as_str().to_string()),
        ("hook_label".to_string(), label.clone()),
        ("target_tool".to_string(), tool_name.clone()),
        ("status".to_string(), status_text.to_string()),
        ("policy".to_string(), policy.as_str().to_string()),
    ];
    if let Some(category) = category {
        fields.push(("category".to_string(), category.to_string()));
    }
    let mut detail = format!(
        "{} hook '{}' for tool '{}' {}",
        phase.as_str(),
        label,
        tool_name,
        status_text
    );
    if let Some(category) = category {
        detail.push_str(&format!(" category={category} policy={}", policy.as_str()));
    }
    ActivityMetadata {
        label: format!("{} hook {label} on {tool_name}", phase.as_str()),
        detail: Some(redact_sensitive_text(&detail)),
        fields,
    }
}