magi-code 0.63.4

Repository-aware CLI coding agent for terminal work
Documentation
mod activity;
mod events;
mod input;
mod layout;
mod perf;
mod prompt;
pub(crate) mod render;
mod screens;
mod sessions;
pub(crate) mod state;
pub(crate) mod terminal;
mod theme;
mod toast;
mod transcript;
mod transcript_cards;
pub(crate) mod usage;
mod viewports;
mod worker;

#[cfg(test)]
use self::input::submit_command::{
    is_new_session_command, is_quit_command, slash_command_token, unsupported_slash_command,
};
#[cfg(test)]
use self::sessions::commands::execute_switch_session_command;
use self::{
    events::delivery::send_tui_event,
    input::{
        autocomplete_sources::{
            refresh_skill_autocomplete_candidates, tui_autocomplete_candidates,
            tui_file_autocomplete_candidates, tui_skill_autocomplete_candidates,
        },
        mouse::{
            apply_copy_request, handle_mouse_event, handle_mouse_event_with_scroll_rows,
            mouse_scroll_batch,
        },
        submit_command::{SubmitDecision, TuiSubmitCommand, submit_decision, tui_submit_command},
    },
    screens::primary_agent_startup::{
        record_primary_agent_startup_diagnostics, resolve_persisted_primary_agent_selection,
    },
    sessions::commands::{
        SessionSwitchLoad, apply_footer_context, apply_session_hydration_snapshot,
        execute_new_session_command, execute_prune_sessions_command, load_session_preview,
        preserve_critical_tui_events, session_picker_rows, short_session_id,
        start_session_switch_load,
    },
    worker::{
        CompactionActivityFinal, WorkerFinalEvent, WorkerOutcomeState, WorkerState,
        apply_worker_final_event, send_completion,
    },
};
#[cfg(test)]
use crate::commands::BuiltInCommand;
use crate::{
    agent::runner::{
        ProviderRunOptions, append_primary_agent_to_main_prompt, run_bash_mode_once,
        run_provider_once_streaming_with_steering,
    },
    commands::CommandRegistry,
    config::EffectiveConfig,
    instructions::InstructionFile,
    output::{ActivityEvent, ActivitySender, OutputEvent},
    sessions::{Session, SessionEventKind, SessionManager, record_session_event},
    shell::ShellState,
    skills::{SkillDiscovery, filter_enabled_skills},
};
use anyhow::Result;
use crossbeam_channel::{Receiver, Sender, bounded};
#[cfg(test)]
use std::fs;
use std::{
    collections::VecDeque,
    panic,
    path::PathBuf,
    process::Command,
    sync::{
        Arc,
        atomic::{AtomicBool, AtomicU64, Ordering},
        mpsc,
    },
    thread::{self, JoinHandle},
    time::{Duration, Instant},
};

#[cfg(test)]
pub(crate) trait TranscriptDequeExt {
    fn join(&self, separator: &str) -> String;
}

#[cfg(test)]
impl TranscriptDequeExt for VecDeque<String> {
    fn join(&self, separator: &str) -> String {
        self.iter()
            .map(String::as_str)
            .collect::<Vec<_>>()
            .join(separator)
    }
}

// Root TUI keeps module-boundary surfaces: session config, event enum,
// channel entrypoints, cross-module separators, and terminal panic restore wiring.
pub(crate) struct TuiSessionConfig {
    pub(crate) config: EffectiveConfig,
    pub(crate) instructions: Vec<InstructionFile>,
    pub(crate) discovered_skills: SkillDiscovery,
    pub(crate) skills: SkillDiscovery,
    pub(crate) commands: CommandRegistry,
    pub(crate) manager: SessionManager,
    pub(crate) active_session: Option<Session>,
    pub(crate) cwd: PathBuf,
}

