magi-code 0.77.1

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

#[test]
fn projects_bash_command_as_bash_card_not_user_turn() {
    let mut state = MissionControlState::default();
    state.apply_output_event(&OutputEvent::BashCommand {
        command: "printf hi".into(),
    });

    let cards = project_cards(&state);
    assert_eq!(cards.len(), 1);
    assert_eq!(cards[0].role, TranscriptCardRole::Bash);
    assert_eq!(cards[0].title, "Bash");
    assert_eq!(cards[0].body, "printf hi");
}

#[test]
fn bash_mode_tool_card_uses_full_activity_preview_inline() {
    let mut state = MissionControlState::default();
    let id = ActivityId::new("bash_mode_1");
    state.apply_output_event(&OutputEvent::BashCommand {
        command: "printf 'one\\ntwo\\nthree\\nfour\\nfive\\n'".into(),
    });
    state
        .transcript
        .push_back("tool: ✓ bash • exit 0".to_string());
    state.transcript.link_activity(
        1,
        TranscriptActivityLink {
            activity_id: id.clone(),
            tool_name: "bash".to_string(),
        },
    );
    state.apply_activity_event(ActivityEvent::Started {
        id: id.clone(),
        parent_id: None,
        kind: ActivityKind::Tool,
        status: ActivityStatus::Success,
        metadata: ActivityMetadata::new("bash"),
    });
    state.apply_activity_event(ActivityEvent::Delta {
        id,
        preview:
            "STATUS: success\nCOMMAND: printf\nEXIT_CODE: 0\nOUTPUT:\none\ntwo\nthree\nfour\nfive\n"
                .to_string(),
    });

    let cards = project_cards(&state);
    assert_eq!(cards.len(), 2);
    assert_eq!(cards[0].role, TranscriptCardRole::Bash);
    assert_eq!(cards[1].role, TranscriptCardRole::Tool);
    assert_eq!(cards[1].title, BASH_MODE_RESPONSE_TITLE);
    assert!(cards[1].body.contains("OUTPUT:"), "{}", cards[1].body);
    assert!(cards[1].body.contains("five"), "{}", cards[1].body);
    assert!(cards[1].body.lines().count() > TOOL_PREVIEW_LINES);

    let visual = state.transcript_visual_text();
    assert!(visual.contains("Bash\n  printf"), "{visual}");
    assert!(
        visual.contains("✓ Tool • Bash Mode Response\n  STATUS: success"),
        "{visual}"
    );
    assert!(visual.contains("OUTPUT:\n  one"), "{visual}");
    assert!(visual.contains("five"), "{visual}");
}

#[test]
fn bash_mode_response_cache_rebuilds_after_command_rollover() {
    let mut state = MissionControlState::default();
    let id = ActivityId::new("bash-rollover");
    state.apply_output_event(&OutputEvent::BashCommand {
        command: "printf response".into(),
    });
    state
        .transcript
        .push_back("tool: ✓ bash • exit 0".to_string());
    state.transcript.link_activity(
        1,
        TranscriptActivityLink {
            activity_id: id.clone(),
            tool_name: "bash".to_string(),
        },
    );
    state.apply_activity_event(ActivityEvent::ToolResultDetail {
        id,
        detail: crate::output::ToolActivityDetail {
            tool_name: "bash".into(),
            label: "bash".into(),
            params: serde_json::json!({"command":"printf response"}),
            metadata: serde_json::json!({
                "exit_code": 0,
                "stdout": "one\ntwo\n",
                "stderr": ""
            }),
            status: ActivityStatus::Success,
            output: "stdout:\none\ntwo\nstderr:\n".into(),
            applied_diff: None,
        },
    });
    state
        .transcript
        .extend((0..498).map(|index| format!("session: filler {index}")));
    assert_eq!(state.transcript.len(), 500);
    state.invalidate_transcript_cache();

    let warmed = project_cards(&state);
    assert_eq!(warmed[1].title, BASH_MODE_RESPONSE_TITLE);
    let _ = state.cached_transcript_lines(80);

    state.apply_output_event(&OutputEvent::UserPrompt {
        text: "after rollover".into(),
    });
    assert_eq!(state.transcript.index_base(), 1);
    assert_eq!(
        state.transcript.front().map(String::as_str),
        Some("tool: ✓ bash • exit 0")
    );

    let cached_cards = project_cards(&state);
    assert_eq!(cached_cards[0].role, TranscriptCardRole::Bash);
    assert_eq!(cached_cards[0].title, "Bash • exit 0");
    let cached_lines = state
        .cached_transcript_lines(80)
        .into_iter()
        .map(|line| line_text(&line))
        .collect::<Vec<_>>();

    state.invalidate_transcript_cache();
    let rebuilt_cards = project_cards(&state);
    let rebuilt_lines = state
        .cached_transcript_lines(80)
        .into_iter()
        .map(|line| line_text(&line))
        .collect::<Vec<_>>();

    assert_eq!(cached_cards, rebuilt_cards);
    assert_eq!(cached_lines, rebuilt_lines);
}

#[test]
fn normal_bash_tool_card_uses_structured_command_and_result_summary() {
    let state = bash_card_state(
        "call_bash",
        "bash",
        ActivityStatus::Success,
        serde_json::json!({"command":"printf hi"}),
        serde_json::json!({
            "exit_code":0,
            "timed_out":false,
            "stdout":"one\ntwo\nthree\nfour\nfive\n",
            "stderr":""
        }),
        "stdout:\none\ntwo\nthree\nfour\nfive\nstderr:\n",
    );

    let cards = project_cards(&state);
    assert_eq!(cards[0].role, TranscriptCardRole::Bash);
    assert_eq!(cards[0].title, "Bash • exit 0");
    assert_eq!(cards[0].body, "printf hi\n5 lines");

    let visual = state.transcript_visual_text();
    assert!(
        visual.contains("✓ Bash • exit 0\n  printf hi\n  5 lines"),
        "{visual}"
    );
    assert!(!visual.contains("STATUS:"), "{visual}");
    assert!(!visual.contains("COMMAND:"), "{visual}");
    assert!(!visual.contains("OUTPUT:"), "{visual}");
    assert!(!visual.contains("one"), "{visual}");
}

