diff --git a/crates/tui/src/core/engine/tests.rs b/crates/tui/src/core/engine/tests.rs index fa888ffdcf..13390522a8 100644 --- a/crates/tui/src/core/engine/tests.rs +++ b/crates/tui/src/core/engine/tests.rs @@ -2601,13 +2601,11 @@ async fn yolo_mode_does_not_prompt_for_model_driven_typed_ask_rule() { Event::ApprovalRequired { .. } => { panic!("YOLO mode must not prompt for a model-driven typed ask-rule"); } - Event::ToolCallComplete { name, result, .. } => { - if name == "exec_shell" { - saw_complete = true; - let result = result.expect("shell result"); - assert!(result.success, "{result:?}"); - assert!(result.content.contains("yolo-model-ask-rule"), "{result:?}"); - } + Event::ToolCallComplete { name, result, .. } if name == "exec_shell" => { + saw_complete = true; + let result = result.expect("shell result"); + assert!(result.success, "{result:?}"); + assert!(result.content.contains("yolo-model-ask-rule"), "{result:?}"); } Event::TurnComplete { status, .. } => { assert_eq!(status, TurnOutcomeStatus::Completed); @@ -2887,8 +2885,12 @@ async fn yolo_mode_does_not_prompt_for_background_shell() { Event::ToolCallComplete { name, result, .. } => { if name == "exec_shell" { saw_tool_result = true; - let result = result.expect("background shell should start"); + let result = result.expect("shell result"); assert!(result.success, "{result:?}"); + assert!( + result.content.contains("Background task started"), + "expected a background start, got: {result:?}" + ); } } Event::TurnComplete { status, .. } => { diff --git a/crates/tui/src/runtime_api/tests.rs b/crates/tui/src/runtime_api/tests.rs index fa91ab20df..b71b2c72dd 100644 --- a/crates/tui/src/runtime_api/tests.rs +++ b/crates/tui/src/runtime_api/tests.rs @@ -3733,6 +3733,9 @@ fn skill_entry_is_bundled_requires_configured_bundle_path() { #[test] fn resolve_skills_dir_rejects_symlink_escaping_workspace() { let tmp = tempfile::tempdir().expect("tempdir"); + let _env_lock = crate::test_support::lock_test_env(); + let _home = crate::test_support::EnvVarGuard::set("HOME", tmp.path()); + let _userprofile = crate::test_support::EnvVarGuard::set("USERPROFILE", tmp.path()); let workspace_root = tmp.path().join("workspace"); let escape_target = tmp.path().join("escape_target"); fs::create_dir_all(&workspace_root).expect("create workspace"); @@ -3762,6 +3765,9 @@ fn resolve_skills_dir_rejects_symlink_escaping_workspace() { #[test] fn resolve_skills_dir_rejects_codewhale_only_symlink_escaping_workspace() { let tmp = tempfile::tempdir().expect("tempdir"); + let _env_lock = crate::test_support::lock_test_env(); + let _home = crate::test_support::EnvVarGuard::set("HOME", tmp.path()); + let _userprofile = crate::test_support::EnvVarGuard::set("USERPROFILE", tmp.path()); let workspace_root = tmp.path().join("workspace"); let escape_target = tmp.path().join("escape_target"); fs::create_dir_all(&workspace_root).expect("create workspace"); diff --git a/crates/tui/src/tui/app.rs b/crates/tui/src/tui/app.rs index ed468426d7..625b8f71c4 100644 --- a/crates/tui/src/tui/app.rs +++ b/crates/tui/src/tui/app.rs @@ -3629,13 +3629,24 @@ impl App { #[must_use] pub fn tool_run_start_for_history_index(&self, index: usize) -> Option { - if !self.tool_collapse_active() || index >= self.history.len() { + if !self.tool_collapse_active() { return None; } - crate::tui::history::detect_tool_runs(&self.history, self.tool_collapse_threshold) - .into_iter() - .find(|run| index >= run.start && index < run.start.saturating_add(run.count)) - .map(|run| run.start) + let active_entries = self + .active_cell + .as_ref() + .map_or(&[][..], crate::tui::active_cell::ActiveCell::entries); + if index >= self.history.len().saturating_add(active_entries.len()) { + return None; + } + crate::tui::history::detect_tool_runs_from_slices( + &self.history, + active_entries, + self.tool_collapse_threshold, + ) + .into_iter() + .find(|run| index >= run.start && index < run.start.saturating_add(run.count)) + .map(|run| run.start) } pub fn toggle_tool_run_expansion_at(&mut self, index: usize) -> bool { diff --git a/crates/tui/src/tui/app/tests.rs b/crates/tui/src/tui/app/tests.rs index 398050203e..59b9f1689a 100644 --- a/crates/tui/src/tui/app/tests.rs +++ b/crates/tui/src/tui/app/tests.rs @@ -1979,6 +1979,36 @@ fn tool_run_expansion_toggle_opens_and_closes_run() { assert!(!app.toggle_tool_run_expansion_at(99)); } +#[test] +fn tool_run_expansion_toggle_handles_active_run() { + let mut app = App::new(test_options(false), &Config::default()); + app.tool_collapse_mode = ToolCollapseMode::Compact; + app.tool_collapse_threshold = 3; + app.add_message(HistoryCell::User { + content: "go".to_string(), + }); + + let active_start = app.history.len(); + let active = app.active_cell.get_or_insert_with(ActiveCell::new); + for name in ["read_file", "list_dir", "web_search"] { + active.push_untracked(HistoryCell::Tool(ToolCell::Generic(GenericToolCell { + name: name.to_string(), + status: ToolStatus::Success, + input_summary: None, + output: Some("ok".to_string()), + prompts: None, + spillover_path: None, + output_summary: None, + is_diff: false, + }))); + } + + assert!(app.toggle_tool_run_expansion_at(active_start)); + assert!(app.expanded_tool_runs.contains(&active_start)); + assert!(app.toggle_tool_run_expansion_at(active_start + 2)); + assert!(!app.expanded_tool_runs.contains(&active_start)); +} + #[test] fn test_scroll_operations() { let mut app = App::new(test_options(false), &Config::default()); diff --git a/crates/tui/src/tui/history.rs b/crates/tui/src/tui/history.rs index 5e48982960..2351c8f47f 100644 --- a/crates/tui/src/tui/history.rs +++ b/crates/tui/src/tui/history.rs @@ -55,7 +55,9 @@ pub use plan::PlanUpdateCell; use thinking::extract_reasoning_summary; #[cfg(test)] use tool_run::ToolRunActivitySummary; -pub use tool_run::{ToolRun, detect_tool_runs, detect_tool_runs_from_slices, tool_run_summary}; +#[cfg(test)] +pub use tool_run::detect_tool_runs; +pub use tool_run::{ToolRun, detect_tool_runs_from_slices, tool_run_summary}; #[cfg(test)] use thinking::{REASONING_CURSOR, REASONING_OPENER, REASONING_RAIL}; diff --git a/crates/tui/src/tui/history/tool_run.rs b/crates/tui/src/tui/history/tool_run.rs index 57f22d0755..fdfc8e6799 100644 --- a/crates/tui/src/tui/history/tool_run.rs +++ b/crates/tui/src/tui/history/tool_run.rs @@ -57,6 +57,7 @@ impl ToolRunActivitySummary { /// cells can join dense runs; `v` / expansion keeps their raw details /// available without making routine verifier/shell work dominate the default /// transcript. +#[cfg(test)] pub fn detect_tool_runs(history: &[HistoryCell], min_size: usize) -> Vec { detect_tool_runs_from_slices(history, &[], min_size) } diff --git a/crates/tui/src/tui/provider_picker.rs b/crates/tui/src/tui/provider_picker.rs index 89da6633a6..0aba7b9282 100644 --- a/crates/tui/src/tui/provider_picker.rs +++ b/crates/tui/src/tui/provider_picker.rs @@ -2577,6 +2577,14 @@ mod tests { #[test] fn self_hosted_provider_row_marks_self_hosted_in_hint() { + let _env_lock = crate::test_support::lock_test_env(); + let _sglang_key = crate::test_support::EnvVarGuard::remove("SGLANG_API_KEY"); + let _sglang_base_url = crate::test_support::EnvVarGuard::remove("SGLANG_BASE_URL"); + let _vllm_key = crate::test_support::EnvVarGuard::remove("VLLM_API_KEY"); + let _vllm_base_url = crate::test_support::EnvVarGuard::remove("VLLM_BASE_URL"); + let _ollama_key = crate::test_support::EnvVarGuard::remove("OLLAMA_API_KEY"); + let _ollama_base_url = crate::test_support::EnvVarGuard::remove("OLLAMA_BASE_URL"); + let config = Config::default(); let row = ProviderDashboardRow::from_config(ApiProvider::Ollama, ApiProvider::Ollama, &config);