use std::collections::VecDeque; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use tokio::sync::RwLock; use super::misc::{DirCache, InputState, MiscState, ScrollState}; use super::runtime::{SessionRuntime, TurnEvent}; use super::types::{AgentMode, Origin, Toast, TranscriptCache}; use crate::app::mcp::manager::McpManager; use crate::app::workflow::engine::WorkflowEngine; use crate::model::editlog::EditLog; use crate::model::settings::Settings; #[derive(Debug, Clone, PartialEq)] pub struct ChatMessageDisplay { pub role: crate::dto::chat::message::Role, pub content: String, pub timestamp: i64, } impl ChatMessageDisplay { pub fn new(role: crate::dto::chat::message::Role, content: String) -> Self { ChatMessageDisplay { role, content, timestamp: chrono::Utc::now().timestamp_millis(), } } } #[derive(Clone)] pub struct AppStateRest { pub mode: AgentMode, pub settings: Settings, pub workspace_roots: Vec, pub session_id: String, pub session_dir: PathBuf, pub memory_dir: PathBuf, pub download_dir: PathBuf, pub worktrees_dir: PathBuf, pub dir_cache: Arc>, pub edit_log: EditLog, pub session_runtime: Option, pub sessions: Vec, pub transcript_cache: TranscriptCache, pub scroll: ScrollState, pub input: InputState, pub misc: MiscState, pub turn_events: Arc>>, pub turn_in_flight: Arc>, pub workflow_engine: WorkflowEngine, pub mcp_manager: McpManager, pub dirty: bool, pub quit: bool, } impl AppStateRest { pub fn new(workspace_roots: Vec, session_dir: PathBuf, memory_dir: PathBuf) -> Self { let settings = Settings::load(); let download_dir = memory_dir.parent().unwrap_or(&memory_dir).join("downloads"); let worktrees_dir = memory_dir.parent().unwrap_or(&memory_dir).join("worktrees"); let dir_cache = DirCache::new(); let session_id = session_dir .file_name() .map(|n| n.to_string_lossy().to_string()) .unwrap_or_default(); AppStateRest { mode: AgentMode::Normal, settings, workspace_roots, session_id, session_dir: session_dir.clone(), memory_dir, download_dir, worktrees_dir, turn_events: Arc::new(Mutex::new(VecDeque::new())), turn_in_flight: Arc::new(Mutex::new(false)), dir_cache: Arc::new(RwLock::new(dir_cache)), edit_log: EditLog::new(&session_dir), session_runtime: Some(SessionRuntime::new(session_dir.clone())), workflow_engine: WorkflowEngine::new(), mcp_manager: McpManager::new(), sessions: Vec::new(), transcript_cache: TranscriptCache::new(200), scroll: ScrollState::new(), input: InputState::new(), misc: MiscState::new(), dirty: true, quit: false, } } pub fn turn_in_flight(&self) -> bool { self.turn_in_flight.lock().map(|g| *g).unwrap_or(false) } pub fn set_mode(&mut self, mode: AgentMode) { self.mode = mode; self.dirty = true; } pub fn push_transcript(&mut self, msg: ChatMessageDisplay) { self.transcript_cache.messages.push(msg); if self.transcript_cache.messages.len() > self.transcript_cache.max_lines { self.transcript_cache.messages.remove(0); } self.transcript_cache.dirty = true; self.dirty = true; } pub fn push_toast(&mut self, toast: Toast) { self.misc.push_toast(toast); self.dirty = true; } pub fn store_base_dir(&self) -> std::path::PathBuf { self.session_dir.parent() .and_then(|p| p.parent()) .map(|p| p.to_path_buf()) .unwrap_or_else(|| self.session_dir.parent() .map(|p| p.to_path_buf()) .unwrap_or_else(|| self.session_dir.clone())) } pub fn tool_ctx(&self) -> crate::tool::ToolCtx { self.tool_ctx_for(Origin::Main) } pub fn tool_ctx_for(&self, origin: Origin) -> crate::tool::ToolCtx { crate::tool::ToolCtx { workspaces: self.workspace_roots.clone(), session_dir: self.session_dir.clone(), memory_dir: self.memory_dir.clone(), _download_dir: self.download_dir.clone(), worktrees_dir: self.worktrees_dir.clone(), dir_cache: self.dir_cache.clone(), internet_mode: self.settings.internet_mode.clone(), origin, graduated_checks: Vec::new(), } } }