holon 0.14.1

A headless, event-driven runtime for long-lived agents
Documentation
use super::chat::CachedChatText;
use super::state::{tui_state_path, TuiClientState};
use super::*;
use std::path::PathBuf;
use tokio::{
    sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
    task::JoinHandle,
};

pub(super) struct TuiApp {
    pub(super) client: LocalClient,
    pub(super) agents: Vec<AgentSummary>,
    pub(super) model_availability: Vec<ResolvedModelAvailability>,
    pub(super) optimistic_operator_messages: Vec<OperatorMessageRecord>,
    pub(super) tasks: Vec<TaskRecord>,
    pub(super) projection: Option<TuiProjection>,
    pub(super) connection_state: TuiConnectionState,
    pub(super) runtime_tx: UnboundedSender<TuiRuntimeMessage>,
    pub(super) runtime_messages: UnboundedReceiver<TuiRuntimeMessage>,
    pub(super) stream_task: Option<JoinHandle<()>>,
    pub(super) agent_list_refresh_deadline: Option<Instant>,
    pub(super) reconnect_deadline: Option<Instant>,
    pub(super) refresh_deadline: Option<Instant>,
    pub(super) agent_list_refresh_in_flight: bool,
    pub(super) model_availability_load_in_flight: bool,
    pub(super) snapshot_refresh_in_flight: bool,
    pub(super) stream_connect_in_flight: bool,
    pub(super) event_history_load_in_flight: bool,
    pub(super) snapshot_refresh_request_id: u64,
    pub(super) stream_connect_request_id: u64,
    pub(super) event_history_request_id: u64,
    pub(super) reconnect_attempt: u32,
    pub(super) selected_agent: usize,
    pub(super) preferred_agent_id: Option<String>,
    pub(super) state_path: PathBuf,
    pub(super) chat_scroll: ChatScrollState,
    pub(super) chat_max_scroll: u16,
    pub(super) composer: ComposerState,
    pub(super) composer_key_burst_started_at: Option<Instant>,
    pub(super) composer_key_burst_last_at: Option<Instant>,
    pub(super) composer_key_burst_len: usize,
    pub(super) slash_menu_selected: usize,
    pub(super) slash_menu_dismissed_for: Option<String>,
    pub(super) overlay: OverlayState,
    pub(super) last_refresh_at: Option<DateTime<Local>>,
    pub(super) last_event_at: Option<DateTime<Local>>,
    pub(super) display_mode: OperatorDisplayMode,
    pub(crate) status_line: String,
    pub(super) should_quit: bool,
    pub(super) chat_text_cache: std::cell::RefCell<Option<CachedChatText>>,
    pub(super) input_history: Vec<String>,
    pub(super) history_index: Option<usize>,
    pub(super) log_writer: TuiLogWriter,
}

impl TuiApp {
    pub(crate) fn new(client: LocalClient, log_writer: TuiLogWriter) -> Self {
        let connection_summary = client.connection_summary();
        let state_path = tui_state_path(&client);
        let preferred_agent_id = TuiClientState::load(&state_path)
            .ok()
            .map(|state| state.last_selected_agent_id);
        let (runtime_tx, runtime_messages) = mpsc::unbounded_channel();
        Self {
            client,
            agents: Vec::new(),
            model_availability: Vec::new(),
            optimistic_operator_messages: Vec::new(),
            tasks: Vec::new(),
            projection: None,
            connection_state: TuiConnectionState::Bootstrapping,
            runtime_tx,
            runtime_messages,
            stream_task: None,
            agent_list_refresh_deadline: None,
            reconnect_deadline: None,
            refresh_deadline: None,
            agent_list_refresh_in_flight: false,
            model_availability_load_in_flight: false,
            snapshot_refresh_in_flight: false,
            stream_connect_in_flight: false,
            event_history_load_in_flight: false,
            snapshot_refresh_request_id: 0,
            stream_connect_request_id: 0,
            event_history_request_id: 0,
            reconnect_attempt: 0,
            selected_agent: 0,
            preferred_agent_id,
            state_path,
            chat_scroll: ChatScrollState::new(),
            chat_max_scroll: 0,
            composer: ComposerState::new(),
            composer_key_burst_started_at: None,
            composer_key_burst_last_at: None,
            composer_key_burst_len: 0,
            slash_menu_selected: 0,
            slash_menu_dismissed_for: None,
            overlay: OverlayState::None,
            last_refresh_at: None,
            last_event_at: None,
            display_mode: OperatorDisplayMode::DEFAULT,
            status_line: format!("Connecting to {connection_summary}..."),
            should_quit: false,
            chat_text_cache: std::cell::RefCell::new(None),
            input_history: Vec::new(),
            history_index: None,
            log_writer,
        }
    }
}