magi-code 0.96.1

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

pub(super) fn hydrate_diagnostic_event(
    ui_state: &mut state::MissionControlState,
    event: &SessionEvent,
) {
    let Some(message) = sanitized_payload_str(&event.payload, "message") else {
        return;
    };
    let level =
        sanitized_payload_str(&event.payload, "level").unwrap_or_else(|| "warning".to_string());
    ui_state.apply_output_event_at(
        &OutputEvent::Diagnostic { level, message },
        Some(event.timestamp),
    );
}

pub(super) fn hydrate_hook_diagnostic_event(
    ui_state: &mut state::MissionControlState,
    event: &SessionEvent,
) {
    let payload = &event.payload;
    let Some(message) = sanitized_payload_str(payload, "message") else {
        return;
    };
    let diagnostic = HookDiagnostic {
        phase: parse_hook_phase(
            sanitized_payload_str(payload, "phase")
                .as_deref()
                .unwrap_or("after_tool"),
        ),
        tool_name: sanitized_payload_str(payload, "tool")
            .or_else(|| sanitized_payload_str(payload, "target_tool"))
            .unwrap_or_else(|| "unknown".to_string()),
        label: sanitized_payload_str(payload, "label").unwrap_or_else(|| "hook".to_string()),
        category: match sanitized_payload_str(payload, "category").as_deref() {
            Some("exit") => HookFailureCategory::Exit,
            Some("timeout") => HookFailureCategory::Timeout,
            Some("stdin") => HookFailureCategory::Stdin,
            Some("output-limit") => HookFailureCategory::OutputLimit,
            _ => HookFailureCategory::Runner,
        },
        policy: match sanitized_payload_str(payload, "policy").as_deref() {
            Some("ignore") => HookFailurePolicy::Ignore,
            Some("block") => HookFailurePolicy::Block,
            Some("fail") => HookFailurePolicy::Fail,
            _ => HookFailurePolicy::Warn,
        },
        target_ran: payload
            .get("target_ran")
            .and_then(serde_json::Value::as_bool)
            .unwrap_or(false),
        message,
    };
    ui_state.apply_output_event_at(
        &OutputEvent::HookDiagnostic {
            diagnostic: Box::new(diagnostic),
        },
        Some(event.timestamp),
    );
}

pub(super) fn parse_hook_phase(value: &str) -> HookPhase {
    match value {
        "before_tool" => HookPhase::Before,
        "after_assistant" => HookPhase::AfterAssistant,
        "after_reasoning" => HookPhase::AfterReasoning,
        _ => HookPhase::After,
    }
}

pub(super) fn hydrate_hook_lifecycle_event(
    ui_state: &mut state::MissionControlState,
    event: &SessionEvent,
    tool_activity_ids: &std::collections::HashMap<String, ActivityId>,
    started_hooks: &mut std::collections::HashSet<ActivityId>,
    hook_activity_sequence: &mut usize,
) {
    let payload = &event.payload;
    let status = payload
        .get("status")
        .and_then(serde_json::Value::as_str)
        .unwrap_or_default();
    let id = payload
        .get("activity_id")
        .and_then(serde_json::Value::as_str)
        .filter(|value| !value.trim().is_empty())
        .map(ActivityId::new)
        .unwrap_or_else(|| {
            *hook_activity_sequence = hook_activity_sequence.saturating_add(1);
            ActivityId::new(format!("history/hook-lifecycle-{hook_activity_sequence}"))
        });
    let parent_id = payload
        .get("tool_call_id")
        .and_then(serde_json::Value::as_str)
        .and_then(|call_id| tool_activity_ids.get(call_id).cloned());
    let metadata = hook_lifecycle_metadata(payload);
    match status {
        "started" => {
            started_hooks.insert(id.clone());
            ui_state.apply_historical_activity_event(ActivityEvent::Started {
                id,
                parent_id,
                kind: ActivityKind::Hook,
                status: ActivityStatus::Running,
                metadata,
            });
        }
        "success" | "failed" | "blocked" | "ignored" => {
            if !started_hooks.contains(&id) {
                ui_state.apply_historical_activity_event(ActivityEvent::Started {
                    id: id.clone(),
                    parent_id,
                    kind: ActivityKind::Hook,
                    status: ActivityStatus::Running,
                    metadata: metadata.clone(),
                });
                started_hooks.insert(id.clone());
            }
            ui_state.apply_historical_activity_event(ActivityEvent::Finished {
                id,
                status: if status == "success" {
                    ActivityStatus::Success
                } else {
                    ActivityStatus::Failed
                },
                metadata: Some(metadata),
            });
        }
        _ => {}
    }
}

fn hook_lifecycle_metadata(payload: &serde_json::Value) -> ActivityMetadata {
    let phase = sanitized_payload_str(payload, "phase").unwrap_or_else(|| "hook".to_string());
    let label = sanitized_payload_str(payload, "label").unwrap_or_else(|| "hook".to_string());
    let target_tool =
        sanitized_payload_str(payload, "target_tool").unwrap_or_else(|| "unknown".to_string());
    let status = sanitized_payload_str(payload, "status").unwrap_or_else(|| "unknown".to_string());
    let policy = sanitized_payload_str(payload, "policy").unwrap_or_else(|| "warn".to_string());
    let category = sanitized_payload_str(payload, "category");
    let mut fields = vec![
        ("phase".to_string(), phase.clone()),
        ("hook_label".to_string(), label.clone()),
        ("target_tool".to_string(), target_tool.clone()),
        ("status".to_string(), status.clone()),
        ("policy".to_string(), policy.clone()),
    ];
    if let Some(category) = &category {
        fields.push(("category".to_string(), category.clone()));
    }
    if let Some(target_ran) = payload
        .get("target_ran")
        .and_then(serde_json::Value::as_bool)
    {
        fields.push(("target_ran".to_string(), target_ran.to_string()));
    }
    if let Some(elapsed_ms) = payload
        .get("elapsed_ms")
        .and_then(serde_json::Value::as_u64)
    {
        fields.push(("elapsed_ms".to_string(), elapsed_ms.to_string()));
    }
    let mut detail = format!("recorded {phase} hook '{label}' for tool '{target_tool}' {status}");
    if let Some(category) = category {
        detail.push_str(&format!(" category={category} policy={policy}"));
    }
    if let Some(message) = sanitized_payload_str(payload, "message") {
        detail.push_str(&format!(": {message}"));
    }
    ActivityMetadata {
        label: format!("{phase} hook {label} on {target_tool}"),
        detail: Some(redact_sensitive_text(&detail)),
        fields,
    }
}