#[derive(Debug, Clone)]
pub(crate) enum TuiEvent {
    Output(OutputEvent),
    Activity(ActivityEvent),
    Error(String),
    ModelCatalog {
        request_id: u64,
        result: Result<crate::model_catalog::CatalogForUi, String>,
        visible_rows: usize,
    },
    UsageLoaded {
        request_id: u64,
        result: crate::tui::usage::UsageLoadResult,
    },
    LoginInstructions(crate::login::LoginInstructions),
    CompactionFinishedAuthoritative {
        result: Result<Option<crate::compaction::CompactionResult>, String>,
        activity: worker::CompactionActivityFinal,
    },
    LoginFinished {
        provider_id: String,
        result: Result<String, String>,
    },
    Done,
    RunCanceled {
        prompt: String,
    },
    RunFinished {
        final_event: Option<Box<TuiEvent>>,
    },
    SessionTitleUpdated {
        session_id: String,
        title: String,
    },
    SessionPreviewLoaded {
        request_id: u64,
        session_id: String,
        preview: state::SessionPreviewState,
    },
    SessionSwitchLoaded {
        request_id: u64,
        session_id: String,
        result: Box<Result<SessionSwitchLoad, String>>,
    },
    FooterGitBranchLoaded {
        request_id: u64,
        branch: Option<String>,
    },
    StartupLoaded {
        request_id: u64,
        result: Box<Result<TuiStartupLoaded, String>>,
    },
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct TuiStartupLoaded {
    pub(crate) settings: Option<crate::config::Settings>,
    pub(crate) subagent_profile_discovery: crate::subagents::profiles::SubagentProfileDiscovery,
    pub(crate) primary_agent_discovery: crate::primary_agents::PrimaryAgentProfileDiscovery,
    pub(crate) persisted_primary_agent: Option<String>,
    pub(crate) persisted_primary_agent_error: Option<String>,
    pub(crate) active_latest_title: Option<String>,
    pub(crate) file_autocomplete_candidates: Vec<state::AutocompleteCandidate>,
    pub(crate) skill_autocomplete_candidates: Vec<state::AutocompleteCandidate>,
    pub(crate) thinking_levels: Vec<crate::thinking::ThinkingLevel>,
    pub(crate) footer_git_branch: Option<String>,
}

const CRITICAL_EVENT_TIMEOUT: Duration = Duration::from_millis(250);
const PROMPT_CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
const FOOTER_GIT_BRANCH_REFRESH_INTERVAL: Duration = Duration::from_secs(2);

pub(crate) const INLINE_SEPARATOR_CHAR: char = '';
pub(crate) const PADDED_INLINE_SEPARATOR: &str = "";
const LEGACY_PADDED_INLINE_SEPARATOR: &str = " · ";

pub(crate) fn split_once_inline_separator(text: &str) -> Option<(&str, &str)> {
    text.split_once(PADDED_INLINE_SEPARATOR)
        .or_else(|| text.split_once(LEGACY_PADDED_INLINE_SEPARATOR))
}

pub(crate) fn rsplit_once_inline_separator(text: &str) -> Option<(&str, &str)> {
    text.rsplit_once(PADDED_INLINE_SEPARATOR)
        .or_else(|| text.rsplit_once(LEGACY_PADDED_INLINE_SEPARATOR))
}

pub(crate) fn normalize_inline_separators(text: &str) -> String {
    text.replace(LEGACY_PADDED_INLINE_SEPARATOR, PADDED_INLINE_SEPARATOR)
}
#[cfg(test)]
const CANCEL_JOIN_GRACE_PERIOD: Duration = Duration::from_millis(50);
#[cfg(test)]
const WORKER_EXIT_JOIN_TIMEOUT: Duration = CANCEL_JOIN_GRACE_PERIOD;
#[cfg(not(test))]
const WORKER_EXIT_JOIN_TIMEOUT: Duration = Duration::from_secs(2);
static NEXT_TUI_RUN_ID: AtomicU64 = AtomicU64::new(1);

pub(crate) fn send_critical(sender: &Sender<TuiEvent>, event: TuiEvent) -> anyhow::Result<()> {
    // PRD-0005: user prompts, final assistant reconciliation, tool lifecycle,
    // tool results, errors, and Done are critical display-state events. Use a
    // bounded wait so the provider worker reports saturation instead of silently
    // losing final state or deadlocking forever.
    sender
        .send_timeout(event, CRITICAL_EVENT_TIMEOUT)
        .map_err(|error| anyhow::anyhow!("tui event channel blocked or closed: {error}"))
}

pub(crate) fn send_best_effort(sender: &Sender<TuiEvent>, event: TuiEvent) {
    // High-volume previews use bounded drop-on-full backpressure. Keep a small
    // queue reserve so critical final/control events do not sit behind stale
    // preview bursts before their bounded send timeout starts failing.
    const BEST_EFFORT_QUEUE_RESERVE: usize = 64;
    if sender.capacity().is_some_and(|capacity| {
        capacity > BEST_EFFORT_QUEUE_RESERVE
            && sender.len() >= capacity.saturating_sub(BEST_EFFORT_QUEUE_RESERVE)
    }) {
        return;
    }
    match sender.try_send(event) {
        Ok(()) | Err(crossbeam_channel::TrySendError::Full(_)) => {}
        Err(crossbeam_channel::TrySendError::Disconnected(_)) => {}
    }
}

pub(crate) fn run_tui(config: TuiSessionConfig) -> Result<()> {
    crate::rendering::highlight::prewarm();
    install_terminal_restore_panic_hook();
    let (sender, receiver) = bounded::<TuiEvent>(1024);
    let mut app = MissionControlApp::new(config, sender);
    app.run(receiver)
}

fn install_terminal_restore_panic_hook() {
    let previous_hook = panic::take_hook();
    panic::set_hook(Box::new(move |panic_info| {
        terminal::restore_terminal_best_effort();
        previous_hook(panic_info);
    }));
}

mod controller;
use controller::*;

#[cfg(test)]
#[path = "tests.rs"]
mod tests;