diff --git a/src/agent/tools/mod.rs b/src/agent/tools/mod.rs index 24b2876..1ced965 100644 --- a/src/agent/tools/mod.rs +++ b/src/agent/tools/mod.rs @@ -21,7 +21,6 @@ mod vision; use std::future::Future; use std::pin::Pin; -use std::sync::Arc; use std::time::Instant; fn default_timeout() -> u64 { 120 } @@ -108,6 +107,12 @@ impl ActiveTools { self.0.iter() } + pub fn abort_all(&mut self) { + for entry in self.0.drain(..) { + entry.handle.abort(); + } + } + pub fn len(&self) -> usize { self.0.len() } pub fn is_empty(&self) -> bool { self.0.is_empty() } } diff --git a/src/mind/mod.rs b/src/mind/mod.rs index 24e8964..c0e5da2 100644 --- a/src/mind/mod.rs +++ b/src/mind/mod.rs @@ -264,8 +264,6 @@ impl Mind { config: SessionConfig, turn_tx: mpsc::Sender<(Result, StreamTarget)>, ) -> Self { - let shared_active_tools = crate::agent::tools::shared_active_tools(); - let client = ApiClient::new(&config.api_base, &config.api_key, &config.model); let conversation_log = log::ConversationLog::new( config.session_dir.join("conversation.jsonl"), @@ -278,7 +276,7 @@ impl Mind { config.app.clone(), config.prompt_file.clone(), conversation_log, - shared_active_tools, + crate::agent::tools::ActiveTools::new(), ).await; let shared = Arc::new(std::sync::Mutex::new(MindState::new(config.app.dmn.max_turns))); @@ -352,10 +350,7 @@ impl Mind { } MindCommand::Interrupt => { self.shared.lock().unwrap().interrupt(); - let active_tools = self.agent.state.lock().await.active_tools.clone(); - let mut tools = active_tools.lock().unwrap(); - for entry in tools.drain(..) { entry.handle.abort(); } - drop(tools); + self.agent.state.lock().await.active_tools.abort_all(); if let Some(h) = self.shared.lock().unwrap().turn_handle.take() { h.abort(); } self.shared.lock().unwrap().turn_active = false; let _ = self.turn_watch.send(false); diff --git a/src/user/chat.rs b/src/user/chat.rs index a6cfed7..271db6e 100644 --- a/src/user/chat.rs +++ b/src/user/chat.rs @@ -585,8 +585,9 @@ impl InteractScreen { /// Draw the main (F1) screen — four-pane layout with status bar. fn draw_main(&mut self, frame: &mut Frame, size: Rect, app: &App) { // Main layout: content area + active tools overlay + status bar - let active_tools = app.active_tools.lock().unwrap(); - let tool_lines = active_tools.len() as u16; + let st_guard = app.agent.state.try_lock().ok(); + let tool_lines = st_guard.as_ref() + .map(|st| st.active_tools.len() as u16).unwrap_or(0); let main_chunks = Layout::default() .direction(Direction::Vertical) .constraints([ @@ -675,10 +676,10 @@ impl InteractScreen { frame.render_widget(gutter, input_chunks[0]); frame.render_widget(&self.textarea, input_chunks[1]); - // Draw active tools overlay - if !active_tools.is_empty() { + if let Some(ref st) = st_guard { + if !st.active_tools.is_empty() { let tool_style = Style::default().fg(Color::Yellow).add_modifier(Modifier::DIM); - let tool_text: Vec = active_tools.iter().map(|t| { + let tool_text: Vec = st.active_tools.iter().map(|t| { let elapsed = t.started.elapsed().as_secs(); let line = if t.detail.is_empty() { format!(" [{}] ({}s)", t.name, elapsed) @@ -689,7 +690,7 @@ impl InteractScreen { }).collect(); let tool_para = Paragraph::new(tool_text); frame.render_widget(tool_para, tools_overlay_area); - } + }} // Draw status bar with live activity indicator let timer = if !app.activity.is_empty() { diff --git a/src/user/context.rs b/src/user/context.rs index 9cdd777..d368a26 100644 --- a/src/user/context.rs +++ b/src/user/context.rs @@ -125,7 +125,9 @@ impl ScreenView for ConsciousScreen { ))); lines.push(Line::raw(format!(" Reasoning: {}", app.reasoning_effort))); lines.push(Line::raw(format!(" Running processes: {}", app.running_processes))); - lines.push(Line::raw(format!(" Active tools: {}", app.active_tools.lock().unwrap().len()))); + let tool_count = app.agent.state.try_lock() + .map(|st| st.active_tools.len()).unwrap_or(0); + lines.push(Line::raw(format!(" Active tools: {}", tool_count))); let block = pane_block("context") .title_top(Line::from(screen_legend()).left_aligned()) diff --git a/src/user/mod.rs b/src/user/mod.rs index b24aa2f..30a7822 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -126,7 +126,7 @@ impl App { temperature: 0.6, top_p: 0.95, top_k: 20, - agent: mind.agent.clone(), + agent, should_quit: false, context_info: None, agent_state: Vec::new(), @@ -181,8 +181,6 @@ async fn start(cli: crate::user::CliArgs) -> Result<()> { let mind = crate::mind::Mind::new(config, turn_tx).await; - let shared_active_tools = mind.agent.state.lock().await.active_tools.clone(); - let mut result = Ok(()); tokio_scoped::scope(|s| { // Mind event loop — init + run @@ -194,7 +192,7 @@ async fn start(cli: crate::user::CliArgs) -> Result<()> { // UI event loop s.spawn(async { result = run( - tui::App::new(String::new(), shared_active_tools), + tui::App::new(String::new(), mind.agent.clone()), &mind, mind_tx, ).await; }); @@ -222,15 +220,11 @@ fn hotkey_cycle_reasoning(mind: &crate::mind::Mind) { async fn hotkey_kill_processes(mind: &crate::mind::Mind) { let mut st = mind.agent.state.lock().await; - let active_tools = st.active_tools.clone(); - let mut tools = active_tools.lock().unwrap(); - if tools.is_empty() { + if st.active_tools.is_empty() { st.notify("no running tools"); } else { - let count = tools.len(); - for entry in tools.drain(..) { - entry.handle.abort(); - } + let count = st.active_tools.len(); + st.active_tools.abort_all(); st.notify(format!("killed {} tools", count)); } }