#[test]
fn bash_card_projects_running_failure_timeout_cancel_and_alias() {
    let running = bash_card_state(
        "bash-running",
        "shell",
        ActivityStatus::Running,
        serde_json::json!({"cmd":"cargo check"}),
        serde_json::json!({}),
        "",
    );
    let card = &project_cards(&running)[0];
    assert_eq!(card.title, "Bash");
    assert_eq!(card.body, "cargo check");

    let failed = bash_card_state(
        "bash-failed",
        "bash",
        ActivityStatus::Failed,
        serde_json::json!({"command":"cargo test"}),
        serde_json::json!({"exit_code":101,"timed_out":false,"stdout":"","stderr":"test parser failed\nmore"}),
        "stdout:\n\nstderr:\ntest parser failed\nmore",
    );
    let card = &project_cards(&failed)[0];
    assert_eq!(card.title, "Bash • exit 101");
    assert_eq!(card.body, "cargo test\ntest parser failed");

    let timed_out = bash_card_state(
        "bash-timeout",
        "bash",
        ActivityStatus::Failed,
        serde_json::json!({"command":"sleep 60","timeout":9}),
        serde_json::json!({"exit_code":null,"timed_out":true,"stdout":"","stderr":""}),
        "stdout:\n\nstderr:\n",
    );
    let card = &project_cards(&timed_out)[0];
    assert_eq!(card.title, "Bash • timed out");
    assert_eq!(card.body, "sleep 60\ntimed out after 9s");

    let canceled = bash_card_state(
        "bash-canceled",
        "bash",
        ActivityStatus::Canceled,
        serde_json::json!({"command":"sleep 60"}),
        serde_json::json!({}),
        "prompt canceled",
    );
    let card = &project_cards(&canceled)[0];
    assert_eq!(card.title, "Bash • canceled");
    assert_eq!(card.body, "sleep 60\ncanceled");

    let runtime_canceled = bash_card_state(
        "bash-runtime-canceled",
        "bash",
        ActivityStatus::Failed,
        serde_json::json!({"command":"sleep 60"}),
        serde_json::json!({}),
        "prompt canceled",
    );
    let card = &project_cards(&runtime_canceled)[0];
    assert_eq!(card.status, TranscriptCardStatus::Canceled);
    assert_eq!(card.title, "Bash • canceled");
    assert_eq!(card.body, "sleep 60\ncanceled");
}

#[test]
fn bash_card_handles_metadata_free_failure_truncation_and_cleanup_warning() {
    let failed = bash_card_state(
        "bash-preflight",
        "bash",
        ActivityStatus::Failed,
        serde_json::json!({"command":"cat ../outside"}),
        serde_json::json!({}),
        "cwd-scope preflight rejected parent traversal",
    );
    assert_eq!(
        project_cards(&failed)[0].body,
        "cat ../outside\ncwd-scope preflight rejected parent traversal"
    );

    let truncated = bash_card_state(
        "bash-truncated",
        "bash",
        ActivityStatus::Failed,
        serde_json::json!({"command":"yes"}),
        serde_json::json!({"exit_code":0,"stdout_truncated":true,"stderr_truncated":false,"stdout":"many","stderr":""}),
        "stdout:\nmany\nstderr:\n\n[output truncated]",
    );
    assert_eq!(project_cards(&truncated)[0].body, "yes\noutput truncated");

    let cleanup = bash_card_state(
        "bash-cleanup",
        "bash",
        ActivityStatus::Failed,
        serde_json::json!({"command":"sleep 1"}),
        serde_json::json!({"cleanup_warning":"process cleanup incomplete"}),
        "stdout:\n\nstderr:\n\n[process cleanup incomplete]",
    );
    assert_eq!(
        project_cards(&cleanup)[0].body,
        "sleep 1\nprocess cleanup incomplete"
    );
}

#[test]
fn bash_card_decodes_fallback_commands_at_ingestion_and_prefers_structured_params() {
    let id = ActivityId::new("typed-bash");
    let mut state = linked_tool_state(
        id.as_str(),
        "bash",
        "bash printf label",
        ActivityStatus::Running,
        Vec::new(),
        "",
    );
    assert_eq!(project_cards(&state)[0].body, "printf label");
    state.apply_activity_event(ActivityEvent::Delta {
        id: id.clone(),
        preview: "COMMAND: printf preview\n".into(),
    });
    let node = state.nodes.get_mut(&id).unwrap();
    node.preview = "COMMAND: should not be decoded during rendering".into();
    node.metadata.label = "bash also not the command".into();
    state.invalidate_transcript_cache();
    assert_eq!(project_cards(&state)[0].body, "printf preview");
    state.apply_activity_event(ActivityEvent::ToolResultDetail {
        id,
        detail: crate::output::ToolActivityDetail {
            tool_name: "bash".into(),
            label: "bash".into(),
            params: serde_json::json!({"command": " ", "cmd": "printf structured"}),
            metadata: serde_json::json!({"exit_code": 0, "stdout": "done\n"}),
            status: ActivityStatus::Success,
            output: "done\n".into(),
            applied_diff: None,
        },
    });
    assert_eq!(project_cards(&state)[0].body, "printf structured\n1 line");
}