From 1db8c5e6e9d3cb7ca6d9df74406414d45c4c27a0 Mon Sep 17 00:00:00 2001 From: cyq <15000851237@163.com> Date: Tue, 30 Jun 2026 07:55:14 +0800 Subject: [PATCH 1/2] fix(tui): expand active tool run summaries --- crates/tui/src/tui/app.rs | 21 ++++++++++++++++----- crates/tui/src/tui/app/tests.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/crates/tui/src/tui/app.rs b/crates/tui/src/tui/app.rs index 2f414e272f..de619ba74f 100644 --- a/crates/tui/src/tui/app.rs +++ b/crates/tui/src/tui/app.rs @@ -3524,13 +3524,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 6d1e938736..ad005102b4 100644 --- a/crates/tui/src/tui/app/tests.rs +++ b/crates/tui/src/tui/app/tests.rs @@ -1973,6 +1973,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()); From 72cccc545046ed50baefc1a17bc6ccb3081eb5d9 Mon Sep 17 00:00:00 2001 From: cyq <15000851237@163.com> Date: Tue, 30 Jun 2026 10:24:51 +0800 Subject: [PATCH 2/2] test(tui): keep CI gates stable --- crates/tui/src/core/engine/tests.rs | 30 +++++++++++--------------- crates/tui/src/runtime_api/tests.rs | 12 +++++++++-- crates/tui/src/tui/history.rs | 4 +++- crates/tui/src/tui/history/tool_run.rs | 1 + crates/tui/src/tui/provider_picker.rs | 8 +++++++ crates/tui/src/working_set.rs | 4 ++-- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/crates/tui/src/core/engine/tests.rs b/crates/tui/src/core/engine/tests.rs index dcd05c4600..8a4467e332 100644 --- a/crates/tui/src/core/engine/tests.rs +++ b/crates/tui/src/core/engine/tests.rs @@ -2555,13 +2555,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); @@ -2690,16 +2688,14 @@ async fn yolo_mode_does_not_prompt_for_background_shell() { Event::ApprovalRequired { .. } => { panic!("YOLO mode must not prompt for a background shell command"); } - 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("Background task started"), - "expected a background start, got: {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("Background task started"), + "expected a background start, got: {result:?}" + ); } Event::TurnComplete { status, .. } => { assert_eq!(status, TurnOutcomeStatus::Completed); diff --git a/crates/tui/src/runtime_api/tests.rs b/crates/tui/src/runtime_api/tests.rs index 391e7d930f..e873601508 100644 --- a/crates/tui/src/runtime_api/tests.rs +++ b/crates/tui/src/runtime_api/tests.rs @@ -527,8 +527,10 @@ async fn spawn_test_server_with_root_token_mobile_workspace_subagents_and_config let _ = rustls::crypto::ring::default_provider().install_default(); fs::create_dir_all(&sessions_dir)?; fs::create_dir_all(&workspace)?; - let mut config = Config::default(); - config.mcp_config_path = Some(root.join("mcp.json").to_string_lossy().to_string()); + let config = Config { + mcp_config_path: Some(root.join("mcp.json").to_string_lossy().to_string()), + ..Default::default() + }; let manager = TaskManager::start_with_executor( TaskManagerConfig { data_dir: root.join("tasks"), @@ -3732,6 +3734,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"); @@ -3761,6 +3766,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/history.rs b/crates/tui/src/tui/history.rs index dfbcb67b2e..0d10fcab23 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 cf1b6a59b5..6e7dcb2ec8 100644 --- a/crates/tui/src/tui/provider_picker.rs +++ b/crates/tui/src/tui/provider_picker.rs @@ -1949,6 +1949,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); diff --git a/crates/tui/src/working_set.rs b/crates/tui/src/working_set.rs index 75e38836d0..bc1a200c2d 100644 --- a/crates/tui/src/working_set.rs +++ b/crates/tui/src/working_set.rs @@ -1679,8 +1679,8 @@ mod tests { let tmp = TempDir::new().expect("tempdir"); let src = tmp.path().join("src"); fs::create_dir_all(&src).expect("mkdir"); - fs::write(src.join("a.rs"), &"a".repeat(200)).expect("write"); - fs::write(src.join("b.rs"), &"b".repeat(200)).expect("write"); + fs::write(src.join("a.rs"), "a".repeat(200)).expect("write"); + fs::write(src.join("b.rs"), "b".repeat(200)).expect("write"); let mut ws = cache_maximal_ws(); ws.config.max_resident_file_bytes = 200;