diff --git a/src/apps/cli/src/modes/chat.rs b/src/apps/cli/src/modes/chat.rs index 6b974e5b..cd20e4f0 100644 --- a/src/apps/cli/src/modes/chat.rs +++ b/src/apps/cli/src/modes/chat.rs @@ -248,6 +248,7 @@ impl ChatMode { Ok(exit_reason) } + #[allow(clippy::too_many_arguments)] fn handle_key_event( &self, key: KeyEvent, diff --git a/src/apps/cli/src/session.rs b/src/apps/cli/src/session.rs index eb170fc4..9c72b898 100644 --- a/src/apps/cli/src/session.rs +++ b/src/apps/cli/src/session.rs @@ -167,20 +167,13 @@ impl Session { /// Add or update text flow of the last message pub fn update_last_message_text_flow(&mut self, content: String, is_streaming: bool) { if let Some(last_message) = self.messages.last_mut() { - if let Some(last_item) = last_message.flow_items.last_mut() { - if let FlowItem::Text { - content: ref mut c, - is_streaming: ref mut s, - } = last_item - { - *c = content.clone(); - *s = is_streaming; - } else { - last_message.flow_items.push(FlowItem::Text { - content: content.clone(), - is_streaming, - }); - } + if let Some(FlowItem::Text { + content: ref mut c, + is_streaming: ref mut s, + }) = last_message.flow_items.last_mut() + { + *c = content.clone(); + *s = is_streaming; } else { last_message.flow_items.push(FlowItem::Text { content: content.clone(), diff --git a/src/apps/cli/src/ui/chat.rs b/src/apps/cli/src/ui/chat.rs index fab52e24..fa2f1bc2 100644 --- a/src/apps/cli/src/ui/chat.rs +++ b/src/apps/cli/src/ui/chat.rs @@ -131,7 +131,7 @@ impl ChatView { fn render_messages(&mut self, frame: &mut Frame, area: Rect) { let title = if self.browse_mode { - format!(" Conversation [Browse Mode ↕] ") + " Conversation [Browse Mode ↕] ".to_string() } else { " Conversation ".to_string() }; diff --git a/src/apps/cli/src/ui/markdown.rs b/src/apps/cli/src/ui/markdown.rs index 7dd99fb8..0a3cce21 100644 --- a/src/apps/cli/src/ui/markdown.rs +++ b/src/apps/cli/src/ui/markdown.rs @@ -242,7 +242,7 @@ impl MarkdownRenderer { } // Remove trailing empty lines - while lines.last().map_or(false, |line| { + while lines.last().is_some_and(|line| { line.spans.is_empty() || (line.spans.len() == 1 && line.spans[0].content.is_empty()) }) { lines.pop(); diff --git a/src/apps/cli/src/ui/startup.rs b/src/apps/cli/src/ui/startup.rs index d1cea763..a679d6ba 100644 --- a/src/apps/cli/src/ui/startup.rs +++ b/src/apps/cli/src/ui/startup.rs @@ -309,14 +309,12 @@ impl StartupPage { lines.push(Line::from("")); if use_fancy_logo { - let logo = vec![ - " ██████╗ ██╗████████╗███████╗██╗ ██╗███╗ ██╗", + let logo = [" ██████╗ ██╗████████╗███████╗██╗ ██╗███╗ ██╗", " ██╔══██╗██║╚══██╔══╝██╔════╝██║ ██║████╗ ██║", " ██████╔╝██║ ██║ █████╗ ██║ ██║██╔██╗ ██║", " ██╔══██╗██║ ██║ ██╔══╝ ██║ ██║██║╚██╗██║", " ██████╔╝██║ ██║ ██║ ╚██████╔╝██║ ╚████║", - " ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝", - ]; + " ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝"]; let colors = [ Color::Rgb(255, 0, 100), @@ -336,13 +334,11 @@ impl StartupPage { ))); } } else { - let logo = vec![ - " ____ _ _ _____ ", + let logo = [" ____ _ _ _____ ", " | __ )(_) |_| ___| _ _ __ ", " | _ \\| | __| |_ | | | | '_ \\ ", " | |_) | | |_| _|| |_| | | | |", - " |____/|_|\\__|_| \\__,_|_| |_|", - ]; + " |____/|_|\\__|_| \\__,_|_| |_|"]; let colors = [ Color::Cyan, @@ -887,7 +883,9 @@ impl StartupPage { PageState::Finished(StartupResult::Exit), ); - let result = match page_state { + + + match page_state { PageState::MainMenu => { self.page_state = PageState::MainMenu; self.handle_main_menu_key(key) @@ -956,9 +954,7 @@ impl StartupPage { self.page_state = PageState::Finished(result); Ok(()) } - }; - - result + } } fn handle_main_menu_key(&mut self, key: KeyEvent) -> Result<()> { @@ -1091,9 +1087,8 @@ impl StartupPage { let path = path.trim(); // Handle paths starting with ~ - if path.starts_with('~') { + if let Some(rest) = path.strip_prefix('~') { if let Some(home) = dirs::home_dir() { - let rest = &path[1..]; return home .join(rest.trim_start_matches('/')) .to_string_lossy() diff --git a/src/apps/cli/src/ui/string_utils.rs b/src/apps/cli/src/ui/string_utils.rs index 25553732..3a2358d4 100644 --- a/src/apps/cli/src/ui/string_utils.rs +++ b/src/apps/cli/src/ui/string_utils.rs @@ -1,4 +1,4 @@ -/// String processing utilities +//! String processing utilities /// Safely truncate string to specified byte length pub fn truncate_str(s: &str, max_bytes: usize) -> String { diff --git a/src/apps/desktop/src/api/commands.rs b/src/apps/desktop/src/api/commands.rs index 2bc322f5..39e0d1a9 100644 --- a/src/apps/desktop/src/api/commands.rs +++ b/src/apps/desktop/src/api/commands.rs @@ -809,9 +809,7 @@ pub async fn open_remote_workspace( let stable_workspace_id = remote_workspace_stable_id(&ssh_host, &remote_path); let display_name = remote_path - .split('/') - .filter(|s| !s.is_empty()) - .last() + .split('/').rfind(|s| !s.is_empty()) .unwrap_or(remote_path.as_str()) .to_string(); @@ -1596,8 +1594,10 @@ pub async fn write_file_content( } let full_path = request.file_path; - let mut options = FileOperationOptions::default(); - options.backup_on_overwrite = false; + let options = FileOperationOptions { + backup_on_overwrite: false, + ..FileOperationOptions::default() + }; match state .filesystem_service @@ -2000,7 +2000,7 @@ pub async fn reveal_in_explorer(request: RevealInExplorerRequest) -> Result<(), } else { let normalized_path = request.path.replace("/", "\\"); bitfun_core::util::process_manager::create_command("explorer") - .args(&["/select,", &normalized_path]) + .args(["/select,", &normalized_path]) .spawn() .map_err(|e| format!("Failed to open explorer: {}", e))?; } diff --git a/src/apps/desktop/src/api/computer_use_api.rs b/src/apps/desktop/src/api/computer_use_api.rs index 4f674186..71def918 100644 --- a/src/apps/desktop/src/api/computer_use_api.rs +++ b/src/apps/desktop/src/api/computer_use_api.rs @@ -83,7 +83,7 @@ pub async fn computer_use_open_system_settings( #[cfg(target_os = "windows")] { let _ = request; - return Err("Open system settings is not wired for Windows yet.".to_string()); + Err("Open system settings is not wired for Windows yet.".to_string()) } #[cfg(target_os = "linux")] { diff --git a/src/apps/desktop/src/api/config_api.rs b/src/apps/desktop/src/api/config_api.rs index 104f997b..2362bd4f 100644 --- a/src/apps/desktop/src/api/config_api.rs +++ b/src/apps/desktop/src/api/config_api.rs @@ -249,7 +249,7 @@ pub async fn get_mode_configs(_state: State<'_, AppState>) -> Result) -> Result) -> Result Result { - let supported = vec!["zh-CN", "en-US"]; + let supported = ["zh-CN", "en-US"]; if !supported.contains(&request.language.as_str()) { return Err(format!("Unsupported language: {}", request.language)); } diff --git a/src/apps/desktop/src/api/mcp_api.rs b/src/apps/desktop/src/api/mcp_api.rs index d825a02d..b14df51f 100644 --- a/src/apps/desktop/src/api/mcp_api.rs +++ b/src/apps/desktop/src/api/mcp_api.rs @@ -176,11 +176,7 @@ pub async fn get_mcp_servers(state: State<'_, AppState>) -> Result Result<(), String> { } drop(guard); - let mut config = RemoteConnectConfig::default(); - config.mobile_web_dir = detect_mobile_web_dir(); + let config = RemoteConnectConfig { + mobile_web_dir: detect_mobile_web_dir(), + ..RemoteConnectConfig::default() + }; let service = RemoteConnectService::new(config).map_err(|e| format!("init remote connect: {e}"))?; *holder.write().await = Some(service); @@ -478,8 +480,10 @@ pub async fn remote_connect_configure_custom_server(url: String) -> Result<(), S let holder = get_service_holder(); let mut guard = holder.write().await; if guard.is_none() { - let mut config = RemoteConnectConfig::default(); - config.custom_server_url = Some(url); + let config = RemoteConnectConfig { + custom_server_url: Some(url), + ..RemoteConnectConfig::default() + }; let service = RemoteConnectService::new(config).map_err(|e| format!("init: {e}"))?; *guard = Some(service); } @@ -530,13 +534,23 @@ pub async fn remote_connect_configure_bot(request: ConfigureBotRequest) -> Resul }; if guard.is_none() { - let mut config = RemoteConnectConfig::default(); - config.mobile_web_dir = detect_mobile_web_dir(); - match &bot_config { - BotConfig::Feishu { .. } => config.bot_feishu = Some(bot_config), - BotConfig::Telegram { .. } => config.bot_telegram = Some(bot_config), - BotConfig::Weixin { .. } => config.bot_weixin = Some(bot_config), - } + let config = match bot_config { + BotConfig::Feishu { .. } => RemoteConnectConfig { + mobile_web_dir: detect_mobile_web_dir(), + bot_feishu: Some(bot_config), + ..RemoteConnectConfig::default() + }, + BotConfig::Telegram { .. } => RemoteConnectConfig { + mobile_web_dir: detect_mobile_web_dir(), + bot_telegram: Some(bot_config), + ..RemoteConnectConfig::default() + }, + BotConfig::Weixin { .. } => RemoteConnectConfig { + mobile_web_dir: detect_mobile_web_dir(), + bot_weixin: Some(bot_config), + ..RemoteConnectConfig::default() + }, + }; let service = RemoteConnectService::new(config).map_err(|e| format!("init: {e}"))?; *guard = Some(service); } else if let Some(service) = guard.as_mut() { diff --git a/src/apps/desktop/src/api/skill_api.rs b/src/apps/desktop/src/api/skill_api.rs index 47af3005..b8dc8c58 100644 --- a/src/apps/desktop/src/api/skill_api.rs +++ b/src/apps/desktop/src/api/skill_api.rs @@ -890,18 +890,14 @@ async fn fill_market_descriptions(client: &Client, base_url: &str, items: &mut [ }); if join_set.len() >= MARKET_DESC_FETCH_CONCURRENCY { - if let Some(result) = join_set.join_next().await { - if let Ok((skill_id, Some(desc))) = result { - fetched.insert(skill_id, desc); - } + if let Some(Ok((skill_id, Some(desc)))) = join_set.join_next().await { + fetched.insert(skill_id, desc); } } } - while let Some(result) = join_set.join_next().await { - if let Ok((skill_id, Some(desc))) = result { - fetched.insert(skill_id, desc); - } + while let Some(Ok((skill_id, Some(desc)))) = join_set.join_next().await { + fetched.insert(skill_id, desc); } if fetched.is_empty() { diff --git a/src/apps/desktop/src/api/snapshot_service.rs b/src/apps/desktop/src/api/snapshot_service.rs index 3eeccca0..4d4ad4a2 100644 --- a/src/apps/desktop/src/api/snapshot_service.rs +++ b/src/apps/desktop/src/api/snapshot_service.rs @@ -890,7 +890,7 @@ pub async fn get_file_change_history( .await .map_err(|e| format!("Failed to get file change history: {}", e))?; - Ok(serde_json::to_value(changes).map_err(|e| format!("Serialization failed: {}", e))?) + serde_json::to_value(changes).map_err(|e| format!("Serialization failed: {}", e)) } #[tauri::command] diff --git a/src/apps/desktop/src/api/storage_commands.rs b/src/apps/desktop/src/api/storage_commands.rs index 8fcef076..550e83cd 100644 --- a/src/apps/desktop/src/api/storage_commands.rs +++ b/src/apps/desktop/src/api/storage_commands.rs @@ -81,7 +81,7 @@ pub async fn cleanup_storage(state: State<'_, AppState>) -> Result Result<(), String> { return Err("Name cannot be empty".to_string()); } let mut chars = name.chars(); - if !chars.next().map_or(false, |c| c.is_ascii_alphabetic()) { + if !chars.next().is_some_and(|c| c.is_ascii_alphabetic()) { return Err("Name must start with a letter".to_string()); } for c in chars { @@ -214,11 +214,10 @@ pub async fn create_subagent( validate_agent_name(name)?; let workspace = workspace_root_from_request(request.workspace_path.as_deref()); - if request.level == SubagentLevel::Project { - if workspace.is_none() { + if request.level == SubagentLevel::Project + && workspace.is_none() { return Err("Project-level Agent requires opening a workspace first".to_string()); } - } let modes = state.agent_registry.get_modes_info().await; let subagents = state diff --git a/src/apps/desktop/src/api/system_api.rs b/src/apps/desktop/src/api/system_api.rs index df82f43c..97584001 100644 --- a/src/apps/desktop/src/api/system_api.rs +++ b/src/apps/desktop/src/api/system_api.rs @@ -102,7 +102,7 @@ pub async fn run_system_command( .env .map(|vars| vars.into_iter().map(|v| (v.key, v.value)).collect()); - let env_ref: Option<&[(String, String)]> = env_vars.as_ref().map(|v| v.as_slice()); + let env_ref: Option<&[(String, String)]> = env_vars.as_deref(); let result = system::run_command( &request.command, diff --git a/src/apps/desktop/src/api/terminal_api.rs b/src/apps/desktop/src/api/terminal_api.rs index bc60a781..42bb007a 100644 --- a/src/apps/desktop/src/api/terminal_api.rs +++ b/src/apps/desktop/src/api/terminal_api.rs @@ -67,8 +67,8 @@ impl TerminalState { *initialized = true; } - Ok(TerminalApi::from_singleton() - .map_err(|e| format!("Terminal API not initialized: {}", e))?) + TerminalApi::from_singleton() + .map_err(|e| format!("Terminal API not initialized: {}", e)) } /// Get the scripts directory path for shell integration diff --git a/src/apps/desktop/src/computer_use/desktop_host.rs b/src/apps/desktop/src/computer_use/desktop_host.rs index 7f092a20..2313795f 100644 --- a/src/apps/desktop/src/computer_use/desktop_host.rs +++ b/src/apps/desktop/src/computer_use/desktop_host.rs @@ -180,8 +180,8 @@ fn coord_blit_glyph( } let iw = img.width() as i32; let ih = img.height() as i32; - let xmin = metrics.xmin as i32; - let ymin = metrics.ymin as i32; + let xmin = metrics.xmin; + let ymin = metrics.ymin; for row in 0..h { for col in 0..w { let alpha = bitmap[row * w + col] as u32; @@ -313,7 +313,7 @@ fn draw_som_labels( // Draw label text centered in badge let text_x = bx0 + SOM_PAD_X; - let baseline_y = by0 + SOM_PAD_Y + text_h - (m_rep.ymin.max(0) as i32); + let baseline_y = by0 + SOM_PAD_Y + text_h - m_rep.ymin.max(0); coord_draw_text_h(frame, text_x, baseline_y, &label_text, SOM_FG, SOM_LABEL_PX); } } @@ -809,6 +809,12 @@ impl std::fmt::Debug for DesktopComputerUseHost { } } +impl Default for DesktopComputerUseHost { + fn default() -> Self { + Self::new() + } +} + impl DesktopComputerUseHost { pub fn new() -> Self { Self { @@ -830,7 +836,7 @@ impl DesktopComputerUseHost { } #[cfg(target_os = "windows")] { - return Self::session_snapshot_windows(); + Self::session_snapshot_windows() } #[cfg(target_os = "linux")] { @@ -1360,6 +1366,7 @@ end tell"#]) /// Pointer position in **scaled image** pixels, if it lies inside the captured display. #[cfg(not(target_os = "macos"))] + #[allow(clippy::too_many_arguments)] fn pointer_in_scaled_image( origin_x: i32, origin_y: i32, @@ -2158,7 +2165,7 @@ impl ComputerUseHost for DesktopComputerUseHost { .map_err(|e| BitFunError::tool(format!("lock: {}", e)))?; s.screenshot_cache = Some(ScreenshotCacheEntry { rgba: rgba.clone(), - screen: screen.clone(), + screen, capture_time: Instant::now(), }); } diff --git a/src/apps/desktop/src/computer_use/screen_ocr.rs b/src/apps/desktop/src/computer_use/screen_ocr.rs index caf1872c..09de6ea5 100644 --- a/src/apps/desktop/src/computer_use/screen_ocr.rs +++ b/src/apps/desktop/src/computer_use/screen_ocr.rs @@ -53,10 +53,10 @@ pub fn find_text_matches( /// If unset or non-zero: write the exact JPEG passed to OCR into `computer_use_debug` under the app data dir (see implementation). Set `BITFUN_COMPUTER_USE_OCR_DEBUG=0` to disable. fn ocr_debug_save_enabled() -> bool { - match std::env::var("BITFUN_COMPUTER_USE_OCR_DEBUG") { - Ok(v) if v == "0" || v.eq_ignore_ascii_case("false") => false, - _ => true, - } + !matches!( + std::env::var("BITFUN_COMPUTER_USE_OCR_DEBUG"), + Ok(v) if v == "0" || v.eq_ignore_ascii_case("false") + ) } /// Same directory as agent `screenshot` debug (`workspace/.bitfun/computer_use_debug`), when PathManager is available. @@ -164,13 +164,11 @@ fn levenshtein_chars(a: &str, b: &str) -> usize { } let mut prev: Vec = (0..=m).collect(); let mut curr = vec![0usize; m + 1]; - for i in 0..n { + for (i, a_ch) in a.iter().enumerate().take(n) { curr[0] = i + 1; for j in 0..m { - let cost = usize::from(a[i] != b[j]); - curr[j + 1] = (prev[j] + cost) - .min(prev[j + 1] + 1) - .min(curr[j] + 1); + let cost = usize::from(*a_ch != b[j]); + curr[j + 1] = (prev[j] + cost).min(prev[j + 1] + 1).min(curr[j] + 1); } std::mem::swap(&mut prev, &mut curr); } @@ -182,7 +180,7 @@ fn fuzzy_max_distance(query_len_chars: usize) -> usize { match query_len_chars { 0 => 0, 1 => 0, - 2 | 3 | 4 => 1, + 2..=4 => 1, 5..=8 => 2, _ => 3, } @@ -230,11 +228,7 @@ fn rank_matches(mut matches: Vec, query: &str) -> Vec std::cmp::Ordering { +fn compare_match(a: &OcrTextMatch, b: &OcrTextMatch, normalized_query: &str) -> std::cmp::Ordering { let sa = match_score(a, normalized_query); let sb = match_score(b, normalized_query); sb.cmp(&sa) @@ -398,7 +392,9 @@ pub fn crop_shot_to_ocr_region( let crop_w = px1 - px0; let crop_h = py1 - py0; if px0.saturating_add(crop_w) > img_w || py0.saturating_add(crop_h) > img_h { - return Err(BitFunError::tool("OCR crop rectangle is out of image bounds.".to_string())); + return Err(BitFunError::tool( + "OCR crop rectangle is out of image bounds.".to_string(), + )); } let cropped_view = image::imageops::crop_imm(&rgb, px0, py0, crop_w, crop_h); let cropped = cropped_view.to_image(); @@ -406,14 +402,13 @@ pub fn crop_shot_to_ocr_region( const OCR_CROP_JPEG_QUALITY: u8 = 75; let mut buf = Vec::new(); let mut enc = JpegEncoder::new_with_quality(&mut buf, OCR_CROP_JPEG_QUALITY); - enc - .encode( - cropped.as_raw(), - cropped.width(), - cropped.height(), - image::ColorType::Rgb8, - ) - .map_err(|e| BitFunError::tool(format!("OCR crop: encode JPEG: {}", e)))?; + enc.encode( + cropped.as_raw(), + cropped.width(), + cropped.height(), + image::ColorType::Rgb8, + ) + .map_err(|e| BitFunError::tool(format!("OCR crop: encode JPEG: {}", e)))?; // Affine mapping must match `image_box_to_global_match`: global = origin + (jpeg_px - content_left) / content_w * native_capture. // Do **not** use ix0/ix1 (intersection clip) as display_origin/native size — they disagree with floor/ceil JPEG bounds px0..px1. @@ -475,9 +470,9 @@ mod macos { use objc2::AnyThread; use objc2_foundation::{NSArray, NSData, NSDictionary, NSError, NSString}; use objc2_vision::{ - VNImageOption, VNImageRectForNormalizedRect, VNImageRequestHandler, - VNRecognizeTextRequest, VNRecognizeTextRequestRevision3, VNRecognizedTextObservation, - VNRequest, VNRequestTextRecognitionLevel, + VNImageOption, VNImageRectForNormalizedRect, VNImageRequestHandler, VNRecognizeTextRequest, + VNRecognizeTextRequestRevision3, VNRecognizedTextObservation, VNRequest, + VNRequestTextRecognitionLevel, }; /// Top-N candidates per observation; Chinese matches often appear below rank 1. @@ -697,12 +692,8 @@ mod windows_backend { // Windows.Media.Ocr requires STA thread let mut co_init = None; if unsafe { CoIncrementMTAUsage() }.is_err() { - let hr = unsafe { - CoInitializeEx( - None, - COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE, - ) - }; + let hr = + unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) }; if hr.is_err() { return Err(BitFunError::tool(format!( "Windows OCR COM initialization failed: {:?}", @@ -730,9 +721,9 @@ mod windows_backend { Ok(e) => e, Err(_) => { // Fallback to English if user profile languages fail - let lang = w(windows::Globalization::Language::CreateLanguage(&HSTRING::from( - "en-US", - )))?; + let lang = w(windows::Globalization::Language::CreateLanguage( + &HSTRING::from("en-US"), + ))?; if !w(OcrEngine::IsLanguageSupported(&lang))? { return Err(BitFunError::tool( "Windows OCR: No supported language packs installed.".to_string(), @@ -769,8 +760,7 @@ mod windows_backend { if ranked.is_empty() { return Err(BitFunError::tool(format!( "No OCR text matched {:?} on screen (Windows OCR found {} text regions total).", - text_query, - line_count + text_query, line_count ))); } Ok(ranked) @@ -810,13 +800,7 @@ mod windows_backend { let height = f64::from(rect.Height); Some(image_box_to_global_match( - shot, - text, - 0.8, - local_left, - local_top, - width, - height, + shot, text, 0.8, local_left, local_top, width, height, )) } } @@ -889,9 +873,7 @@ mod linux_backend { let boxa = api .get_component_images(TessPageIteratorLevel_RIL_WORD, true) .ok_or_else(|| { - BitFunError::tool( - "Linux OCR: Tesseract did not return word regions.".to_string(), - ) + BitFunError::tool("Linux OCR: Tesseract did not return word regions.".to_string()) })?; let word_region_count = boxa.get_n(); @@ -945,7 +927,10 @@ mod linux_backend { text_query: &str, text: &str, confidence: f32, - x1: i32, y1: i32, x2: i32, y2: i32, + x1: i32, + y1: i32, + x2: i32, + y2: i32, content_left: u32, content_top: u32, _content_width: u32, diff --git a/src/apps/desktop/src/computer_use/ui_locate_common.rs b/src/apps/desktop/src/computer_use/ui_locate_common.rs index af8dbdec..9c954789 100644 --- a/src/apps/desktop/src/computer_use/ui_locate_common.rs +++ b/src/apps/desktop/src/computer_use/ui_locate_common.rs @@ -5,8 +5,16 @@ use bitfun_core::util::errors::{BitFunError, BitFunResult}; use screenshots::display_info::DisplayInfo; pub fn validate_query(q: &UiElementLocateQuery) -> BitFunResult<()> { - let t = q.title_contains.as_ref().map(|s| !s.trim().is_empty()).unwrap_or(false); - let r = q.role_substring.as_ref().map(|s| !s.trim().is_empty()).unwrap_or(false); + let t = q + .title_contains + .as_ref() + .map(|s| !s.trim().is_empty()) + .unwrap_or(false); + let r = q + .role_substring + .as_ref() + .map(|s| !s.trim().is_empty()) + .unwrap_or(false); let i = q .identifier_contains .as_ref() @@ -106,10 +114,7 @@ pub fn role_substring_matches_ax_role(ax_role: &str, want: &str) -> bool { } fn combine_is_any(query: &UiElementLocateQuery) -> bool { - matches!( - query.filter_combine.as_deref(), - Some("any") | Some("or") - ) + matches!(query.filter_combine.as_deref(), Some("any") | Some("or")) } /// OR semantics: element matches if **at least one** non-empty filter matches. @@ -196,6 +201,7 @@ pub fn matches_filters( } #[allow(dead_code)] // Used by windows_ax_ui / linux_ax_ui (not compiled on macOS) +#[allow(clippy::too_many_arguments)] pub fn ok_result( gx: f64, gy: f64, @@ -208,12 +214,22 @@ pub fn ok_result( matched_identifier: Option, ) -> BitFunResult { ok_result_with_context( - gx, gy, bounds_left, bounds_top, bounds_width, bounds_height, - matched_role, matched_title, matched_identifier, - None, 1, vec![], + gx, + gy, + bounds_left, + bounds_top, + bounds_width, + bounds_height, + matched_role, + matched_title, + matched_identifier, + None, + 1, + vec![], ) } +#[allow(clippy::too_many_arguments)] pub fn ok_result_with_context( gx: f64, gy: f64, @@ -230,7 +246,14 @@ pub fn ok_result_with_context( ) -> BitFunResult { let (nx, ny) = global_to_native_center(gx, gy)?; let (nminx, nminy, nmaxx, nmaxy) = if bounds_width > 0.0 && bounds_height > 0.0 { - global_bounds_to_native_minmax(gx, gy, bounds_left, bounds_top, bounds_width, bounds_height)? + global_bounds_to_native_minmax( + gx, + gy, + bounds_left, + bounds_top, + bounds_width, + bounds_height, + )? } else { (nx, ny, nx, ny) }; diff --git a/src/apps/relay-server/src/relay/room.rs b/src/apps/relay-server/src/relay/room.rs index 592b3299..7ed8d1e4 100644 --- a/src/apps/relay-server/src/relay/room.rs +++ b/src/apps/relay-server/src/relay/room.rs @@ -1,4 +1,4 @@ -//! Room management for the relay server. +//! Room management for the relay server. //! //! Each room holds a single desktop participant connected via WebSocket. //! Mobile clients interact through HTTP requests that the relay bridges @@ -181,7 +181,7 @@ impl RoomManager { if room .desktop .as_ref() - .map_or(false, |d| d.conn_id == conn_id) + .is_some_and(|d| d.conn_id == conn_id) { info!("Desktop disconnected from room {room_id}"); room.desktop = None; @@ -203,7 +203,7 @@ impl RoomManager { let is_match = room .desktop .as_ref() - .map_or(false, |d| d.conn_id == conn_id); + .is_some_and(|d| d.conn_id == conn_id); if is_match { let now = Utc::now().timestamp(); room.last_activity = now; @@ -245,7 +245,7 @@ impl RoomManager { pub fn has_desktop(&self, room_id: &str) -> bool { self.rooms .get(room_id) - .map_or(false, |r| r.desktop.is_some()) + .is_some_and(|r| r.desktop.is_some()) } pub fn room_count(&self) -> usize { diff --git a/src/apps/relay-server/src/routes/websocket.rs b/src/apps/relay-server/src/routes/websocket.rs index 3e456121..0843160e 100644 --- a/src/apps/relay-server/src/routes/websocket.rs +++ b/src/apps/relay-server/src/routes/websocket.rs @@ -1,4 +1,4 @@ -//! WebSocket handler for the relay server. +//! WebSocket handler for the relay server. //! //! Only desktop clients connect via WebSocket. Mobile clients use HTTP. //! The relay bridges HTTP requests to the desktop via WebSocket using @@ -82,11 +82,10 @@ async fn handle_socket(socket: WebSocket, state: AppState) { let write_task = tokio::spawn(async move { while let Some(msg) = out_rx.recv().await { - if !msg.text.is_empty() { - if ws_sender.send(Message::Text(msg.text)).await.is_err() { + if !msg.text.is_empty() + && ws_sender.send(Message::Text(msg.text)).await.is_err() { break; } - } } }); diff --git a/src/crates/core/src/agentic/agents/agentic_mode.rs b/src/crates/core/src/agentic/agents/agentic_mode.rs index 25b6f0be..1713afc6 100644 --- a/src/crates/core/src/agentic/agents/agentic_mode.rs +++ b/src/crates/core/src/agentic/agents/agentic_mode.rs @@ -6,6 +6,12 @@ pub struct AgenticMode { default_tools: Vec, } +impl Default for AgenticMode { + fn default() -> Self { + Self::new() + } +} + impl AgenticMode { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/agentic/agents/claw_mode.rs b/src/crates/core/src/agentic/agents/claw_mode.rs index c50dd177..087a1963 100644 --- a/src/crates/core/src/agentic/agents/claw_mode.rs +++ b/src/crates/core/src/agentic/agents/claw_mode.rs @@ -6,6 +6,12 @@ pub struct ClawMode { default_tools: Vec, } +impl Default for ClawMode { + fn default() -> Self { + Self::new() + } +} + impl ClawMode { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/agentic/agents/cowork_mode.rs b/src/crates/core/src/agentic/agents/cowork_mode.rs index 2ccd3269..5a3c222f 100644 --- a/src/crates/core/src/agentic/agents/cowork_mode.rs +++ b/src/crates/core/src/agentic/agents/cowork_mode.rs @@ -9,6 +9,12 @@ pub struct CoworkMode { default_tools: Vec, } +impl Default for CoworkMode { + fn default() -> Self { + Self::new() + } +} + impl CoworkMode { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/agentic/agents/custom_subagents/custom_subagent.rs b/src/crates/core/src/agentic/agents/custom_subagents/custom_subagent.rs index 858bb9ae..88e4dbf7 100644 --- a/src/crates/core/src/agentic/agents/custom_subagents/custom_subagent.rs +++ b/src/crates/core/src/agentic/agents/custom_subagents/custom_subagent.rs @@ -197,6 +197,6 @@ impl CustomSubagent { } let metadata = Value::Mapping(metadata); FrontMatterMarkdown::save(&self.path, &metadata, &self.prompt) - .map_err(|e| BitFunError::Agent(e)) + .map_err(BitFunError::Agent) } } diff --git a/src/crates/core/src/agentic/agents/custom_subagents/custom_subagent_loader.rs b/src/crates/core/src/agentic/agents/custom_subagents/custom_subagent_loader.rs index 0b2929df..508876ff 100644 --- a/src/crates/core/src/agentic/agents/custom_subagents/custom_subagent_loader.rs +++ b/src/crates/core/src/agentic/agents/custom_subagents/custom_subagent_loader.rs @@ -103,7 +103,7 @@ impl CustomSubagentLoader { }; for e in rd.flatten() { let p = e.path(); - if p.is_file() && p.extension().map_or(false, |ext| ext == "md") { + if p.is_file() && p.extension().is_some_and(|ext| ext == "md") { out.push(p); } } diff --git a/src/crates/core/src/agentic/agents/debug_mode.rs b/src/crates/core/src/agentic/agents/debug_mode.rs index 9c460479..24f3fc7e 100644 --- a/src/crates/core/src/agentic/agents/debug_mode.rs +++ b/src/crates/core/src/agentic/agents/debug_mode.rs @@ -14,6 +14,12 @@ pub struct DebugMode; include!(concat!(env!("OUT_DIR"), "/embedded_agents_prompt.rs")); +impl Default for DebugMode { + fn default() -> Self { + Self::new() + } +} + impl DebugMode { pub fn new() -> Self { Self @@ -147,9 +153,9 @@ impl DebugMode { let mut section = format!("## {} Instrumentation\n\n", template.display_name); section.push_str("```"); section.push_str(lang_hint); - section.push_str("\n"); + section.push('\n'); section.push_str(&template.region_start); - section.push_str("\n"); + section.push('\n'); section.push_str( &template .instrumentation_template @@ -159,7 +165,7 @@ impl DebugMode { .replace("{HYPOTHESIS_ID}", "X") .replace("{RUN_ID}", "pre-fix"), ); - section.push_str("\n"); + section.push('\n'); section.push_str(&template.region_end); section.push_str("\n```\n\n"); @@ -168,7 +174,7 @@ impl DebugMode { for note in &template.notes { section.push_str(&format!("- {}\n", note)); } - section.push_str("\n"); + section.push('\n'); } section diff --git a/src/crates/core/src/agentic/agents/explore_agent.rs b/src/crates/core/src/agentic/agents/explore_agent.rs index bd1bc7c1..42899ffe 100644 --- a/src/crates/core/src/agentic/agents/explore_agent.rs +++ b/src/crates/core/src/agentic/agents/explore_agent.rs @@ -4,6 +4,12 @@ pub struct ExploreAgent { default_tools: Vec, } +impl Default for ExploreAgent { + fn default() -> Self { + Self::new() + } +} + impl ExploreAgent { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/agentic/agents/file_finder_agent.rs b/src/crates/core/src/agentic/agents/file_finder_agent.rs index 8f56ea98..c44d8e14 100644 --- a/src/crates/core/src/agentic/agents/file_finder_agent.rs +++ b/src/crates/core/src/agentic/agents/file_finder_agent.rs @@ -5,6 +5,12 @@ pub struct FileFinderAgent { default_tools: Vec, } +impl Default for FileFinderAgent { + fn default() -> Self { + Self::new() + } +} + impl FileFinderAgent { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/agentic/agents/generate_doc_agent.rs b/src/crates/core/src/agentic/agents/generate_doc_agent.rs index c1af554a..c8c97a70 100644 --- a/src/crates/core/src/agentic/agents/generate_doc_agent.rs +++ b/src/crates/core/src/agentic/agents/generate_doc_agent.rs @@ -5,6 +5,12 @@ pub struct GenerateDocAgent { default_tools: Vec, } +impl Default for GenerateDocAgent { + fn default() -> Self { + Self::new() + } +} + impl GenerateDocAgent { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/agentic/agents/init_agent.rs b/src/crates/core/src/agentic/agents/init_agent.rs index 5e95c7a4..a34d107e 100644 --- a/src/crates/core/src/agentic/agents/init_agent.rs +++ b/src/crates/core/src/agentic/agents/init_agent.rs @@ -5,6 +5,12 @@ pub struct InitAgent { default_tools: Vec, } +impl Default for InitAgent { + fn default() -> Self { + Self::new() + } +} + impl InitAgent { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/agentic/agents/plan_mode.rs b/src/crates/core/src/agentic/agents/plan_mode.rs index dc77b8d2..c6d19bc6 100644 --- a/src/crates/core/src/agentic/agents/plan_mode.rs +++ b/src/crates/core/src/agentic/agents/plan_mode.rs @@ -6,6 +6,12 @@ pub struct PlanMode { default_tools: Vec, } +impl Default for PlanMode { + fn default() -> Self { + Self::new() + } +} + impl PlanMode { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/agentic/agents/prompt_builder/mod.rs b/src/crates/core/src/agentic/agents/prompt_builder/mod.rs index affb34c8..6d45db5c 100644 --- a/src/crates/core/src/agentic/agents/prompt_builder/mod.rs +++ b/src/crates/core/src/agentic/agents/prompt_builder/mod.rs @@ -1,3 +1,3 @@ -mod prompt_builder; +mod prompt_builder_impl; -pub use prompt_builder::{PromptBuilder, PromptBuilderContext, RemoteExecutionHints}; +pub use prompt_builder_impl::{PromptBuilder, PromptBuilderContext, RemoteExecutionHints}; diff --git a/src/crates/core/src/agentic/agents/prompt_builder/prompt_builder.rs b/src/crates/core/src/agentic/agents/prompt_builder/prompt_builder_impl.rs similarity index 100% rename from src/crates/core/src/agentic/agents/prompt_builder/prompt_builder.rs rename to src/crates/core/src/agentic/agents/prompt_builder/prompt_builder_impl.rs diff --git a/src/crates/core/src/agentic/agents/registry.rs b/src/crates/core/src/agentic/agents/registry.rs index d8466a3c..4c22c80a 100644 --- a/src/crates/core/src/agentic/agents/registry.rs +++ b/src/crates/core/src/agentic/agents/registry.rs @@ -196,6 +196,12 @@ pub struct AgentRegistry { project_subagents: RwLock>>, } +impl Default for AgentRegistry { + fn default() -> Self { + Self::new() + } +} + impl AgentRegistry { fn read_agents(&self) -> std::sync::RwLockReadGuard<'_, HashMap> { match self.agents.read() { @@ -380,14 +386,14 @@ impl AgentRegistry { /// check if a subagent exists with specified source (used for duplicate check before adding) pub fn has_subagent(&self, agent_id: &str, source: SubAgentSource) -> bool { - if self.read_agents().get(agent_id).map_or(false, |e| { + if self.read_agents().get(agent_id).is_some_and(|e| { e.category == AgentCategory::SubAgent && e.subagent_source == Some(source) }) { return true; } self.read_project_subagents().values().any(|entries| { - entries.get(agent_id).map_or(false, |entry| { + entries.get(agent_id).is_some_and(|entry| { entry.category == AgentCategory::SubAgent && entry.subagent_source == Some(source) }) }) @@ -517,7 +523,7 @@ impl AgentRegistry { result.extend( project_entries .values() - .map(|entry| AgentInfo::from_agent_entry(entry)), + .map(AgentInfo::from_agent_entry), ); } } diff --git a/src/crates/core/src/agentic/coordination/coordinator.rs b/src/crates/core/src/agentic/coordination/coordinator.rs index bdea500d..9e24cddc 100644 --- a/src/crates/core/src/agentic/coordination/coordinator.rs +++ b/src/crates/core/src/agentic/coordination/coordinator.rs @@ -856,6 +856,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet /// Note: Events are sent to frontend via EventLoop, no Stream returned. /// Submission behavior is controlled by `submission_policy`, which provides /// default per-source behavior while still allowing selective overrides. + #[allow(clippy::too_many_arguments)] pub async fn start_dialog_turn( &self, session_id: String, @@ -881,6 +882,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet .await } + #[allow(clippy::too_many_arguments)] pub async fn start_dialog_turn_with_image_contexts( &self, session_id: String, @@ -940,7 +942,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet let needs_restore = if context_messages.is_empty() { true } else { - context_messages.len() == 1 && session.dialog_turn_ids.len() > 0 + context_messages.len() == 1 && !session.dialog_turn_ids.is_empty() }; if needs_restore { @@ -1065,6 +1067,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet } } + #[allow(clippy::too_many_arguments)] async fn start_dialog_turn_internal( &self, session_id: String, @@ -1190,7 +1193,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet session_id ); true - } else if context_messages.len() == 1 && session.dialog_turn_ids.len() > 0 { + } else if context_messages.len() == 1 && !session.dialog_turn_ids.is_empty() { debug!( "Session {} has {} turns but only {} messages, restoring history", session_id, @@ -1879,8 +1882,10 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet "workspace_path is required when creating a subagent session".to_string(), ) })?; - let mut subagent_config = SessionConfig::default(); - subagent_config.workspace_path = Some(workspace_path); + let subagent_config = SessionConfig { + workspace_path: Some(workspace_path), + ..SessionConfig::default() + }; let session = self .create_subagent_session( format!("Subagent: {}", task_description), @@ -2140,6 +2145,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet } /// Persist a completed `/btw` side-question turn into an existing child session. + #[allow(clippy::too_many_arguments)] pub async fn persist_btw_turn( &self, workspace_path: &Path, diff --git a/src/crates/core/src/agentic/coordination/scheduler.rs b/src/crates/core/src/agentic/coordination/scheduler.rs index 208bfeec..faf3f2da 100644 --- a/src/crates/core/src/agentic/coordination/scheduler.rs +++ b/src/crates/core/src/agentic/coordination/scheduler.rs @@ -214,6 +214,7 @@ impl DialogScheduler { /// - Session error → queue cleared, dispatched immediately. /// /// Returns `Err(String)` if the queue is full or the coordinator returns an error. + #[allow(clippy::too_many_arguments)] pub async fn submit( &self, session_id: String, diff --git a/src/crates/core/src/agentic/core/message.rs b/src/crates/core/src/agentic/core/message.rs index acd8263b..66d328c6 100644 --- a/src/crates/core/src/agentic/core/message.rs +++ b/src/crates/core/src/agentic/core/message.rs @@ -4,6 +4,7 @@ use crate::util::types::{Message as AIMessage, ToolCall as AIToolCall, ToolImage use crate::util::TokenCounter; use log::warn; use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; use std::time::SystemTime; use uuid::Uuid; @@ -247,7 +248,7 @@ impl From for AIMessage { // Convert serde_json::Value to HashMap let arguments = if let serde_json::Value::Object(map) = tc.arguments { - map.into_iter().map(|(k, v)| (k, v)).collect() + map.into_iter().collect() } else { std::collections::HashMap::new() }; @@ -495,8 +496,8 @@ impl Message { }) .unwrap_or((1024, 1024)); - let tiles_w = (width + 511) / 512; - let tiles_h = (height + 511) / 512; + let tiles_w = width.div_ceil(512); + let tiles_h = height.div_ceil(512); let tiles = (tiles_w.max(1) * tiles_h.max(1)) as usize; 50 + tiles * 200 } @@ -561,11 +562,12 @@ impl Message { } } -impl ToString for MessageContent { - fn to_string(&self) -> String { +impl Display for MessageContent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - MessageContent::Text(text) => text.clone(), - MessageContent::Multimodal { text, images } => format!( + MessageContent::Text(text) => write!(f, "{}", text), + MessageContent::Multimodal { text, images } => write!( + f, "Multimodal: text_length={}, images={}", text.len(), images.len() @@ -577,36 +579,34 @@ impl ToString for MessageContent { result_for_assistant, is_error, image_attachments, - } => { - format!( - "ToolResult: tool_id={}, tool_name={}, result={}, result_for_assistant={:?}, is_error={}, images={}", - tool_id, - tool_name, - result, - result_for_assistant, - is_error, - image_attachments.as_ref().map(|v| v.len()).unwrap_or(0) - ) - } + } => write!( + f, + "ToolResult: tool_id={}, tool_name={}, result={}, result_for_assistant={:?}, is_error={}, images={}", + tool_id, + tool_name, + result, + result_for_assistant, + is_error, + image_attachments.as_ref().map(|v| v.len()).unwrap_or(0) + ), MessageContent::Mixed { reasoning_content, text, tool_calls, - } => { - format!( - "Mixed: reasoning_content={:?}, text={}, tool_calls={}", - reasoning_content, - text, - tool_calls - .iter() - .map(|tc| format!( - "ToolCall: tool_id={}, tool_name={}, arguments={}", - tc.tool_id, tc.tool_name, tc.arguments - )) - .collect::>() - .join(", ") - ) - } + } => write!( + f, + "Mixed: reasoning_content={:?}, text={}, tool_calls={}", + reasoning_content, + text, + tool_calls + .iter() + .map(|tc| format!( + "ToolCall: tool_id={}, tool_name={}, arguments={}", + tc.tool_id, tc.tool_name, tc.arguments + )) + .collect::>() + .join(", ") + ), } } } diff --git a/src/crates/core/src/agentic/core/messages_helper.rs b/src/crates/core/src/agentic/core/messages_helper.rs index 8e5871d8..281af38e 100644 --- a/src/crates/core/src/agentic/core/messages_helper.rs +++ b/src/crates/core/src/agentic/core/messages_helper.rs @@ -5,7 +5,7 @@ pub struct MessageHelper; impl MessageHelper { pub fn compute_keep_thinking_flags( - messages: &mut Vec, + messages: &mut [Message], enable_thinking: bool, support_preserved_thinking: bool, ) { @@ -69,7 +69,7 @@ impl MessageHelper { } pub fn convert_messages(messages: &[Message]) -> Vec { - messages.iter().map(|m| AIMessage::from(m)).collect() + messages.iter().map(AIMessage::from).collect() } pub fn group_messages_by_turns(mut messages: Vec) -> Vec> { @@ -112,11 +112,9 @@ impl MessageHelper { mid_idx = Some(idx); } - if message.role == MessageRole::Assistant { - if delta < min_delta0 { - min_delta0 = delta; - mid_assistant_msg_idx = Some(idx); - } + if message.role == MessageRole::Assistant && delta < min_delta0 { + min_delta0 = delta; + mid_assistant_msg_idx = Some(idx); } // Delta will only get larger going forward, so can exit early diff --git a/src/crates/core/src/agentic/core/session.rs b/src/crates/core/src/agentic/core/session.rs index 7523d81b..7992e079 100644 --- a/src/crates/core/src/agentic/core/session.rs +++ b/src/crates/core/src/agentic/core/session.rs @@ -57,6 +57,7 @@ pub struct Session { /// Context compression state #[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default)] pub struct CompressionState { /// Time of last compression pub last_compression_at: Option, @@ -64,14 +65,6 @@ pub struct CompressionState { pub compression_count: usize, } -impl Default for CompressionState { - fn default() -> Self { - Self { - last_compression_at: None, - compression_count: 0, - } - } -} impl CompressionState { pub fn increment_compression_count(&mut self) { diff --git a/src/crates/core/src/agentic/execution/execution_engine.rs b/src/crates/core/src/agentic/execution/execution_engine.rs index 3acced74..dd6c5111 100644 --- a/src/crates/core/src/agentic/execution/execution_engine.rs +++ b/src/crates/core/src/agentic/execution/execution_engine.rs @@ -447,6 +447,7 @@ impl ExecutionEngine { } /// Compress context, will emit compression events (Started, Completed, and Failed) + #[allow(clippy::too_many_arguments)] pub async fn compress_messages( &self, session_id: &str, @@ -586,6 +587,7 @@ impl ExecutionEngine { /// Compact the current session context outside the normal dialog execution loop. /// Always emits compression started/completed/failed events for the provided turn. + #[allow(clippy::too_many_arguments)] pub async fn compact_session_context( &self, session_id: &str, diff --git a/src/crates/core/src/agentic/execution/stream_processor.rs b/src/crates/core/src/agentic/execution/stream_processor.rs index 782f40ac..97ea7df3 100644 --- a/src/crates/core/src/agentic/execution/stream_processor.rs +++ b/src/crates/core/src/agentic/execution/stream_processor.rs @@ -26,16 +26,12 @@ use tokio::sync::mpsc; /// SSE log collector configuration #[derive(Debug, Clone)] +#[derive(Default)] pub struct SseLogConfig { /// Maximum number of SSE data entries to output on error, None means unlimited pub max_output: Option, } -impl Default for SseLogConfig { - fn default() -> Self { - Self { max_output: None } - } -} /// SSE log collector - Collects raw SSE data, outputs only on error pub struct SseLogCollector { @@ -466,8 +462,8 @@ impl StreamProcessor { session_id: ctx.session_id.clone(), turn_id: ctx.dialog_turn_id.clone(), tool_event: ToolEventData::EarlyDetected { - tool_id: tool_id, - tool_name: tool_name, + tool_id, + tool_name, }, subagent_parent_info: ctx.event_subagent_parent_info.clone(), }, @@ -618,6 +614,7 @@ impl StreamProcessor { /// * `round_id` - Model round ID /// * `subagent_parent_info` - Subagent parent info /// * `cancellation_token` - Cancellation token + #[allow(clippy::too_many_arguments)] pub async fn process_stream( &self, mut stream: futures::stream::BoxStream<'static, Result>, diff --git a/src/crates/core/src/agentic/image_analysis/enhancer.rs b/src/crates/core/src/agentic/image_analysis/enhancer.rs index 3d24e799..39a86ad3 100644 --- a/src/crates/core/src/agentic/image_analysis/enhancer.rs +++ b/src/crates/core/src/agentic/image_analysis/enhancer.rs @@ -37,7 +37,7 @@ impl MessageEnhancer { if !analysis.detected_elements.is_empty() { enhanced.push_str("• Key elements: "); enhanced.push_str(&analysis.detected_elements.join(", ")); - enhanced.push_str("\n"); + enhanced.push('\n'); } enhanced.push_str(&format!( @@ -45,7 +45,7 @@ impl MessageEnhancer { analysis.confidence * 100.0 )); - enhanced.push_str("\n"); + enhanced.push('\n'); } } @@ -55,10 +55,10 @@ impl MessageEnhancer { for ctx in other_contexts { if let Some(formatted) = Self::format_context(ctx) { enhanced.push_str(&formatted); - enhanced.push_str("\n"); + enhanced.push('\n'); } } - enhanced.push_str("\n"); + enhanced.push('\n'); } enhanced.push_str("The above image analysis has already been performed. Do NOT suggest the user to view or re-analyze the image. Respond directly to the user's question based on the analysis.\n\n"); diff --git a/src/crates/core/src/agentic/insights/collector.rs b/src/crates/core/src/agentic/insights/collector.rs index be698d2b..98404eea 100644 --- a/src/crates/core/src/agentic/insights/collector.rs +++ b/src/crates/core/src/agentic/insights/collector.rs @@ -299,7 +299,7 @@ impl InsightsCollector { if let Some(prev) = last_assistant_time { if let Ok(duration) = msg.timestamp.duration_since(prev) { let secs = duration.as_secs(); - if secs >= 2 && secs <= ACTIVITY_GAP_THRESHOLD_SECS { + if (2..=ACTIVITY_GAP_THRESHOLD_SECS).contains(&secs) { base_stats.response_times_raw.push(secs as f64); } } @@ -564,11 +564,7 @@ fn smart_truncate_parts(parts: &[String], max_chars: usize, tail_reserve: usize) } tail_parts.reverse(); - let omitted = if tail_start_idx > head_end_idx { - tail_start_idx - head_end_idx - } else { - 0 - }; + let omitted = tail_start_idx.saturating_sub(head_end_idx); let mut result = head_parts.join("\n"); if omitted > 0 { @@ -633,7 +629,7 @@ fn compute_response_time_stats(raw: &[f64]) -> (f64, f64) { let mut sorted = raw.to_vec(); sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); - let median = if sorted.len() % 2 == 0 { + let median = if sorted.len().is_multiple_of(2) { let mid = sorted.len() / 2; (sorted[mid - 1] + sorted[mid]) / 2.0 } else { diff --git a/src/crates/core/src/agentic/insights/html.rs b/src/crates/core/src/agentic/insights/html.rs index 9eab26f2..44896a54 100644 --- a/src/crates/core/src/agentic/insights/html.rs +++ b/src/crates/core/src/agentic/insights/html.rs @@ -1085,11 +1085,10 @@ fn find_closing_double_star(chars: &[char], start: usize) -> Option { let len = chars.len(); let mut i = start; while i + 1 < len { - if chars[i] == '*' && chars[i + 1] == '*' { - if i > start { + if chars[i] == '*' && chars[i + 1] == '*' + && i > start { return Some(i); } - } i += 1; } None @@ -1099,13 +1098,11 @@ fn find_closing_single_star(chars: &[char], start: usize) -> Option { let len = chars.len(); let mut i = start; while i < len { - if chars[i] == '*' { - if i + 1 >= len || chars[i + 1] != '*' { - if i > start { + if chars[i] == '*' + && (i + 1 >= len || chars[i + 1] != '*') + && i > start { return Some(i); } - } - } i += 1; } None diff --git a/src/crates/core/src/agentic/insights/service.rs b/src/crates/core/src/agentic/insights/service.rs index ace6c46a..09279282 100644 --- a/src/crates/core/src/agentic/insights/service.rs +++ b/src/crates/core/src/agentic/insights/service.rs @@ -525,7 +525,7 @@ impl InsightsService { suggestions_handle, "Suggestions", || async { Self::generate_suggestions(ai_client, aggregate, lang_instruction).await }, - || default_suggestions(), + default_suggestions, ).await; let areas = Self::resolve_with_retry( @@ -793,28 +793,23 @@ impl InsightsService { .as_array() .map(|arr| { arr.iter() - .filter_map(|v| { - Some(UsagePattern { - pattern: v["pattern"] - .as_str() - .or(v["title"].as_str()) - .unwrap_or("") - .to_string(), - description: v["description"] - .as_str() - .or(v["suggestion"].as_str()) - .unwrap_or("") - .to_string(), - detail: v["detail"] - .as_str() - .unwrap_or("") - .to_string(), - suggested_prompt: v["suggested_prompt"] - .as_str() - .or(v["copyable_prompt"].as_str()) - .unwrap_or("") - .to_string(), - }) + .map(|v| UsagePattern { + pattern: v["pattern"] + .as_str() + .or(v["title"].as_str()) + .unwrap_or("") + .to_string(), + description: v["description"] + .as_str() + .or(v["suggestion"].as_str()) + .unwrap_or("") + .to_string(), + detail: v["detail"].as_str().unwrap_or("").to_string(), + suggested_prompt: v["suggested_prompt"] + .as_str() + .or(v["copyable_prompt"].as_str()) + .unwrap_or("") + .to_string(), }) .collect() }) @@ -1160,6 +1155,7 @@ impl InsightsService { // ============ Stage 5: Assembly ============ + #[allow(clippy::too_many_arguments)] fn assemble_report( _base_stats: BaseStats, aggregate: InsightsAggregate, diff --git a/src/crates/core/src/agentic/persistence/manager.rs b/src/crates/core/src/agentic/persistence/manager.rs index 0e4ced62..7a175170 100644 --- a/src/crates/core/src/agentic/persistence/manager.rs +++ b/src/crates/core/src/agentic/persistence/manager.rs @@ -640,7 +640,7 @@ impl PersistenceManager { custom_metadata: existing.and_then(|value| value.custom_metadata.clone()), todos: existing.and_then(|value| value.todos.clone()), workspace_path: Some(workspace_root), - workspace_hostname: workspace_hostname, + workspace_hostname, } } @@ -1743,7 +1743,7 @@ impl PersistenceManager { metadata.workspace_path = metadata.workspace_path.clone().or_else(|| { turns .first() - .and_then(|_| None::) + .and(None::) .or_else(|| Some(workspace_path.to_string_lossy().to_string())) }); self.save_session_metadata(workspace_path, &metadata).await diff --git a/src/crates/core/src/agentic/session/context_store.rs b/src/crates/core/src/agentic/session/context_store.rs index 9023ccdf..3e486e97 100644 --- a/src/crates/core/src/agentic/session/context_store.rs +++ b/src/crates/core/src/agentic/session/context_store.rs @@ -12,6 +12,12 @@ pub struct SessionContextStore { session_contexts: Arc>>, } +impl Default for SessionContextStore { + fn default() -> Self { + Self::new() + } +} + impl SessionContextStore { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/agentic/session/session_manager.rs b/src/crates/core/src/agentic/session/session_manager.rs index e02c00a7..25ddf159 100644 --- a/src/crates/core/src/agentic/session/session_manager.rs +++ b/src/crates/core/src/agentic/session/session_manager.rs @@ -1010,6 +1010,7 @@ impl SessionManager { // ============ Dialog Turn Management ============ + #[allow(clippy::too_many_arguments)] async fn start_persisted_turn( &self, session_id: &str, @@ -1410,6 +1411,7 @@ impl SessionManager { } /// Persist a completed `/btw` side-question turn into an existing child session. + #[allow(clippy::too_many_arguments)] pub async fn persist_btw_turn( &self, workspace_path: &Path, diff --git a/src/crates/core/src/agentic/side_question.rs b/src/crates/core/src/agentic/side_question.rs index 68c2f733..41293e14 100644 --- a/src/crates/core/src/agentic/side_question.rs +++ b/src/crates/core/src/agentic/side_question.rs @@ -24,6 +24,12 @@ pub struct SideQuestionRuntime { tokens: Arc>>, } +impl Default for SideQuestionRuntime { + fn default() -> Self { + Self::new() + } +} + impl SideQuestionRuntime { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/agentic/tools/implementations/ask_user_question_tool.rs b/src/crates/core/src/agentic/tools/implementations/ask_user_question_tool.rs index b831c08a..2d8d6baf 100644 --- a/src/crates/core/src/agentic/tools/implementations/ask_user_question_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/ask_user_question_tool.rs @@ -40,6 +40,12 @@ pub struct AskUserQuestionInput { /// AskUserQuestion tool pub struct AskUserQuestionTool; +impl Default for AskUserQuestionTool { + fn default() -> Self { + Self::new() + } +} + impl AskUserQuestionTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/bash_tool.rs b/src/crates/core/src/agentic/tools/implementations/bash_tool.rs index 47312af8..1fe4dbaa 100644 --- a/src/crates/core/src/agentic/tools/implementations/bash_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/bash_tool.rs @@ -80,6 +80,12 @@ struct ResolvedShell { /// Bash tool pub struct BashTool; +impl Default for BashTool { + fn default() -> Self { + Self::new() + } +} + impl BashTool { pub fn new() -> Self { Self @@ -777,6 +783,7 @@ Usage notes: impl BashTool { /// Execute a command in a new background terminal session. /// Returns immediately with the new session ID. + #[allow(clippy::too_many_arguments)] async fn call_background( &self, command_str: &str, diff --git a/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_click_tool.rs b/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_click_tool.rs index bd7fb0d7..7581835f 100644 --- a/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_click_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_click_tool.rs @@ -10,6 +10,12 @@ use serde_json::{json, Value}; pub struct ComputerUseMouseClickTool; +impl Default for ComputerUseMouseClickTool { + fn default() -> Self { + Self::new() + } +} + impl ComputerUseMouseClickTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_precise_tool.rs b/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_precise_tool.rs index d93b72d7..6757d262 100644 --- a/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_precise_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_precise_tool.rs @@ -10,6 +10,12 @@ use serde_json::{json, Value}; pub struct ComputerUseMousePreciseTool; +impl Default for ComputerUseMousePreciseTool { + fn default() -> Self { + Self::new() + } +} + impl ComputerUseMousePreciseTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_step_tool.rs b/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_step_tool.rs index 07145ecd..e05508d7 100644 --- a/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_step_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/computer_use_mouse_step_tool.rs @@ -10,6 +10,12 @@ use serde_json::{json, Value}; pub struct ComputerUseMouseStepTool; +impl Default for ComputerUseMouseStepTool { + fn default() -> Self { + Self::new() + } +} + impl ComputerUseMouseStepTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/computer_use_tool.rs b/src/crates/core/src/agentic/tools/implementations/computer_use_tool.rs index 61227040..441e6961 100644 --- a/src/crates/core/src/agentic/tools/implementations/computer_use_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/computer_use_tool.rs @@ -88,6 +88,12 @@ const COMPUTER_USE_DEBUG_SUBDIR: &str = ".bitfun/computer_use_debug"; pub struct ComputerUseTool; +impl Default for ComputerUseTool { + fn default() -> Self { + Self::new() + } +} + impl ComputerUseTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/create_plan_tool.rs b/src/crates/core/src/agentic/tools/implementations/create_plan_tool.rs index f315e5c8..d11d53df 100644 --- a/src/crates/core/src/agentic/tools/implementations/create_plan_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/create_plan_tool.rs @@ -208,7 +208,7 @@ Additional guidelines: .workspace_root() .ok_or(BitFunError::tool("Workspace path not set".to_string()))?; let path_manager = get_path_manager_arc(); - let plans_dir = path_manager.project_plans_dir(&workspace_path); + let plans_dir = path_manager.project_plans_dir(workspace_path); let plan_file_path = plans_dir.join(&plan_file_name); path_manager .ensure_dir(&plans_dir) diff --git a/src/crates/core/src/agentic/tools/implementations/cron_tool.rs b/src/crates/core/src/agentic/tools/implementations/cron_tool.rs index 98d588ae..17483353 100644 --- a/src/crates/core/src/agentic/tools/implementations/cron_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/cron_tool.rs @@ -113,7 +113,7 @@ impl CronTool { ) })?; self.resolve_workspace( - &workspace.to_string_lossy().to_string(), + workspace.to_string_lossy().as_ref(), Some(context), ) } diff --git a/src/crates/core/src/agentic/tools/implementations/delete_file_tool.rs b/src/crates/core/src/agentic/tools/implementations/delete_file_tool.rs index 2f5164e3..2a72a3ba 100644 --- a/src/crates/core/src/agentic/tools/implementations/delete_file_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/delete_file_tool.rs @@ -13,6 +13,12 @@ use tokio::fs; /// This tool automatically integrates with the snapshot system, all deletion operations are recorded and support rollback pub struct DeleteFileTool; +impl Default for DeleteFileTool { + fn default() -> Self { + Self::new() + } +} + impl DeleteFileTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/file_edit_tool.rs b/src/crates/core/src/agentic/tools/implementations/file_edit_tool.rs index 5933b785..811270b5 100644 --- a/src/crates/core/src/agentic/tools/implementations/file_edit_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/file_edit_tool.rs @@ -6,6 +6,12 @@ use tool_runtime::fs::edit_file::edit_file; pub struct FileEditTool; +impl Default for FileEditTool { + fn default() -> Self { + Self::new() + } +} + impl FileEditTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/file_read_tool.rs b/src/crates/core/src/agentic/tools/implementations/file_read_tool.rs index bcd7b459..ae4f1385 100644 --- a/src/crates/core/src/agentic/tools/implementations/file_read_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/file_read_tool.rs @@ -16,6 +16,12 @@ pub struct FileReadTool { max_total_chars: usize, } +impl Default for FileReadTool { + fn default() -> Self { + Self::new() + } +} + impl FileReadTool { pub fn new() -> Self { Self { @@ -322,7 +328,7 @@ Usage: self.max_line_chars, self.max_total_chars, ) - .map_err(|e| BitFunError::tool(e))? + .map_err(BitFunError::tool)? }; let file_rules = match get_global_ai_rules_service().await { diff --git a/src/crates/core/src/agentic/tools/implementations/file_write_tool.rs b/src/crates/core/src/agentic/tools/implementations/file_write_tool.rs index 42895ee6..2ee516dd 100644 --- a/src/crates/core/src/agentic/tools/implementations/file_write_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/file_write_tool.rs @@ -10,6 +10,12 @@ use tokio::fs; pub struct FileWriteTool; +impl Default for FileWriteTool { + fn default() -> Self { + Self::new() + } +} + impl FileWriteTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/get_file_diff_tool.rs b/src/crates/core/src/agentic/tools/implementations/get_file_diff_tool.rs index f63c9dde..08c1e2a3 100644 --- a/src/crates/core/src/agentic/tools/implementations/get_file_diff_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/get_file_diff_tool.rs @@ -23,6 +23,12 @@ use std::path::Path; /// 3. Return full file content pub struct GetFileDiffTool; +impl Default for GetFileDiffTool { + fn default() -> Self { + Self::new() + } +} + impl GetFileDiffTool { pub fn new() -> Self { Self @@ -477,7 +483,7 @@ Usage: // Priority 1: Try baseline diff let path = Path::new(&resolved_path); if let Some(result) = self - .try_baseline_diff(&path, context.workspace_root()) + .try_baseline_diff(path, context.workspace_root()) .await { match result { @@ -501,7 +507,7 @@ Usage: } // Priority 2: Try git diff - if let Some(result) = self.try_git_diff(&path).await { + if let Some(result) = self.try_git_diff(path).await { match result { Ok(data) => { debug!("GetFileDiff tool using git diff"); @@ -524,7 +530,7 @@ Usage: // Priority 3: Return full file content debug!("GetFileDiff tool returning full file content"); - let data = self.return_full_content(&path)?; + let data = self.return_full_content(path)?; let result_for_assistant = self.render_tool_result_message(&data); Ok(vec![ToolResult::Result { diff --git a/src/crates/core/src/agentic/tools/implementations/git_tool.rs b/src/crates/core/src/agentic/tools/implementations/git_tool.rs index eb1e38d3..82cc9bfd 100644 --- a/src/crates/core/src/agentic/tools/implementations/git_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/git_tool.rs @@ -374,7 +374,7 @@ impl GitTool { .collect(); let params = GitPushParams { - remote: parts.get(0).map(|s| s.to_string()), + remote: parts.first().map(|s| s.to_string()), branch: parts.get(1).map(|s| s.to_string()), force: Some(args_str.contains("--force") || args_str.contains("-f")), set_upstream: Some(args_str.contains("-u") || args_str.contains("--set-upstream")), @@ -402,7 +402,7 @@ impl GitTool { .collect(); let params = GitPullParams { - remote: parts.get(0).map(|s| s.to_string()), + remote: parts.first().map(|s| s.to_string()), branch: parts.get(1).map(|s| s.to_string()), rebase: Some(args_str.contains("--rebase")), }; @@ -427,17 +427,13 @@ impl GitTool { // Extract branch name let branch_name = args_str - .split_whitespace() - .filter(|s| !s.starts_with('-')) - .last() + .split_whitespace().rfind(|s| !s.starts_with('-')) .ok_or_else(|| BitFunError::tool("Branch name is required".to_string()))?; let result = if create_branch { // Create and switch to new branch let start_point = args_str - .split_whitespace() - .filter(|s| !s.starts_with('-') && *s != branch_name) - .last(); + .split_whitespace().rfind(|s| !s.starts_with('-') && *s != branch_name); GitService::create_branch(repo_path, branch_name, start_point).await } else { // Switch to existing branch @@ -493,9 +489,7 @@ impl GitTool { // Delete branch let force = args_str.contains("-D"); let branch_name = args_str - .split_whitespace() - .filter(|s| !s.starts_with('-')) - .next() + .split_whitespace().find(|s| !s.starts_with('-')) .ok_or_else(|| { BitFunError::tool("Branch name is required for deletion".to_string()) })?; diff --git a/src/crates/core/src/agentic/tools/implementations/glob_tool.rs b/src/crates/core/src/agentic/tools/implementations/glob_tool.rs index c0487806..dd06459c 100644 --- a/src/crates/core/src/agentic/tools/implementations/glob_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/glob_tool.rs @@ -150,7 +150,7 @@ fn build_fallback_matcher(relative_pattern: &str) -> Result fn match_relative_path(matcher: &GlobMatcher, relative_path: &str, is_dir: bool) -> bool { if is_dir { - matcher.is_match(relative_path) || matcher.is_match(&format!("{}/", relative_path)) + matcher.is_match(relative_path) || matcher.is_match(format!("{}/", relative_path)) } else { matcher.is_match(relative_path) } @@ -390,6 +390,12 @@ fn build_remote_find_command(search_dir: &str, pattern: &str, limit: usize) -> S pub struct GlobTool; +impl Default for GlobTool { + fn default() -> Self { + Self::new() + } +} + impl GlobTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/grep_tool.rs b/src/crates/core/src/agentic/tools/implementations/grep_tool.rs index afa3ada7..c81e5676 100644 --- a/src/crates/core/src/agentic/tools/implementations/grep_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/grep_tool.rs @@ -1,7 +1,8 @@ -use crate::agentic::tools::framework::{Tool, ToolResult, ToolUseContext}; +use crate::agentic::tools::framework::{Tool, ToolResult, ToolUseContext}; use crate::util::errors::{BitFunError, BitFunResult}; use async_trait::async_trait; use serde_json::{json, Value}; +use std::str::FromStr; use std::sync::Arc; use tool_runtime::search::grep_search::{ grep_search, GrepOptions, GrepSearchResult, OutputMode, ProgressCallback, @@ -11,6 +12,12 @@ const DEFAULT_HEAD_LIMIT: usize = 250; pub struct GrepTool; +impl Default for GrepTool { + fn default() -> Self { + Self::new() + } +} + impl GrepTool { pub fn new() -> Self { Self @@ -239,7 +246,7 @@ impl GrepTool { .get("output_mode") .and_then(|v| v.as_str()) .unwrap_or("files_with_matches"); - let output_mode = OutputMode::from_str(output_mode_str); + let output_mode = OutputMode::from_str(output_mode_str).unwrap_or(OutputMode::FilesWithMatches); let show_line_numbers = input .get("-n") .and_then(|v| v.as_bool()) diff --git a/src/crates/core/src/agentic/tools/implementations/log_tool.rs b/src/crates/core/src/agentic/tools/implementations/log_tool.rs index 4dcc1b2b..93d7bfa9 100644 --- a/src/crates/core/src/agentic/tools/implementations/log_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/log_tool.rs @@ -25,6 +25,12 @@ pub struct LogToolInput { pub level: Option, // Log level filter: "error", "warn", "info", "debug" } +impl Default for LogTool { + fn default() -> Self { + Self::new() + } +} + impl LogTool { pub fn new() -> Self { Self @@ -90,7 +96,7 @@ impl LogTool { } if results.is_empty() { - Ok(format!("No matching log records found")) + Ok("No matching log records found".to_string()) } else { Ok(format!( "Found {} matching records:\n{}", diff --git a/src/crates/core/src/agentic/tools/implementations/ls_tool.rs b/src/crates/core/src/agentic/tools/implementations/ls_tool.rs index f8a79477..ae35f392 100644 --- a/src/crates/core/src/agentic/tools/implementations/ls_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/ls_tool.rs @@ -23,6 +23,12 @@ pub struct LSTool { default_limit: usize, } +impl Default for LSTool { + fn default() -> Self { + Self::new() + } +} + impl LSTool { pub fn new() -> Self { Self { default_limit: 200 } @@ -271,7 +277,7 @@ Usage: .collect::>() }); - let entries = list_files(path, limit, ignore_patterns).map_err(|e| BitFunError::tool(e))?; + let entries = list_files(path, limit, ignore_patterns).map_err(BitFunError::tool)?; let entries_json = entries .iter() diff --git a/src/crates/core/src/agentic/tools/implementations/mcp_tools.rs b/src/crates/core/src/agentic/tools/implementations/mcp_tools.rs index 32d7d207..abdae47b 100644 --- a/src/crates/core/src/agentic/tools/implementations/mcp_tools.rs +++ b/src/crates/core/src/agentic/tools/implementations/mcp_tools.rs @@ -200,6 +200,12 @@ fn render_prompt_catalog(prompts: &[MCPPrompt]) -> String { pub struct ListMCPResourcesTool; +impl Default for ListMCPResourcesTool { + fn default() -> Self { + Self::new() + } +} + impl ListMCPResourcesTool { pub fn new() -> Self { Self @@ -301,6 +307,12 @@ pub struct ReadMCPResourceTool { max_render_chars: usize, } +impl Default for ReadMCPResourceTool { + fn default() -> Self { + Self::new() + } +} + impl ReadMCPResourceTool { pub fn new() -> Self { Self { @@ -410,6 +422,12 @@ impl Tool for ReadMCPResourceTool { pub struct ListMCPPromptsTool; +impl Default for ListMCPPromptsTool { + fn default() -> Self { + Self::new() + } +} + impl ListMCPPromptsTool { pub fn new() -> Self { Self @@ -511,6 +529,12 @@ pub struct GetMCPPromptTool { max_render_chars: usize, } +impl Default for GetMCPPromptTool { + fn default() -> Self { + Self::new() + } +} + impl GetMCPPromptTool { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/agentic/tools/implementations/mermaid_interactive_tool.rs b/src/crates/core/src/agentic/tools/implementations/mermaid_interactive_tool.rs index c8daddcb..ee284a30 100644 --- a/src/crates/core/src/agentic/tools/implementations/mermaid_interactive_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/mermaid_interactive_tool.rs @@ -13,6 +13,12 @@ use serde_json::{json, Value}; /// Mermaid interactive diagram tool pub struct MermaidInteractiveTool; +impl Default for MermaidInteractiveTool { + fn default() -> Self { + Self::new() + } +} + impl MermaidInteractiveTool { pub fn new() -> Self { Self @@ -79,29 +85,26 @@ impl MermaidInteractiveTool { } // Check if sequenceDiagram has participants - if trimmed.starts_with("sequenceDiagram") { - if !trimmed.contains("participant") + if trimmed.starts_with("sequenceDiagram") + && !trimmed.contains("participant") && !trimmed.contains("->>") && !trimmed.contains("-->>") { return (false, Some("Sequence diagram (sequenceDiagram) must contain participant definitions and interaction arrows. Example: participant A\nA->>B: Message".to_string())); } - } // Check if classDiagram has class definitions - if trimmed.starts_with("classDiagram") { - if !trimmed.contains("class ") && !trimmed.contains("<|--") && !trimmed.contains("..>") + if trimmed.starts_with("classDiagram") + && !trimmed.contains("class ") && !trimmed.contains("<|--") && !trimmed.contains("..>") { return (false, Some("Class diagram (classDiagram) must contain class definitions and relationships. Example: class A\nclass B\nA <|-- B".to_string())); } - } // Check if stateDiagram has state definitions - if trimmed.starts_with("stateDiagram") { - if !trimmed.contains("state ") && !trimmed.contains("[*]") && !trimmed.contains("-->") { + if trimmed.starts_with("stateDiagram") + && !trimmed.contains("state ") && !trimmed.contains("[*]") && !trimmed.contains("-->") { return (false, Some("State diagram (stateDiagram) must contain state definitions and transitions. Example: state A\n[*] --> A".to_string())); } - } // Check for unclosed brackets let open_brackets = trimmed.matches('[').count(); diff --git a/src/crates/core/src/agentic/tools/implementations/session_control_tool.rs b/src/crates/core/src/agentic/tools/implementations/session_control_tool.rs index 548bfa62..13d6910c 100644 --- a/src/crates/core/src/agentic/tools/implementations/session_control_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/session_control_tool.rs @@ -14,6 +14,12 @@ use std::time::SystemTime; /// SessionControl tool - create, delete, or list persisted sessions pub struct SessionControlTool; +impl Default for SessionControlTool { + fn default() -> Self { + Self::new() + } +} + impl SessionControlTool { pub fn new() -> Self { Self @@ -27,7 +33,7 @@ impl SessionControlTool { let current_session_id = context.session_id.as_deref()?; let current_workspace = context.workspace_root()?; let normalized_current_workspace = - normalize_path(¤t_workspace.to_string_lossy().to_string()); + normalize_path(current_workspace.to_string_lossy().as_ref()); if normalized_current_workspace == workspace { Some(current_session_id) diff --git a/src/crates/core/src/agentic/tools/implementations/session_history_tool.rs b/src/crates/core/src/agentic/tools/implementations/session_history_tool.rs index 55f53bcf..23183cb0 100644 --- a/src/crates/core/src/agentic/tools/implementations/session_history_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/session_history_tool.rs @@ -15,6 +15,12 @@ use std::sync::Arc; /// SessionHistory tool - export a grep-friendly transcript file for a session. pub struct SessionHistoryTool; +impl Default for SessionHistoryTool { + fn default() -> Self { + Self::new() + } +} + impl SessionHistoryTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/session_message_tool.rs b/src/crates/core/src/agentic/tools/implementations/session_message_tool.rs index 8e638719..9311056b 100644 --- a/src/crates/core/src/agentic/tools/implementations/session_message_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/session_message_tool.rs @@ -17,6 +17,12 @@ use std::path::Path; /// SessionMessage tool - send a message to another session via the dialog scheduler pub struct SessionMessageTool; +impl Default for SessionMessageTool { + fn default() -> Self { + Self::new() + } +} + impl SessionMessageTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/skill_tool.rs b/src/crates/core/src/agentic/tools/implementations/skill_tool.rs index 23e8cb7f..24139356 100644 --- a/src/crates/core/src/agentic/tools/implementations/skill_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/skill_tool.rs @@ -159,7 +159,7 @@ impl Tool for SkillTool { if input .get("command") .and_then(|v| v.as_str()) - .map_or(true, |s| s.is_empty()) + .is_none_or(|s| s.is_empty()) { return ValidationResult { result: false, diff --git a/src/crates/core/src/agentic/tools/implementations/skills/registry.rs b/src/crates/core/src/agentic/tools/implementations/skills/registry.rs index 26335353..bd58372a 100644 --- a/src/crates/core/src/agentic/tools/implementations/skills/registry.rs +++ b/src/crates/core/src/agentic/tools/implementations/skills/registry.rs @@ -331,9 +331,8 @@ impl SkillRegistry { remote_root: &str, ) -> Vec { let mut roots = Vec::new(); - let mut priority = 0usize; let root = remote_root.trim_end_matches('/'); - for (parent, sub, slot) in PROJECT_SKILL_SLOTS { + for (priority, (parent, sub, slot)) in PROJECT_SKILL_SLOTS.iter().enumerate() { let path = format!("{}/{}/{}", root, parent, sub); if fs.is_dir(&path).await.unwrap_or(false) { roots.push(RemoteSkillRootEntry { @@ -342,7 +341,6 @@ impl SkillRegistry { priority, }); } - priority += 1; } let mut skills = Vec::new(); diff --git a/src/crates/core/src/agentic/tools/implementations/task_tool.rs b/src/crates/core/src/agentic/tools/implementations/task_tool.rs index 951fe4c8..a6bf7079 100644 --- a/src/crates/core/src/agentic/tools/implementations/task_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/task_tool.rs @@ -12,6 +12,12 @@ use std::path::Path; pub struct TaskTool; +impl Default for TaskTool { + fn default() -> Self { + Self::new() + } +} + impl TaskTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/terminal_control_tool.rs b/src/crates/core/src/agentic/tools/implementations/terminal_control_tool.rs index 4cf6ccff..28bbf456 100644 --- a/src/crates/core/src/agentic/tools/implementations/terminal_control_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/terminal_control_tool.rs @@ -10,6 +10,12 @@ use terminal_core::{CloseSessionRequest, SignalRequest, TerminalApi}; /// TerminalControl tool - kill or interrupt a terminal session pub struct TerminalControlTool; +impl Default for TerminalControlTool { + fn default() -> Self { + Self::new() + } +} + impl TerminalControlTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/fs/edit_file.rs b/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/fs/edit_file.rs index 6978a119..5b5c8bc0 100644 --- a/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/fs/edit_file.rs +++ b/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/fs/edit_file.rs @@ -1,4 +1,4 @@ -use crate::util::string::normalize_string; +use crate::util::string::normalize_string; use std::fs; /// Edit result, contains line number range information @@ -45,7 +45,7 @@ pub fn edit_file( let matches: Vec<_> = normalized_content.match_indices(&normalized_old).collect(); if matches.is_empty() { - return Err(format!("old_string not found in file.")); + return Err("old_string not found in file.".to_string()); } if matches.len() > 1 && !replace_all { @@ -75,7 +75,7 @@ pub fn edit_file( new_content = new_content.replace("\n", "\r\n"); } - fs::write(&file_path, &new_content) + fs::write(file_path, &new_content) .map_err(|e| format!("Failed to write file {}: {}", file_path, e))?; Ok(EditResult { diff --git a/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/fs/read_file.rs b/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/fs/read_file.rs index f66a0b19..f0103333 100644 --- a/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/fs/read_file.rs +++ b/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/fs/read_file.rs @@ -1,4 +1,4 @@ -use crate::util::string::truncate_string_by_chars; +use crate::util::string::truncate_string_by_chars; use std::fs::File; use std::io::BufRead; use std::io::BufReader; @@ -21,10 +21,10 @@ pub fn read_file( max_total_chars: usize, ) -> Result { if start_line == 0 { - return Err(format!("`start_line` should start from 1",)); + return Err("`start_line` should start from 1".to_string()); } if limit == 0 { - return Err(format!("`limit` can't be 0")); + return Err("`limit` can't be 0".to_string()); } if max_total_chars == 0 { return Err("`max_total_chars` can't be 0".to_string()); diff --git a/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/search/grep_search.rs b/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/search/grep_search.rs index 694ffb0d..9ffaecfe 100644 --- a/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/search/grep_search.rs +++ b/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/search/grep_search.rs @@ -1,4 +1,5 @@ -use log::{debug, info, warn}; +use log::{debug, info, warn}; +use std::fmt; use std::io; use std::path::{Component, Path, PathBuf}; use std::sync::{Arc, Mutex}; @@ -21,21 +22,25 @@ pub enum OutputMode { Count, } -impl OutputMode { - pub fn from_str(s: &str) -> Self { +impl std::str::FromStr for OutputMode { + type Err = String; + + fn from_str(s: &str) -> Result { match s { - "content" => OutputMode::Content, - "count" => OutputMode::Count, - "files_with_matches" => OutputMode::FilesWithMatches, - _ => OutputMode::Content, // Default to Content mode + "content" => Ok(OutputMode::Content), + "count" => Ok(OutputMode::Count), + "files_with_matches" => Ok(OutputMode::FilesWithMatches), + _ => Err(format!("Unknown output mode: {}", s)), } } +} - pub fn to_string(&self) -> String { +impl fmt::Display for OutputMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - OutputMode::Content => "content".to_string(), - OutputMode::Count => "count".to_string(), - OutputMode::FilesWithMatches => "files_with_matches".to_string(), + OutputMode::Content => write!(f, "content"), + OutputMode::Count => write!(f, "count"), + OutputMode::FilesWithMatches => write!(f, "files_with_matches"), } } } diff --git a/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/util/string.rs b/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/util/string.rs index 4d3a2f8d..5164814a 100644 --- a/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/util/string.rs +++ b/src/crates/core/src/agentic/tools/implementations/tool-runtime/src/util/string.rs @@ -1,4 +1,4 @@ -pub fn normalize_string(s: &str) -> String { +pub fn normalize_string(s: &str) -> String { if s.contains("\r\n") { s.replace("\r\n", "\n") } else { @@ -8,5 +8,5 @@ pub fn normalize_string(s: &str) -> String { pub fn truncate_string_by_chars(s: &str, kept_chars: usize) -> String { let chars: Vec = s.chars().collect(); - chars[..kept_chars].into_iter().collect() + chars[..kept_chars].iter().collect() } diff --git a/src/crates/core/src/agentic/tools/implementations/web_tools.rs b/src/crates/core/src/agentic/tools/implementations/web_tools.rs index ce04302f..444dad90 100644 --- a/src/crates/core/src/agentic/tools/implementations/web_tools.rs +++ b/src/crates/core/src/agentic/tools/implementations/web_tools.rs @@ -31,6 +31,12 @@ struct ExaContent { pub struct WebSearchTool; +impl Default for WebSearchTool { + fn default() -> Self { + Self::new() + } +} + impl WebSearchTool { pub fn new() -> Self { Self @@ -354,6 +360,12 @@ Advanced features: /// WebFetch tool pub struct WebFetchTool; +impl Default for WebFetchTool { + fn default() -> Self { + Self::new() + } +} + impl WebFetchTool { pub fn new() -> Self { Self diff --git a/src/crates/core/src/agentic/tools/pipeline/state_manager.rs b/src/crates/core/src/agentic/tools/pipeline/state_manager.rs index 1e36d7e3..b809ce09 100644 --- a/src/crates/core/src/agentic/tools/pipeline/state_manager.rs +++ b/src/crates/core/src/agentic/tools/pipeline/state_manager.rs @@ -230,8 +230,10 @@ impl ToolStateManager { pub fn get_stats(&self) -> ToolStats { let tasks: Vec<_> = self.tasks.iter().map(|e| e.value().clone()).collect(); - let mut stats = ToolStats::default(); - stats.total = tasks.len(); + let mut stats = ToolStats { + total: tasks.len(), + ..ToolStats::default() + }; for task in tasks { match task.state { diff --git a/src/crates/core/src/agentic/tools/pipeline/tool_pipeline.rs b/src/crates/core/src/agentic/tools/pipeline/tool_pipeline.rs index f1408357..02e550d8 100644 --- a/src/crates/core/src/agentic/tools/pipeline/tool_pipeline.rs +++ b/src/crates/core/src/agentic/tools/pipeline/tool_pipeline.rs @@ -96,8 +96,8 @@ fn generate_default_assistant_text(tool_name: &str, data: &serde_json::Value) -> } // If it is an empty object or empty array - if (data.is_object() && data.as_object().map_or(false, |o| o.is_empty())) - || (data.is_array() && data.as_array().map_or(false, |a| a.is_empty())) + if (data.is_object() && data.as_object().is_some_and(|o| o.is_empty())) + || (data.is_array() && data.as_array().is_some_and(|a| a.is_empty())) { return Some(format!( "Tool {} completed, returned empty result.", @@ -504,10 +504,7 @@ impl ToolPipeline { timeout_secs, tool_name ); // There is a timeout limit - match timeout(Duration::from_secs(timeout_secs), rx).await { - Ok(result) => Some(result), - Err(_) => None, - } + timeout(Duration::from_secs(timeout_secs), rx).await.ok() } None => { debug!( diff --git a/src/crates/core/src/agentic/tools/registry.rs b/src/crates/core/src/agentic/tools/registry.rs index d4799989..06a14a8c 100644 --- a/src/crates/core/src/agentic/tools/registry.rs +++ b/src/crates/core/src/agentic/tools/registry.rs @@ -12,6 +12,12 @@ pub struct ToolRegistry { tools: IndexMap>, } +impl Default for ToolRegistry { + fn default() -> Self { + Self::new() + } +} + impl ToolRegistry { /// Create a new tool registry pub fn new() -> Self { diff --git a/src/crates/core/src/agentic/tools/user_input_manager.rs b/src/crates/core/src/agentic/tools/user_input_manager.rs index bd0e8c63..73cc1a74 100644 --- a/src/crates/core/src/agentic/tools/user_input_manager.rs +++ b/src/crates/core/src/agentic/tools/user_input_manager.rs @@ -21,6 +21,12 @@ pub struct UserInputManager { channels: Arc>>, } +impl Default for UserInputManager { + fn default() -> Self { + Self::new() + } +} + impl UserInputManager { /// Create a new manager instance pub fn new() -> Self { diff --git a/src/crates/core/src/agentic/util/list_files.rs b/src/crates/core/src/agentic/util/list_files.rs index 6cc17b6d..a20472ce 100644 --- a/src/crates/core/src/agentic/util/list_files.rs +++ b/src/crates/core/src/agentic/util/list_files.rs @@ -33,7 +33,7 @@ impl CompiledGlob { if is_dir { // For directories, try matching with and without trailing slash self.matcher.is_match(rel_path_str) - || self.matcher.is_match(&format!("{}/", rel_path_str)) + || self.matcher.is_match(format!("{}/", rel_path_str)) } else { self.matcher.is_match(rel_path_str) } @@ -105,15 +105,13 @@ pub fn list_files( let gitignore = load_gitignore(path); // Special folders that should not be expanded - let special_folders = vec![ - Path::new("/"), + let special_folders = [Path::new("/"), Path::new("/home"), Path::new("/Users"), Path::new("/System"), Path::new("/Windows"), Path::new("/Program Files"), - Path::new("/Program Files (x86)"), - ]; + Path::new("/Program Files (x86)")]; // Folders to exclude let excluded_folders = vec![ @@ -169,9 +167,7 @@ pub fn list_files( // Never exclude the root directory false } else { - excluded_folders - .iter() - .any(|excluded| folder_name == *excluded) + excluded_folders.contains(&folder_name) || (folder_name.starts_with('.') && folder_name != "." && folder_name != "..") }; diff --git a/src/crates/core/src/function_agents/git-func-agent/utils.rs b/src/crates/core/src/function_agents/git-func-agent/utils.rs index dd191e42..908d0253 100644 --- a/src/crates/core/src/function_agents/git-func-agent/utils.rs +++ b/src/crates/core/src/function_agents/git-func-agent/utils.rs @@ -76,10 +76,7 @@ pub fn detect_change_patterns(file_changes: &[FileChange]) -> Vec let mut has_new_files = false; for change in file_changes { - match change.change_type { - FileChangeType::Added => has_new_files = true, - _ => {} - } + if change.change_type == FileChangeType::Added { has_new_files = true } if is_test_file(&change.path) { has_test_changes = true; diff --git a/src/crates/core/src/function_agents/mod.rs b/src/crates/core/src/function_agents/mod.rs index 3e90239c..a877e2a5 100644 --- a/src/crates/core/src/function_agents/mod.rs +++ b/src/crates/core/src/function_agents/mod.rs @@ -1,4 +1,4 @@ -/** +/*! * Function Agents module * * Provides various function agents for automating specific tasks diff --git a/src/crates/core/src/function_agents/startchat-func-agent/work_state_analyzer.rs b/src/crates/core/src/function_agents/startchat-func-agent/work_state_analyzer.rs index 67f37155..ff91ada4 100644 --- a/src/crates/core/src/function_agents/startchat-func-agent/work_state_analyzer.rs +++ b/src/crates/core/src/function_agents/startchat-func-agent/work_state_analyzer.rs @@ -30,7 +30,7 @@ impl WorkStateAnalyzer { let git_diff = if git_state .as_ref() - .map_or(false, |g| g.unstaged_files > 0 || g.staged_files > 0) + .is_some_and(|g| g.unstaged_files > 0 || g.staged_files > 0) { Self::get_git_diff(repo_path).await.unwrap_or_default() } else { @@ -242,7 +242,7 @@ impl WorkStateAnalyzer { } let result = String::from_utf8_lossy(&output.stdout); - let parts: Vec<&str> = result.trim().split_whitespace().collect(); + let parts: Vec<&str> = result.split_whitespace().collect(); if parts.len() >= 2 { let ahead = parts[0].parse().unwrap_or(0); diff --git a/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/anthropic.rs b/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/anthropic.rs index 4f101ab1..de779a96 100644 --- a/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/anthropic.rs +++ b/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/anthropic.rs @@ -1,4 +1,4 @@ -use super::unified::{UnifiedResponse, UnifiedTokenUsage, UnifiedToolCall}; +use super::unified::{UnifiedResponse, UnifiedTokenUsage, UnifiedToolCall}; use serde::Deserialize; #[derive(Debug, Deserialize)] @@ -12,6 +12,7 @@ pub struct Message { } #[derive(Debug, Clone, Deserialize)] +#[derive(Default)] pub struct Usage { input_tokens: Option, output_tokens: Option, @@ -19,16 +20,6 @@ pub struct Usage { cache_creation_input_tokens: Option, } -impl Default for Usage { - fn default() -> Self { - Self { - input_tokens: None, - output_tokens: None, - cache_read_input_tokens: None, - cache_creation_input_tokens: None, - } - } -} impl Usage { pub fn update(&mut self, other: &Usage) { @@ -123,16 +114,13 @@ pub enum ContentBlock { impl From for UnifiedResponse { fn from(value: ContentBlockStart) -> Self { let mut result = UnifiedResponse::default(); - match value.content_block { - ContentBlock::ToolUse { id, name } => { - let tool_call = UnifiedToolCall { - id: Some(id), - name: Some(name), - arguments: None, - }; - result.tool_call = Some(tool_call); - } - _ => {} + if let ContentBlock::ToolUse { id, name } = value.content_block { + let tool_call = UnifiedToolCall { + id: Some(id), + name: Some(name), + arguments: None, + }; + result.tool_call = Some(tool_call); } result } @@ -147,13 +135,13 @@ pub struct ContentBlockDelta { #[serde(tag = "type")] pub enum Delta { #[serde(rename = "thinking_delta")] - ThinkingDelta { thinking: String }, + Thinking { thinking: String }, #[serde(rename = "text_delta")] - TextDelta { text: String }, + Text { text: String }, #[serde(rename = "input_json_delta")] - InputJsonDelta { partial_json: String }, + InputJson { partial_json: String }, #[serde(rename = "signature_delta")] - SignatureDelta { signature: String }, + Signature { signature: String }, #[serde(other)] Unknown, } @@ -163,13 +151,13 @@ impl TryFrom for UnifiedResponse { fn try_from(value: ContentBlockDelta) -> Result { let mut result = UnifiedResponse::default(); match value.delta { - Delta::ThinkingDelta { thinking } => { + Delta::Thinking { thinking } => { result.reasoning_content = Some(thinking); } - Delta::TextDelta { text } => { + Delta::Text { text } => { result.text = Some(text); } - Delta::InputJsonDelta { partial_json } => { + Delta::InputJson { partial_json } => { let tool_call = UnifiedToolCall { id: None, name: None, @@ -177,7 +165,7 @@ impl TryFrom for UnifiedResponse { }; result.tool_call = Some(tool_call); } - Delta::SignatureDelta { signature } => { + Delta::Signature { signature } => { result.thinking_signature = Some(signature); } Delta::Unknown => { diff --git a/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/unified.rs b/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/unified.rs index 309a3501..e3b36c37 100644 --- a/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/unified.rs +++ b/src/crates/core/src/infrastructure/ai/ai_stream_handlers/src/types/unified.rs @@ -10,6 +10,7 @@ pub struct UnifiedToolCall { /// Unified AI response format #[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default)] pub struct UnifiedResponse { pub text: Option, pub reasoning_content: Option, @@ -23,19 +24,6 @@ pub struct UnifiedResponse { pub provider_metadata: Option, } -impl Default for UnifiedResponse { - fn default() -> Self { - Self { - text: None, - reasoning_content: None, - thinking_signature: None, - tool_call: None, - usage: None, - finish_reason: None, - provider_metadata: None, - } - } -} /// Unified token usage statistics #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/crates/core/src/infrastructure/ai/client.rs b/src/crates/core/src/infrastructure/ai/client.rs index dd571899..b24ce0ca 100644 --- a/src/crates/core/src/infrastructure/ai/client.rs +++ b/src/crates/core/src/infrastructure/ai/client.rs @@ -101,9 +101,7 @@ impl AIClient { .filter(|s| !s.is_empty()) .collect(); - if tokens - .iter() - .any(|token| *token == Self::TEST_IMAGE_EXPECTED_CODE) + if tokens.contains(&Self::TEST_IMAGE_EXPECTED_CODE) { return true; } @@ -536,7 +534,7 @@ impl AIClient { .config .custom_headers .as_ref() - .map_or(false, |h| !h.is_empty()); + .is_some_and(|h| !h.is_empty()); let is_merge_mode = self.is_merge_headers_mode(); if has_custom_headers && !is_merge_mode { @@ -568,7 +566,7 @@ impl AIClient { .config .custom_headers .as_ref() - .map_or(false, |h| !h.is_empty()); + .is_some_and(|h| !h.is_empty()); let is_merge_mode = self.is_merge_headers_mode(); if has_custom_headers && !is_merge_mode { @@ -605,7 +603,7 @@ impl AIClient { .config .custom_headers .as_ref() - .map_or(false, |h| !h.is_empty()); + .is_some_and(|h| !h.is_empty()); let is_merge_mode = self.is_merge_headers_mode(); if has_custom_headers && !is_merge_mode { @@ -874,7 +872,7 @@ impl AIClient { if let Some(tools) = openai_tools { let tool_names = tools .iter() - .map(|tool| Self::extract_openai_tool_name(tool)) + .map(Self::extract_openai_tool_name) .collect::>(); debug!(target: "ai::openai_stream_request", "\ntools: {:?}", tool_names); if !tools.is_empty() { @@ -944,7 +942,7 @@ impl AIClient { if let Some(tools) = openai_tools { let tool_names = tools .iter() - .map(|tool| Self::extract_openai_tool_name(tool)) + .map(Self::extract_openai_tool_name) .collect::>(); debug!(target: "ai::responses_stream_request", "\ntools: {:?}", tool_names); if !tools.is_empty() { @@ -1017,7 +1015,7 @@ impl AIClient { if let Some(tools) = anthropic_tools { let tool_names = tools .iter() - .map(|tool| Self::extract_anthropic_tool_name(tool)) + .map(Self::extract_anthropic_tool_name) .collect::>(); debug!(target: "ai::anthropic_stream_request", "\ntools: {:?}", tool_names); if !tools.is_empty() { diff --git a/src/crates/core/src/infrastructure/ai/tool_call_accumulator.rs b/src/crates/core/src/infrastructure/ai/tool_call_accumulator.rs index c35b4fb5..33eaffa5 100644 --- a/src/crates/core/src/infrastructure/ai/tool_call_accumulator.rs +++ b/src/crates/core/src/infrastructure/ai/tool_call_accumulator.rs @@ -56,7 +56,7 @@ impl PendingToolCall { .find(|(_, ch)| !ch.is_whitespace()) .map(|(idx, _)| idx)?; - if raw_arguments[last_non_whitespace_idx..].chars().next() != Some('}') { + if !raw_arguments[last_non_whitespace_idx..].starts_with('}') { return None; } diff --git a/src/crates/core/src/infrastructure/debug_log/http_server.rs b/src/crates/core/src/infrastructure/debug_log/http_server.rs index e894e408..5c360265 100644 --- a/src/crates/core/src/infrastructure/debug_log/http_server.rs +++ b/src/crates/core/src/infrastructure/debug_log/http_server.rs @@ -31,6 +31,12 @@ pub struct IngestServerManager { actual_port: Arc>, } +impl Default for IngestServerManager { + fn default() -> Self { + Self::new() + } +} + impl IngestServerManager { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/infrastructure/debug_log/mod.rs b/src/crates/core/src/infrastructure/debug_log/mod.rs index 7da06c56..087e29cd 100644 --- a/src/crates/core/src/infrastructure/debug_log/mod.rs +++ b/src/crates/core/src/infrastructure/debug_log/mod.rs @@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::fs::{self, OpenOptions}; use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::LazyLock; use tokio::task; use uuid::Uuid; @@ -160,7 +160,7 @@ fn build_log_line(entry: DebugLogEntry, config: &DebugLogConfig) -> Value { }) } -fn ensure_parent_exists(path: &PathBuf) -> Result<()> { +fn ensure_parent_exists(path: &Path) -> Result<()> { if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } diff --git a/src/crates/core/src/infrastructure/filesystem/file_tree.rs b/src/crates/core/src/infrastructure/filesystem/file_tree.rs index b28b54a6..f6004dd0 100644 --- a/src/crates/core/src/infrastructure/filesystem/file_tree.rs +++ b/src/crates/core/src/infrastructure/filesystem/file_tree.rs @@ -207,7 +207,7 @@ impl FileTreeService { // For remote workspaces, return simple directory listing with empty stats if crate::service::remote_ssh::workspace_state::is_remote_path(root_path).await { let nodes = self.get_directory_contents_with_remote_hint(root_path, None).await - .map_err(|e| BitFunError::service(e))?; + .map_err(BitFunError::service)?; let stats = FileTreeStatistics { total_files: nodes.iter().filter(|n| !n.is_directory).count(), total_directories: nodes.iter().filter(|n| n.is_directory).count(), @@ -252,7 +252,7 @@ impl FileTreeService { &mut stats, ) .await - .map_err(|e| BitFunError::service(e))?; + .map_err(BitFunError::service)?; Ok((nodes, stats)) } @@ -356,9 +356,9 @@ impl FileTreeService { } let last_modified = metadata.and_then(|m| { - m.modified().ok().and_then(|t| { + m.modified().ok().map(|t| { let datetime: chrono::DateTime = t.into(); - Some(datetime.format("%Y-%m-%d %H:%M:%S").to_string()) + datetime.format("%Y-%m-%d %H:%M:%S").to_string() }) }); @@ -389,8 +389,8 @@ impl FileTreeService { .with_depth(depth) .with_enhanced_info(is_symlink, permissions, mime_type, None); - if is_directory { - if !is_symlink || self.options.follow_symlinks { + if is_directory + && (!is_symlink || self.options.follow_symlinks) { match self .build_tree_recursive(&entry_path, root_path, visited, depth + 1) .await @@ -403,7 +403,6 @@ impl FileTreeService { } } } - } nodes.push(node); } @@ -551,9 +550,9 @@ impl FileTreeService { } let last_modified = metadata.and_then(|m| { - m.modified().ok().and_then(|t| { + m.modified().ok().map(|t| { let datetime: chrono::DateTime = t.into(); - Some(datetime.format("%Y-%m-%d %H:%M:%S").to_string()) + datetime.format("%Y-%m-%d %H:%M:%S").to_string() }) }); @@ -584,8 +583,8 @@ impl FileTreeService { .with_depth(depth) .with_enhanced_info(is_symlink, permissions, mime_type, None); - if is_directory { - if !is_symlink || self.options.follow_symlinks { + if is_directory + && (!is_symlink || self.options.follow_symlinks) { match self .build_tree_recursive_with_stats( &entry_path, @@ -604,7 +603,6 @@ impl FileTreeService { } } } - } nodes.push(node); } @@ -1040,6 +1038,7 @@ impl FileTreeService { Ok(final_results) } + #[allow(clippy::too_many_arguments)] fn search_file_content_static( path: &Path, file_name: &str, diff --git a/src/crates/core/src/infrastructure/filesystem/path_manager.rs b/src/crates/core/src/infrastructure/filesystem/path_manager.rs index 6290d9e8..94781341 100644 --- a/src/crates/core/src/infrastructure/filesystem/path_manager.rs +++ b/src/crates/core/src/infrastructure/filesystem/path_manager.rs @@ -270,14 +270,14 @@ impl PathManager { let canonical_exists = canonical.exists(); let legacy_exists = legacy.exists(); - let chosen = if canonical_exists { + + if canonical_exists { canonical.clone() } else if legacy_exists { legacy.clone() } else { canonical.clone() - }; - chosen + } } /// Root for per-host, per-remote-path workspace mirrors: `~/.bitfun/remote_ssh/`. diff --git a/src/crates/core/src/miniapp/bridge_builder.rs b/src/crates/core/src/miniapp/bridge_builder.rs index 00ecabd7..1922b632 100644 --- a/src/crates/core/src/miniapp/bridge_builder.rs +++ b/src/crates/core/src/miniapp/bridge_builder.rs @@ -152,7 +152,7 @@ pub fn build_import_map(deps: &[EsmDep]) -> String { imports.insert(dep.name.clone(), serde_json::Value::String(url)); } let json = serde_json::json!({ "imports": imports }); - format!(r#""#, json.to_string()) + format!(r#""#, json) } /// Build CSP meta content from permissions (net.allow → connect-src). diff --git a/src/crates/core/src/miniapp/js_worker.rs b/src/crates/core/src/miniapp/js_worker.rs index f7cf7673..57df5b1c 100644 --- a/src/crates/core/src/miniapp/js_worker.rs +++ b/src/crates/core/src/miniapp/js_worker.rs @@ -11,11 +11,15 @@ use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::{Child, ChildStdin, Command}; use tokio::sync::{oneshot, Mutex}; +type JsWorkerResponse = Result; +type PendingResponseSender = oneshot::Sender; +type PendingResponseMap = HashMap; + /// Single JS Worker process: stdin for requests, stderr for RPC responses, stdout for user logs. pub struct JsWorker { _child: Child, stdin: Mutex>, - pending: Arc>>>>, + pending: Arc>, last_activity: Arc, } @@ -44,10 +48,7 @@ impl JsWorker { let stderr = child.stderr.take().ok_or("No stderr")?; let _stdout = child.stdout.take(); - let pending = Arc::new(Mutex::new(HashMap::< - String, - oneshot::Sender>, - >::new())); + let pending = Arc::new(Mutex::new(PendingResponseMap::new())); let last_activity = Arc::new(AtomicI64::new( std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) diff --git a/src/crates/core/src/miniapp/js_worker_pool.rs b/src/crates/core/src/miniapp/js_worker_pool.rs index 42d7fdc8..4e854182 100644 --- a/src/crates/core/src/miniapp/js_worker_pool.rs +++ b/src/crates/core/src/miniapp/js_worker_pool.rs @@ -124,7 +124,7 @@ impl JsWorkerPool { let worker = JsWorker::spawn(&self.runtime, &self.worker_host_path, &app_dir, policy_json) .await - .map_err(|e| BitFunError::validation(e))?; + .map_err(BitFunError::validation)?; let _timeout_ms = node_perms.and_then(|n| n.timeout_ms).unwrap_or(30_000); let worker = Arc::new(Mutex::new(worker)); diff --git a/src/crates/core/src/miniapp/manager.rs b/src/crates/core/src/miniapp/manager.rs index 442665f7..b21a71be 100644 --- a/src/crates/core/src/miniapp/manager.rs +++ b/src/crates/core/src/miniapp/manager.rs @@ -146,6 +146,7 @@ impl MiniAppManager { } /// Create a new MiniApp (generates id, sets created_at/updated_at, compiles). + #[allow(clippy::too_many_arguments)] pub async fn create( &self, name: String, @@ -188,6 +189,7 @@ impl MiniAppManager { } /// Update existing MiniApp (increment version, recompile, save). + #[allow(clippy::too_many_arguments)] pub async fn update( &self, app_id: &str, diff --git a/src/crates/core/src/miniapp/runtime_detect.rs b/src/crates/core/src/miniapp/runtime_detect.rs index 39fce75c..5b877ec3 100644 --- a/src/crates/core/src/miniapp/runtime_detect.rs +++ b/src/crates/core/src/miniapp/runtime_detect.rs @@ -45,8 +45,7 @@ fn get_version(executable: &std::path::Path) -> Result { let v = String::from_utf8_lossy(&out.stdout); Ok(v.trim().to_string()) } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(std::io::Error::other( "version check failed", )) } diff --git a/src/crates/core/src/service/agent_memory/agent_memory.rs b/src/crates/core/src/service/agent_memory/agent_memory_impl.rs similarity index 100% rename from src/crates/core/src/service/agent_memory/agent_memory.rs rename to src/crates/core/src/service/agent_memory/agent_memory_impl.rs diff --git a/src/crates/core/src/service/agent_memory/mod.rs b/src/crates/core/src/service/agent_memory/mod.rs index c836b9eb..a0f94ba8 100644 --- a/src/crates/core/src/service/agent_memory/mod.rs +++ b/src/crates/core/src/service/agent_memory/mod.rs @@ -1,3 +1,3 @@ -mod agent_memory; +mod agent_memory_impl; -pub(crate) use agent_memory::build_workspace_agent_memory_prompt; +pub(crate) use agent_memory_impl::build_workspace_agent_memory_prompt; diff --git a/src/crates/core/src/service/ai_memory/manager.rs b/src/crates/core/src/service/ai_memory/manager.rs index fee92d23..eb0c99bb 100644 --- a/src/crates/core/src/service/ai_memory/manager.rs +++ b/src/crates/core/src/service/ai_memory/manager.rs @@ -188,9 +188,9 @@ impl AIMemoryManager { "## {} [{}] (Importance: {}/5)\n{}\n", memory.title, type_label, memory.importance, memory.content )); - prompt.push_str("\n"); + prompt.push('\n'); } - prompt.push_str("\n"); + prompt.push('\n'); Ok(Some(prompt)) } diff --git a/src/crates/core/src/service/ai_memory/types.rs b/src/crates/core/src/service/ai_memory/types.rs index 0929da7f..6cd13ec6 100644 --- a/src/crates/core/src/service/ai_memory/types.rs +++ b/src/crates/core/src/service/ai_memory/types.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; /// Memory type #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] +#[derive(Default)] pub enum MemoryType { /// Technology preference TechPreference, @@ -18,14 +19,10 @@ pub enum MemoryType { /// Architecture decision Decision, /// Other + #[default] Other, } -impl Default for MemoryType { - fn default() -> Self { - Self::Other - } -} /// AI memory point #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/crates/core/src/service/ai_rules/service.rs b/src/crates/core/src/service/ai_rules/service.rs index 19dcded7..4aa8e047 100644 --- a/src/crates/core/src/service/ai_rules/service.rs +++ b/src/crates/core/src/service/ai_rules/service.rs @@ -636,7 +636,7 @@ The rules section has a number of possible rules/memories/context that you shoul while let Ok(Some(entry)) = entries.next_entry().await { let path = entry.path(); - if path.extension().map_or(false, |ext| ext == "mdc") { + if path.extension().is_some_and(|ext| ext == "mdc") { match self.load_rule_from_file(&path, level).await { Ok(rule) => rules.push(rule), Err(e) => { @@ -664,7 +664,7 @@ The rules section has a number of possible rules/memories/context that you shoul .ok_or_else(|| BitFunError::service("Invalid file name".to_string()))?; AIRule::from_mdc(name, level, path.to_path_buf(), &content) - .map_err(|e| BitFunError::service(e)) + .map_err(BitFunError::service) } /// Creates a rule file. @@ -733,7 +733,7 @@ The rules section has a number of possible rules/memories/context that you shoul .map_err(|e| BitFunError::service(format!("Failed to read file: {}", e)))?; let (mut frontmatter, mut body) = - parse_mdc_content(&content).map_err(|e| BitFunError::service(e))?; + parse_mdc_content(&content).map_err(BitFunError::service)?; if let Some(apply_type) = request.apply_type { match apply_type { diff --git a/src/crates/core/src/service/bootstrap/bootstrap.rs b/src/crates/core/src/service/bootstrap/bootstrap_impl.rs similarity index 100% rename from src/crates/core/src/service/bootstrap/bootstrap.rs rename to src/crates/core/src/service/bootstrap/bootstrap_impl.rs diff --git a/src/crates/core/src/service/bootstrap/mod.rs b/src/crates/core/src/service/bootstrap/mod.rs index 04b56c3b..94bf1477 100644 --- a/src/crates/core/src/service/bootstrap/mod.rs +++ b/src/crates/core/src/service/bootstrap/mod.rs @@ -1,7 +1,7 @@ -mod bootstrap; +mod bootstrap_impl; -pub use bootstrap::reset_workspace_persona_files_to_default; -pub(crate) use bootstrap::{ +pub use bootstrap_impl::reset_workspace_persona_files_to_default; +pub(crate) use bootstrap_impl::{ build_workspace_persona_prompt, ensure_workspace_persona_files_for_prompt, initialize_workspace_persona_files, is_workspace_bootstrap_pending, diff --git a/src/crates/core/src/service/config/manager.rs b/src/crates/core/src/service/config/manager.rs index 6d9d8297..0bf49198 100644 --- a/src/crates/core/src/service/config/manager.rs +++ b/src/crates/core/src/service/config/manager.rs @@ -14,6 +14,9 @@ use std::path::PathBuf; use std::sync::Arc; use tokio::fs; +type ConfigMigrationFn = fn(Value) -> BitFunResult; +type ConfigMigration = (&'static str, &'static str, ConfigMigrationFn); + /// Configuration manager. pub struct ConfigManager { config_dir: PathBuf, @@ -184,7 +187,7 @@ impl ConfigManager { /// Auto-completes missing fields in model configuration (backward compatible). /// Ensures older configurations won't panic. - fn ensure_models_config(models: &mut Vec) { + fn ensure_models_config(models: &mut [AIModelConfig]) { for model in models.iter_mut() { model.ensure_category_and_capabilities(); } @@ -229,7 +232,7 @@ impl ConfigManager { from_version: &str, mut config: Value, ) -> BitFunResult { - let migrations: Vec<(&str, &str, fn(Value) -> BitFunResult)> = + let migrations: Vec = vec![("0.0.0", "1.0.0", migrate_0_0_0_to_1_0_0)]; let mut current_version = from_version.to_string(); @@ -605,7 +608,7 @@ pub(crate) fn version_lt(v1: &str, v2: &str) -> bool { /// Parses a version string into a tuple `(major, minor, patch)`. pub(crate) fn parse_version(version: &str) -> (u32, u32, u32) { let parts: Vec<&str> = version.split('.').collect(); - let major = parts.get(0).and_then(|s| s.parse().ok()).unwrap_or(0); + let major = parts.first().and_then(|s| s.parse().ok()).unwrap_or(0); let minor = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0); let patch = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0); (major, minor, patch) diff --git a/src/crates/core/src/service/config/providers.rs b/src/crates/core/src/service/config/providers.rs index 4a68d2ca..4018ee0b 100644 --- a/src/crates/core/src/service/config/providers.rs +++ b/src/crates/core/src/service/config/providers.rs @@ -72,7 +72,7 @@ impl ConfigProvider for AIConfigProvider { } } if let Some(temperature) = model.temperature { - if temperature < 0.0 || temperature > 2.0 { + if !(0.0..=2.0).contains(&temperature) { warnings.push(format!( "Model '{}' temperature should be between 0 and 2", model.name @@ -543,8 +543,8 @@ impl ConfigProviderRegistry { } /// Gets a provider by name. - pub fn get_provider(&self, name: &str) -> Option<&Box> { - self.providers.get(name) + pub fn get_provider(&self, name: &str) -> Option<&dyn ConfigProvider> { + self.providers.get(name).map(Box::as_ref) } /// Returns all provider names. diff --git a/src/crates/core/src/service/config/types.rs b/src/crates/core/src/service/config/types.rs index b75dda20..f15a9dcc 100644 --- a/src/crates/core/src/service/config/types.rs +++ b/src/crates/core/src/service/config/types.rs @@ -339,8 +339,10 @@ pub enum ModelCapability { /// Model category (for UI display and filtering). #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] +#[derive(Default)] pub enum ModelCategory { /// General chat model. + #[default] GeneralChat, /// Multimodal model (text + image understanding). Multimodal, @@ -356,15 +358,11 @@ pub enum ModelCategory { SpeechRecognition, } -impl Default for ModelCategory { - fn default() -> Self { - Self::GeneralChat - } -} /// Default model configuration. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] +#[derive(Default)] pub struct DefaultModelsConfig { /// Primary model ID (for complex tasks). pub primary: Option, @@ -380,18 +378,6 @@ pub struct DefaultModelsConfig { pub speech_recognition: Option, } -impl Default for DefaultModelsConfig { - fn default() -> Self { - Self { - primary: None, - fast: None, - search: None, - image_understanding: None, - image_generation: None, - speech_recognition: None, - } - } -} /// AI configuration. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -876,6 +862,7 @@ pub struct AIModelConfig { /// Proxy configuration. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] +#[derive(Default)] pub struct ProxyConfig { /// Whether the proxy is enabled. pub enabled: bool, @@ -1230,16 +1217,6 @@ impl Default for AIConfig { } } -impl Default for ProxyConfig { - fn default() -> Self { - Self { - enabled: false, - url: String::new(), - username: None, - password: None, - } - } -} impl Default for AIModelConfig { fn default() -> Self { diff --git a/src/crates/core/src/service/diff/service.rs b/src/crates/core/src/service/diff/service.rs index 7c6250ac..820ea1f7 100644 --- a/src/crates/core/src/service/diff/service.rs +++ b/src/crates/core/src/service/diff/service.rs @@ -20,6 +20,7 @@ impl DiffService { } /// Creates with default configuration. + #[allow(clippy::should_implement_trait)] pub fn default() -> Self { Self::new(DiffConfig::new()) } diff --git a/src/crates/core/src/service/diff/types.rs b/src/crates/core/src/service/diff/types.rs index 063ad393..3c0df60f 100644 --- a/src/crates/core/src/service/diff/types.rs +++ b/src/crates/core/src/service/diff/types.rs @@ -65,6 +65,7 @@ pub struct DiffHunk { /// Diff result #[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default)] pub struct DiffResult { /// Hunk list pub hunks: Vec, @@ -76,16 +77,6 @@ pub struct DiffResult { pub changes: usize, } -impl Default for DiffResult { - fn default() -> Self { - Self { - hunks: Vec::new(), - additions: 0, - deletions: 0, - changes: 0, - } - } -} /// Diff computation options #[derive(Debug, Clone, Default, Serialize, Deserialize)] diff --git a/src/crates/core/src/service/filesystem/service.rs b/src/crates/core/src/service/filesystem/service.rs index d2ea1a0d..032be276 100644 --- a/src/crates/core/src/service/filesystem/service.rs +++ b/src/crates/core/src/service/filesystem/service.rs @@ -26,6 +26,7 @@ impl FileSystemService { } /// Creates the default service. + #[allow(clippy::should_implement_trait)] pub fn default() -> Self { Self::new(FileSystemConfig::default()) } @@ -44,7 +45,7 @@ impl FileSystemService { self.file_tree_service .build_tree_with_remote_hint(root_path, preferred_remote_connection_id) .await - .map_err(|e| BitFunError::service(e)) + .map_err(BitFunError::service) } /// Scans a directory and returns a detailed result. @@ -78,7 +79,7 @@ impl FileSystemService { self.file_tree_service .get_directory_contents_with_remote_hint(path, preferred_remote_connection_id) .await - .map_err(|e| BitFunError::service(e)) + .map_err(BitFunError::service) } /// Searches files. diff --git a/src/crates/core/src/service/filesystem/types.rs b/src/crates/core/src/service/filesystem/types.rs index d0032f76..4d607ea8 100644 --- a/src/crates/core/src/service/filesystem/types.rs +++ b/src/crates/core/src/service/filesystem/types.rs @@ -5,19 +5,12 @@ use serde::{Deserialize, Serialize}; /// File system service configuration #[derive(Debug, Clone)] +#[derive(Default)] pub struct FileSystemConfig { pub tree_options: FileTreeOptions, pub operation_options: FileOperationOptions, } -impl Default for FileSystemConfig { - fn default() -> Self { - Self { - tree_options: FileTreeOptions::default(), - operation_options: FileOperationOptions::default(), - } - } -} /// Directory scan result #[derive(Debug, Serialize, Deserialize)] diff --git a/src/crates/core/src/service/git/git_service.rs b/src/crates/core/src/service/git/git_service.rs index afd70e66..e4adfde1 100644 --- a/src/crates/core/src/service/git/git_service.rs +++ b/src/crates/core/src/service/git/git_service.rs @@ -11,6 +11,8 @@ use tokio::time::timeout; pub struct GitService; +type CommitStats = (Option, Option, Option); + impl GitService { /// Checks whether the path is a Git repository. pub async fn is_repository>(path: P) -> Result { @@ -232,7 +234,7 @@ impl GitService { for branch in &mut branches { if !branch.remote { branch.stats = Self::calculate_branch_stats(&repo, &branch.name).ok(); - branch.is_stale = Some(Self::is_branch_stale(&branch)); + branch.is_stale = Some(Self::is_branch_stale(branch)); branch.can_merge = Self::can_merge_safely(&repo, &branch.name).ok(); branch.has_conflicts = branch.can_merge.map(|can| !can); } @@ -265,7 +267,7 @@ impl GitService { } /// Analyzes branch relationships. - fn analyze_branch_relations(branches: &mut Vec) -> Result<(), GitError> { + fn analyze_branch_relations(branches: &mut [GitBranch]) -> Result<(), GitError> { let main_branches = ["main", "master", "develop"]; let available_main_branches: Vec = branches @@ -289,7 +291,7 @@ impl GitService { if let Some(base) = &branch.base_branch { child_map .entry(base.clone()) - .or_insert_with(Vec::new) + .or_default() .push(branch.name.clone()); } } @@ -340,11 +342,7 @@ impl GitService { /// Checks whether a branch is stale. fn is_branch_stale(branch: &GitBranch) -> bool { - if let Some(_last_commit_date) = &branch.last_commit_date { - false - } else { - true - } + !matches!(&branch.last_commit_date, Some(_last_commit_date)) } /// Checks whether a branch can be merged safely. @@ -856,7 +854,7 @@ impl GitService { fn get_commit_stats( _repo: &Repository, _commit: &Commit, - ) -> Result<(Option, Option, Option), GitError> { + ) -> Result { Ok((None, None, None)) } @@ -1068,7 +1066,7 @@ impl GitService { let worktree_path_str = worktree_path.to_string_lossy().to_string(); if !worktree_dir.exists() { - std::fs::create_dir_all(&worktree_dir).map_err(|e| GitError::IoError(e))?; + std::fs::create_dir_all(&worktree_dir).map_err(GitError::IoError)?; } let args = if create_branch { diff --git a/src/crates/core/src/service/git/git_utils.rs b/src/crates/core/src/service/git/git_utils.rs index b23bc298..d6bcacaa 100644 --- a/src/crates/core/src/service/git/git_utils.rs +++ b/src/crates/core/src/service/git/git_utils.rs @@ -250,10 +250,10 @@ pub fn parse_branch_line(line: &str) -> Option<(String, bool)> { return None; } - if trimmed.starts_with("* ") { - Some((trimmed[2..].to_string(), true)) - } else if trimmed.starts_with(" ") { - Some((trimmed[2..].to_string(), false)) + if let Some(stripped) = trimmed.strip_prefix("* ") { + Some((stripped.to_string(), true)) + } else if let Some(stripped) = trimmed.strip_prefix(" ") { + Some((stripped.to_string(), false)) } else { Some((trimmed.to_string(), false)) } diff --git a/src/crates/core/src/service/git/graph.rs b/src/crates/core/src/service/git/graph.rs index 60bf2420..46d62517 100644 --- a/src/crates/core/src/service/git/graph.rs +++ b/src/crates/core/src/service/git/graph.rs @@ -190,7 +190,7 @@ pub fn build_git_graph_for_branch( let parent_hash = parent_id.to_string(); children_map .entry(parent_hash) - .or_insert_with(Vec::new) + .or_default() .push(hash.clone()); } } @@ -251,11 +251,10 @@ fn collect_refs(repo: &Repository) -> Result>, git if let Some(oid) = reference.target() { let hash = oid.to_string(); - let is_current = current_branch.as_ref().map_or(false, |cb| cb == name); + let is_current = current_branch.as_ref().is_some_and(|cb| cb == name); let is_head = head .as_ref() - .and_then(|h| h.target()) - .map_or(false, |h| h == oid); + .and_then(|h| h.target()) == Some(oid); let graph_ref = GraphRef { name: name.to_string(), @@ -266,7 +265,7 @@ fn collect_refs(repo: &Repository) -> Result>, git refs_map .entry(hash) - .or_insert_with(Vec::new) + .or_default() .push(graph_ref); } } diff --git a/src/crates/core/src/service/i18n/types.rs b/src/crates/core/src/service/i18n/types.rs index e9849a23..c8dfb8ba 100644 --- a/src/crates/core/src/service/i18n/types.rs +++ b/src/crates/core/src/service/i18n/types.rs @@ -4,20 +4,15 @@ use serde::{Deserialize, Serialize}; /// Locale identifier. /// Currently supports Chinese and English only. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] pub enum LocaleId { #[serde(rename = "zh-CN")] + #[default] ZhCN, #[serde(rename = "en-US")] EnUS, } -impl Default for LocaleId { - fn default() -> Self { - LocaleId::ZhCN - } -} - impl LocaleId { /// Returns the locale identifier string. pub fn as_str(&self) -> &'static str { @@ -28,6 +23,7 @@ impl LocaleId { } /// Parses a locale identifier from a string. + #[allow(clippy::should_implement_trait)] pub fn from_str(s: &str) -> Option { match s { "zh-CN" => Some(LocaleId::ZhCN), diff --git a/src/crates/core/src/service/lsp/file_sync.rs b/src/crates/core/src/service/lsp/file_sync.rs index 5c03168b..83d564bd 100644 --- a/src/crates/core/src/service/lsp/file_sync.rs +++ b/src/crates/core/src/service/lsp/file_sync.rs @@ -343,7 +343,7 @@ impl LspFileSync { if let Some(ws) = workspace { grouped .entry(ws.clone()) - .or_insert_with(Vec::new) + .or_default() .push(path); } } diff --git a/src/crates/core/src/service/lsp/global.rs b/src/crates/core/src/service/lsp/global.rs index d02e673f..871454d2 100644 --- a/src/crates/core/src/service/lsp/global.rs +++ b/src/crates/core/src/service/lsp/global.rs @@ -12,12 +12,14 @@ use tokio::sync::RwLock; use super::file_sync::{FileSyncConfig, LspFileSync}; use super::{LspManager, WorkspaceLspManager}; +type WorkspaceManagerMap = HashMap>; +type GlobalWorkspaceManagers = Arc>; + /// Global LSP manager instance. static GLOBAL_LSP_MANAGER: OnceLock>> = OnceLock::new(); /// Global workspace manager mapping. -static WORKSPACE_MANAGERS: OnceLock>>>> = - OnceLock::new(); +static WORKSPACE_MANAGERS: OnceLock = OnceLock::new(); /// Global file sync manager. static GLOBAL_FILE_SYNC: OnceLock> = OnceLock::new(); diff --git a/src/crates/core/src/service/lsp/process.rs b/src/crates/core/src/service/lsp/process.rs index b6651f99..7bd4fb57 100644 --- a/src/crates/core/src/service/lsp/process.rs +++ b/src/crates/core/src/service/lsp/process.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Result}; use log::{debug, error, info, warn}; use std::collections::HashMap; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::Stdio; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; @@ -512,7 +512,7 @@ impl LspServerProcess { let response_message = super::types::JsonRpcMessage::Response(response); let mut stdin_lock = stdin.write().await; if let Err(e) = - super::protocol::write_message(&mut *stdin_lock, &response_message) + super::protocol::write_message(&mut stdin_lock, &response_message) .await { error!( @@ -532,7 +532,7 @@ impl LspServerProcess { let response_message = super::types::JsonRpcMessage::Response(response); let mut stdin_lock = stdin.write().await; if let Err(e) = - super::protocol::write_message(&mut *stdin_lock, &response_message) + super::protocol::write_message(&mut stdin_lock, &response_message) .await { error!( @@ -552,7 +552,7 @@ impl LspServerProcess { let response_message = super::types::JsonRpcMessage::Response(response); let mut stdin_lock = stdin.write().await; if let Err(e) = - super::protocol::write_message(&mut *stdin_lock, &response_message) + super::protocol::write_message(&mut stdin_lock, &response_message) .await { error!("[{}] Failed to send configuration response: {}", id, e); @@ -575,7 +575,7 @@ impl LspServerProcess { let response_message = super::types::JsonRpcMessage::Response(response); let mut stdin_lock = stdin.write().await; if let Err(e) = - super::protocol::write_message(&mut *stdin_lock, &response_message) + super::protocol::write_message(&mut stdin_lock, &response_message) .await { error!("[{}] Failed to send error response: {}", id, e); @@ -610,7 +610,7 @@ impl LspServerProcess { { let mut stdin = self.stdin.write().await; - write_message(&mut *stdin, &message).await?; + write_message(&mut stdin, &message).await?; } let response = timeout(Duration::from_secs(60), rx).await.map_err(|_| { @@ -634,7 +634,7 @@ impl LspServerProcess { let message = create_notification(method_str, params); let mut stdin = self.stdin.write().await; - write_message(&mut *stdin, &message).await?; + write_message(&mut stdin, &message).await?; Ok(()) } @@ -855,7 +855,7 @@ impl LspServerProcess { } /// Detects the runtime type. - fn detect_runtime_type(config: &ServerConfig, server_bin: &PathBuf) -> RuntimeType { + fn detect_runtime_type(config: &ServerConfig, server_bin: &Path) -> RuntimeType { if let Some(runtime) = &config.runtime { debug!("Runtime explicitly specified: {}", runtime); return match runtime.to_lowercase().as_str() { diff --git a/src/crates/core/src/service/lsp/project_detector.rs b/src/crates/core/src/service/lsp/project_detector.rs index d1ee7800..c1976f02 100644 --- a/src/crates/core/src/service/lsp/project_detector.rs +++ b/src/crates/core/src/service/lsp/project_detector.rs @@ -17,6 +17,7 @@ use tokio::fs; /// Project information. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +#[derive(Default)] pub struct ProjectInfo { /// Detected languages. pub languages: Vec, @@ -30,17 +31,6 @@ pub struct ProjectInfo { pub total_files: usize, } -impl Default for ProjectInfo { - fn default() -> Self { - Self { - languages: Vec::new(), - primary_language: None, - file_counts: HashMap::new(), - project_types: Vec::new(), - total_files: 0, - } - } -} /// Project type detector. pub struct ProjectDetector; diff --git a/src/crates/core/src/service/lsp/workspace_manager.rs b/src/crates/core/src/service/lsp/workspace_manager.rs index 981e1e2c..26191a09 100644 --- a/src/crates/core/src/service/lsp/workspace_manager.rs +++ b/src/crates/core/src/service/lsp/workspace_manager.rs @@ -476,11 +476,7 @@ impl WorkspaceLspManager { let status = { let states = self.server_states.read().await; - if let Some(state) = states.get(language) { - Some(state.status.clone()) - } else { - None - } + states.get(language).map(|state| state.status.clone()) }; if let Some(status) = status { @@ -737,7 +733,7 @@ impl WorkspaceLspManager { if let Some(state) = states.get_mut(&language) { state.status = ServerStatus::Failed; state.last_error = - Some(format!("Server process crashed or became unresponsive")); + Some("Server process crashed or became unresponsive".to_string()); } } diff --git a/src/crates/core/src/service/mcp/config/json_config.rs b/src/crates/core/src/service/mcp/config/json_config.rs index 392395e9..4f6e0200 100644 --- a/src/crates/core/src/service/mcp/config/json_config.rs +++ b/src/crates/core/src/service/mcp/config/json_config.rs @@ -89,10 +89,9 @@ impl MCPConfigService { return Err(BitFunError::validation(error_msg.to_string())); } - if !config_value + if config_value .get("mcpServers") - .and_then(|v| v.as_object()) - .is_some() + .and_then(|v| v.as_object()).is_none() { let error_msg = "'mcpServers' field must be an object"; error!("{}", error_msg); diff --git a/src/crates/core/src/service/mcp/protocol/transport_remote.rs b/src/crates/core/src/service/mcp/protocol/transport_remote.rs index 00caa192..536d0cf0 100644 --- a/src/crates/core/src/service/mcp/protocol/transport_remote.rs +++ b/src/crates/core/src/service/mcp/protocol/transport_remote.rs @@ -496,7 +496,7 @@ impl RemoteMCPTransport { let info = service.peer().peer_info().ok_or_else(|| { BitFunError::MCPError("Handshake succeeded but server info missing".to_string()) })?; - return Ok(map_initialize_result(info)); + Ok(map_initialize_result(info)) } ClientState::Connecting { transport } => { let Some(transport) = transport.take() else { @@ -813,13 +813,13 @@ fn map_prompt_message(message: rmcp::model::PromptMessage) -> MCPPromptMessage { let content = match message.content { rmcp::model::PromptMessageContent::Text { text } => { - MCPPromptMessageContent::Block(MCPPromptMessageContentBlock::Text { text }) + MCPPromptMessageContent::Block(Box::new(MCPPromptMessageContentBlock::Text { text })) } rmcp::model::PromptMessageContent::Image { image } => { - MCPPromptMessageContent::Block(MCPPromptMessageContentBlock::Image { + MCPPromptMessageContent::Block(Box::new(MCPPromptMessageContentBlock::Image { data: image.data.clone(), mime_type: image.mime_type.clone(), - }) + })) } rmcp::model::PromptMessageContent::Resource { resource } => { let mut mapped = map_resource_content(resource.resource.clone()); @@ -827,17 +827,17 @@ fn map_prompt_message(message: rmcp::model::PromptMessage) -> MCPPromptMessage { mapped.meta = map_optional_via_json(resource.meta.as_ref()); } mapped.annotations = map_annotations(resource.annotations.as_ref()); - MCPPromptMessageContent::Block(MCPPromptMessageContentBlock::Resource { - resource: mapped, - }) + MCPPromptMessageContent::Block(Box::new(MCPPromptMessageContentBlock::Resource { + resource: Box::new(mapped), + })) } rmcp::model::PromptMessageContent::ResourceLink { link } => { - MCPPromptMessageContent::Block(MCPPromptMessageContentBlock::ResourceLink { + MCPPromptMessageContent::Block(Box::new(MCPPromptMessageContentBlock::ResourceLink { uri: link.uri.clone(), name: Some(link.name.clone()), description: link.description.clone(), mime_type: link.mime_type.clone(), - }) + })) } }; @@ -874,7 +874,7 @@ fn map_content_block(content: Content) -> Option { mime_type: image.mime_type, }), rmcp::model::RawContent::Resource(resource) => Some(MCPToolResultContent::Resource { - resource: map_resource_content(resource.resource), + resource: Box::new(map_resource_content(resource.resource)), }), rmcp::model::RawContent::Audio(audio) => Some(MCPToolResultContent::Audio { data: audio.data, diff --git a/src/crates/core/src/service/mcp/protocol/types.rs b/src/crates/core/src/service/mcp/protocol/types.rs index b00351f5..9ec34d12 100644 --- a/src/crates/core/src/service/mcp/protocol/types.rs +++ b/src/crates/core/src/service/mcp/protocol/types.rs @@ -259,7 +259,7 @@ pub enum MCPPromptMessageContent { /// Legacy: plain string content from older servers. Plain(String), /// Structured content block. - Block(MCPPromptMessageContentBlock), + Block(Box), } /// Structured content block types for prompt messages. @@ -283,7 +283,7 @@ pub enum MCPPromptMessageContentBlock { mime_type: Option, }, #[serde(rename = "resource")] - Resource { resource: MCPResourceContent }, + Resource { resource: Box }, } impl MCPPromptMessageContent { @@ -291,33 +291,24 @@ impl MCPPromptMessageContent { pub fn text_or_placeholder(&self) -> String { match self { MCPPromptMessageContent::Plain(s) => s.clone(), - MCPPromptMessageContent::Block(MCPPromptMessageContentBlock::Text { text }) => { - text.clone() - } - MCPPromptMessageContent::Block(MCPPromptMessageContentBlock::Image { - mime_type, - .. - }) => { - format!("[Image: {}]", mime_type) - } - MCPPromptMessageContent::Block(MCPPromptMessageContentBlock::Audio { - mime_type, - .. - }) => { - format!("[Audio: {}]", mime_type) - } - MCPPromptMessageContent::Block(MCPPromptMessageContentBlock::ResourceLink { - uri, - name, - .. - }) => name - .as_ref() - .map_or_else(|| format!("[Resource Link: {}]", uri), |n| { - format!("[Resource Link: {} ({})]", n, uri) - }), - MCPPromptMessageContent::Block(MCPPromptMessageContentBlock::Resource { resource }) => { - format!("[Resource: {}]", resource.uri) - } + MCPPromptMessageContent::Block(block) => match block.as_ref() { + MCPPromptMessageContentBlock::Text { text } => text.clone(), + MCPPromptMessageContentBlock::Image { mime_type, .. } => { + format!("[Image: {}]", mime_type) + } + MCPPromptMessageContentBlock::Audio { mime_type, .. } => { + format!("[Audio: {}]", mime_type) + } + MCPPromptMessageContentBlock::ResourceLink { uri, name, .. } => { + name.as_ref().map_or_else( + || format!("[Resource Link: {}]", uri), + |n| format!("[Resource Link: {} ({})]", n, uri), + ) + } + MCPPromptMessageContentBlock::Resource { resource } => { + format!("[Resource: {}]", resource.uri) + } + }, } } @@ -330,13 +321,14 @@ impl MCPPromptMessageContent { *s = s.replace(&placeholder, value); } } - MCPPromptMessageContent::Block(MCPPromptMessageContentBlock::Text { text }) => { - for (key, value) in arguments { - let placeholder = format!("{{{{{}}}}}", key); - *text = text.replace(&placeholder, value); + MCPPromptMessageContent::Block(block) => { + if let MCPPromptMessageContentBlock::Text { text } = block.as_mut() { + for (key, value) in arguments { + let placeholder = format!("{{{{{}}}}}", key); + *text = text.replace(&placeholder, value); + } } } - _ => {} } } } @@ -456,7 +448,7 @@ pub enum MCPToolResultContent { }, /// Embedded resource content. #[serde(rename = "resource")] - Resource { resource: MCPResourceContent }, + Resource { resource: Box }, } /// MCP message type (based on JSON-RPC 2.0). diff --git a/src/crates/core/src/service/project_context/service.rs b/src/crates/core/src/service/project_context/service.rs index eaba9ff4..2c4c2f86 100644 --- a/src/crates/core/src/service/project_context/service.rs +++ b/src/crates/core/src/service/project_context/service.rs @@ -336,7 +336,7 @@ impl ProjectContextService { pub async fn cancel_generate_document(&self, doc_id: &str) -> BitFunResult<()> { super::cancellation::cancel_generation(doc_id) .await - .map_err(|e| BitFunError::service(e)) + .map_err(BitFunError::service) } /// Parses a filter string. @@ -435,7 +435,7 @@ impl ProjectContextService { workspace: &Path, filter: Option<&str>, ) -> BitFunResult { - let filter = filter.and_then(|f| Self::parse_filter(f)); + let filter = filter.and_then(Self::parse_filter); let config = self.load_config_and_cleanup(workspace).await?; let statuses = self.get_document_statuses(workspace).await?; diff --git a/src/crates/core/src/service/remote_connect/bot/command_router.rs b/src/crates/core/src/service/remote_connect/bot/command_router.rs index 3f1676b1..f090a62a 100644 --- a/src/crates/core/src/service/remote_connect/bot/command_router.rs +++ b/src/crates/core/src/service/remote_connect/bot/command_router.rs @@ -1972,7 +1972,7 @@ async fn select_session( )); reply.push_str(&format!( "{}: {assistant_text}\n\n", - if language.is_chinese() { "AI" } else { "AI" } + "AI" )); reply.push_str(if language.is_chinese() { "你可以继续对话。" @@ -2147,6 +2147,7 @@ async fn submit_question_answers(tool_id: &str, answers: &[Value]) -> HandleResu } } +#[allow(clippy::too_many_arguments)] async fn handle_question_reply( state: &mut BotChatState, tool_id: String, diff --git a/src/crates/core/src/service/remote_connect/bot/feishu.rs b/src/crates/core/src/service/remote_connect/bot/feishu.rs index 9153f81a..abca311b 100644 --- a/src/crates/core/src/service/remote_connect/bot/feishu.rs +++ b/src/crates/core/src/service/remote_connect/bot/feishu.rs @@ -21,6 +21,12 @@ use super::command_router::{ }; use super::{load_bot_persistence, save_bot_persistence, BotConfig, SavedBotConnection}; +type FeishuWsStream = tokio_tungstenite::WebSocketStream< + tokio_tungstenite::MaybeTlsStream, +>; +type FeishuWsWrite = futures::stream::SplitSink; +type SharedFeishuWsWrite = Arc>; + // ── Minimal protobuf codec for Feishu WebSocket binary protocol ───────── mod pb { @@ -160,7 +166,7 @@ mod pb { } fn write_varint(buf: &mut Vec, field: u32, val: u64) { - buf.extend(encode_varint(((field << 3) | 0) as u64)); + buf.extend(encode_varint((field << 3) as u64)); buf.extend(encode_varint(val)); } @@ -1135,16 +1141,7 @@ impl FeishuBot { async fn handle_data_frame_for_pairing( &self, frame: &pb::Frame, - write: &Arc< - RwLock< - futures::stream::SplitSink< - tokio_tungstenite::WebSocketStream< - tokio_tungstenite::MaybeTlsStream, - >, - WsMessage, - >, - >, - >, + write: &SharedFeishuWsWrite, ) -> Option { let msg_type = frame.get_header("type").unwrap_or(""); if msg_type != "event" { @@ -1519,8 +1516,8 @@ impl FeishuBot { } // Intercept file download callbacks before normal command routing. - if text.starts_with("download_file:") { - let token = text["download_file:".len()..].trim().to_string(); + if let Some(stripped) = text.strip_prefix("download_file:") { + let token = stripped.trim().to_string(); drop(states); self.handle_download_request(chat_id, &token).await; return; diff --git a/src/crates/core/src/service/remote_connect/bot/mod.rs b/src/crates/core/src/service/remote_connect/bot/mod.rs index 3fbe03fa..afe8d037 100644 --- a/src/crates/core/src/service/remote_connect/bot/mod.rs +++ b/src/crates/core/src/service/remote_connect/bot/mod.rs @@ -385,7 +385,7 @@ pub fn extract_computer_file_paths( .find(|c: char| c.is_whitespace() || matches!(c, '<' | '>' | '(' | ')' | '"' | '\'')) .unwrap_or(rest.len()); let raw_suffix = - rest[..end].trim_end_matches(|c: char| matches!(c, '.' | ',' | ';' | ':' | ')' | ']')); + rest[..end].trim_end_matches(['.', ',', ';', ':', ')', ']']); if !raw_suffix.is_empty() { push_if_existing_file(&format!("{PREFIX}{raw_suffix}"), &mut paths, workspace_root); } @@ -437,7 +437,7 @@ pub fn extract_downloadable_file_paths( }) .unwrap_or(rest.len()); let raw_suffix = rest[..end] - .trim_end_matches(|c: char| matches!(c, '.' | ',' | ';' | ':' | ')' | ']')); + .trim_end_matches(['.', ',', ';', ':', ')', ']']); if !raw_suffix.is_empty() { let resolve_input = if prefix == "computer://" { format!("{prefix}{raw_suffix}") @@ -469,11 +469,9 @@ pub fn extract_downloadable_file_paths( && !href.starts_with("tel:") && !href.starts_with('#') && !href.starts_with("//") - { - if is_downloadable_by_extension(href) { + && is_downloadable_by_extension(href) { push_if_existing_file(href, &mut paths, workspace_root); } - } i = href_start + rel_end + 1; } else { i += 2; diff --git a/src/crates/core/src/service/remote_connect/bot/telegram.rs b/src/crates/core/src/service/remote_connect/bot/telegram.rs index d0c89d2e..0688b651 100644 --- a/src/crates/core/src/service/remote_connect/bot/telegram.rs +++ b/src/crates/core/src/service/remote_connect/bot/telegram.rs @@ -109,7 +109,7 @@ impl TelegramBot { pub async fn send_message(&self, chat_id: i64, text: &str) -> Result<()> { let client = reqwest::Client::new(); let resp = client - .post(&self.api_url("sendMessage")) + .post(self.api_url("sendMessage")) .json(&serde_json::json!({ "chat_id": chat_id, "text": text, @@ -152,7 +152,7 @@ impl TelegramBot { let client = reqwest::Client::new(); let resp = client - .post(&self.api_url("sendMessage")) + .post(self.api_url("sendMessage")) .json(&serde_json::json!({ "chat_id": chat_id, "text": text, @@ -188,7 +188,7 @@ impl TelegramBot { let client = reqwest::Client::new(); let resp = client - .post(&self.api_url("sendDocument")) + .post(self.api_url("sendDocument")) .multipart(form) .send() .await?; @@ -280,7 +280,7 @@ impl TelegramBot { async fn answer_callback_query(&self, callback_query_id: &str) { let client = reqwest::Client::new(); let _ = client - .post(&self.api_url("answerCallbackQuery")) + .post(self.api_url("answerCallbackQuery")) .json(&serde_json::json!({ "callback_query_id": callback_query_id })) .send() .await; @@ -345,7 +345,7 @@ impl TelegramBot { ] }); let resp = client - .post(&self.api_url("setMyCommands")) + .post(self.api_url("setMyCommands")) .json(&commands) .send() .await?; @@ -443,7 +443,7 @@ impl TelegramBot { .build()?; let resp = client - .get(&self.api_url("getUpdates")) + .get(self.api_url("getUpdates")) .query(&[ ("offset", (offset + 1).to_string()), ("timeout", "30".to_string()), @@ -668,8 +668,8 @@ impl TelegramBot { } // Intercept file download callbacks before normal command routing. - if text.starts_with("download_file:") { - let token = text["download_file:".len()..].trim().to_string(); + if let Some(stripped) = text.strip_prefix("download_file:") { + let token = stripped.trim().to_string(); drop(states); self.handle_download_request(chat_id, &token).await; return; diff --git a/src/crates/core/src/service/remote_connect/bot/weixin.rs b/src/crates/core/src/service/remote_connect/bot/weixin.rs index 497f534d..986c6fd3 100644 --- a/src/crates/core/src/service/remote_connect/bot/weixin.rs +++ b/src/crates/core/src/service/remote_connect/bot/weixin.rs @@ -59,7 +59,7 @@ fn encrypt_aes_128_ecb_pkcs7(plaintext: &[u8], key: &[u8; 16]) -> Vec { let pad_len = 16 - (plaintext.len() % 16); let pad_len = if pad_len == 0 { 16 } else { pad_len }; let mut buf = plaintext.to_vec(); - buf.extend(std::iter::repeat(pad_len as u8).take(pad_len)); + buf.extend(std::iter::repeat_n(pad_len as u8, pad_len)); let mut out = Vec::with_capacity(buf.len()); for chunk in buf.chunks_exact(16) { let mut block = aes::cipher::generic_array::GenericArray::clone_from_slice(chunk); @@ -94,7 +94,7 @@ fn build_cdn_download_url(cdn_base: &str, encrypted_query_param: &str) -> String } fn decrypt_aes_128_ecb_pkcs7(ciphertext: &[u8], key: &[u8; 16]) -> Result> { - if ciphertext.is_empty() || ciphertext.len() % 16 != 0 { + if ciphertext.is_empty() || !ciphertext.len().is_multiple_of(16) { return Err(anyhow!( "invalid ciphertext length {}", ciphertext.len() @@ -824,6 +824,7 @@ impl WeixinBot { } /// `ilink/bot/getuploadurl` — returns `upload_param` for CDN POST. + #[allow(clippy::too_many_arguments)] async fn ilink_get_upload_url( &self, to_user_id: &str, @@ -1212,7 +1213,7 @@ impl WeixinBot { } fn is_weixin_media_item_type(type_id: i64) -> bool { - matches!(type_id, 2 | 3 | 4 | 5) + matches!(type_id, 2..=5) } fn body_from_item_list(items: &[Value]) -> String { @@ -1333,11 +1334,7 @@ impl WeixinBot { let mut s = header.to_string(); for (i, a) in actions.iter().enumerate() { let n = i + 1; - if language.is_chinese() { - s.push_str(&format!("{n}. {} → {}\n", a.label, a.command)); - } else { - s.push_str(&format!("{n}. {} → {}\n", a.label, a.command)); - } + s.push_str(&format!("{n}. {} → {}\n", a.label, a.command)); } s } @@ -1672,8 +1669,8 @@ impl WeixinBot { } let trimmed = text.trim(); - if trimmed.starts_with("download_file:") { - let token = trimmed["download_file:".len()..].trim().to_string(); + if let Some(stripped) = trimmed.strip_prefix("download_file:") { + let token = stripped.trim().to_string(); let workspace_root = state.current_workspace.clone(); drop(states); self.handle_download_request(&peer_id, &token, workspace_root) diff --git a/src/crates/core/src/service/remote_connect/ngrok.rs b/src/crates/core/src/service/remote_connect/ngrok.rs index 291a1801..9966ac23 100644 --- a/src/crates/core/src/service/remote_connect/ngrok.rs +++ b/src/crates/core/src/service/remote_connect/ngrok.rs @@ -41,13 +41,9 @@ fn find_ngrok() -> Option { PathBuf::from("C:\\ngrok\\ngrok.exe"), ]; - for path in candidates { - if path.exists() && path.is_file() { - return Some(path); - } - } - - None + candidates + .into_iter() + .find(|path| path.exists() && path.is_file()) } /// Check if ngrok is installed and available. diff --git a/src/crates/core/src/service/remote_connect/remote_server.rs b/src/crates/core/src/service/remote_connect/remote_server.rs index a60fe585..21f8d5c0 100644 --- a/src/crates/core/src/service/remote_connect/remote_server.rs +++ b/src/crates/core/src/service/remote_connect/remote_server.rs @@ -433,7 +433,7 @@ pub enum RemoteResponse { #[serde(skip_serializing_if = "Option::is_none")] active_turn: Option, #[serde(skip_serializing_if = "Option::is_none")] - model_catalog: Option, + model_catalog: Box>, }, AnswerAccepted, InteractionAccepted { @@ -1198,7 +1198,7 @@ impl RemoteSessionStateTracker { if let Some(item) = state.active_items.iter_mut().rev().find(|i| { i.item_type == "tool" - && i.tool.as_ref().map_or(false, |t| { + && i.tool.as_ref().is_some_and(|t| { t.id == resolved_id || (allow_name_fallback && t.name == tool_name) }) }) { @@ -1233,7 +1233,7 @@ impl RemoteSessionStateTracker { .. } => subagent_parent_info .as_ref() - .map_or(false, |p| p.session_id == self.target_session_id), + .is_some_and(|p| p.session_id == self.target_session_id), _ => false, } } else { @@ -1333,7 +1333,7 @@ impl RemoteSessionStateTracker { } "ConfirmationNeeded" => { let params = val.get("params").cloned(); - let input_preview = params.as_ref().and_then(|v| make_slim_params(v)); + let input_preview = params.as_ref().and_then(make_slim_params); Self::upsert_active_tool( &mut s, &tool_id, @@ -1346,7 +1346,7 @@ impl RemoteSessionStateTracker { } "Started" => { let params = val.get("params").cloned(); - let input_preview = params.as_ref().and_then(|v| make_slim_params(v)); + let input_preview = params.as_ref().and_then(make_slim_params); let tool_input = if tool_name == "AskUserQuestion" || tool_name == "Task" || tool_name == "TodoWrite" @@ -1403,7 +1403,7 @@ impl RemoteSessionStateTracker { } if let Some(item) = s.active_items.iter_mut().rev().find(|i| { i.item_type == "tool" - && i.tool.as_ref().map_or(false, |t| { + && i.tool.as_ref().is_some_and(|t| { (t.id == tool_id || (allow_name_fallback && t.name == tool_name)) && t.status == "running" @@ -1430,7 +1430,7 @@ impl RemoteSessionStateTracker { } if let Some(item) = s.active_items.iter_mut().rev().find(|i| { i.item_type == "tool" - && i.tool.as_ref().map_or(false, |t| { + && i.tool.as_ref().is_some_and(|t| { (t.id == tool_id || (allow_name_fallback && t.name == tool_name)) && t.status == "running" @@ -1459,7 +1459,7 @@ impl RemoteSessionStateTracker { } if let Some(item) = s.active_items.iter_mut().rev().find(|i| { i.item_type == "tool" - && i.tool.as_ref().map_or(false, |t| { + && i.tool.as_ref().is_some_and(|t| { (t.id == tool_id || (allow_name_fallback && t.name == tool_name)) && matches!( @@ -2037,7 +2037,7 @@ impl RemoteServer { new_messages: None, total_msg_count: None, active_turn: None, - model_catalog: None, + model_catalog: Box::new(None), }; } @@ -2058,11 +2058,11 @@ impl RemoteServer { new_messages: None, total_msg_count: None, active_turn, - model_catalog: if should_send_model_catalog { + model_catalog: Box::new(if should_send_model_catalog { current_model_catalog } else { None - }, + }), }; } @@ -2120,11 +2120,11 @@ impl RemoteServer { new_messages: send_msgs, total_msg_count: send_total, active_turn, - model_catalog: if should_send_model_catalog { + model_catalog: Box::new(if should_send_model_catalog { current_model_catalog } else { None - }, + }), } } diff --git a/src/crates/core/src/service/remote_ssh/manager.rs b/src/crates/core/src/service/remote_ssh/manager.rs index a83cf5fc..1e4b0e64 100644 --- a/src/crates/core/src/service/remote_ssh/manager.rs +++ b/src/crates/core/src/service/remote_ssh/manager.rs @@ -65,7 +65,7 @@ struct SSHHandler { /// Expected host key (if connecting to known host) expected_key: Option<(String, u16, PublicKey)>, /// Callback for new host key verification - verify_callback: Option bool + Send + Sync>>, + verify_callback: Option>, /// Known hosts storage for verification known_hosts: Option>>>, /// Host info for known hosts lookup @@ -78,6 +78,8 @@ struct SSHHandler { disconnect_reason: Arc>>, } +type HostKeyVerifyCallback = dyn Fn(String, u16, &PublicKey) -> bool + Send + Sync; + impl SSHHandler { #[allow(dead_code)] fn new() -> Self { @@ -531,7 +533,7 @@ impl SSHConnectionManager { let has_proxy_command = ssh_cfg_has(&host_settings, "ProxyCommand"); - return SSHConfigLookupResult { + SSHConfigLookupResult { found: true, config: Some(SSHConfigEntry { host: host.to_string(), @@ -541,7 +543,7 @@ impl SSHConnectionManager { identity_file, agent: if has_proxy_command { None } else { Some(true) }, }), - }; + } } #[cfg(not(feature = "ssh_config"))] diff --git a/src/crates/core/src/service/remote_ssh/remote_fs.rs b/src/crates/core/src/service/remote_ssh/remote_fs.rs index 13c28b8f..47ded685 100644 --- a/src/crates/core/src/service/remote_ssh/remote_fs.rs +++ b/src/crates/core/src/service/remote_ssh/remote_fs.rs @@ -206,10 +206,7 @@ impl RemoteFileService { .unwrap_or_else(|| path.to_string()); // Check if this is a directory - let is_dir = match self.exists(connection_id, path).await { - Ok(exists) => exists, - Err(_) => false, - }; + let is_dir: bool = self.exists(connection_id, path).await.unwrap_or_default(); // Check if it's a directory by trying to read it let is_dir = if is_dir { @@ -300,19 +297,16 @@ impl RemoteFileService { /// Remove a directory and its contents recursively via SFTP pub async fn remove_dir_all(&self, connection_id: &str, path: &str) -> anyhow::Result<()> { // First, delete all contents - match self.read_dir(connection_id, path).await { - Ok(entries) => { - for entry in entries { - let entry_path = entry.path.clone(); - if entry.is_dir { - Box::pin(self.remove_dir_all(connection_id, &entry_path)).await?; - } else { - let manager = self.get_manager(connection_id).await?; - manager.sftp_remove(connection_id, &entry_path).await?; - } + if let Ok(entries) = self.read_dir(connection_id, path).await { + for entry in entries { + let entry_path = entry.path.clone(); + if entry.is_dir { + Box::pin(self.remove_dir_all(connection_id, &entry_path)).await?; + } else { + let manager = self.get_manager(connection_id).await?; + manager.sftp_remove(connection_id, &entry_path).await?; } } - Err(_) => {} } // Then remove the directory itself diff --git a/src/crates/core/src/service/remote_ssh/remote_terminal.rs b/src/crates/core/src/service/remote_ssh/remote_terminal.rs index 5d49fa2c..8b8f6e10 100644 --- a/src/crates/core/src/service/remote_ssh/remote_terminal.rs +++ b/src/crates/core/src/service/remote_ssh/remote_terminal.rs @@ -86,6 +86,7 @@ impl RemoteTerminalManager { /// Returns a `CreateSessionResult` with a pre-subscribed output receiver. /// The owner task is spawned immediately — the output_rx is guaranteed to /// receive all data including the initial shell prompt. + #[allow(clippy::too_many_arguments)] pub async fn create_session( &self, session_id: Option, diff --git a/src/crates/core/src/service/remote_ssh/workspace_state.rs b/src/crates/core/src/service/remote_ssh/workspace_state.rs index e2dc62f3..070212a0 100644 --- a/src/crates/core/src/service/remote_ssh/workspace_state.rs +++ b/src/crates/core/src/service/remote_ssh/workspace_state.rs @@ -338,6 +338,12 @@ pub struct RemoteWorkspaceStateManager { terminal_manager: Arc>>, } +impl Default for RemoteWorkspaceStateManager { + fn default() -> Self { + Self::new() + } +} + impl RemoteWorkspaceStateManager { pub fn new() -> Self { Self { diff --git a/src/crates/core/src/service/session/types.rs b/src/crates/core/src/service/session/types.rs index d76ae6c1..d89246ce 100644 --- a/src/crates/core/src/service/session/types.rs +++ b/src/crates/core/src/service/session/types.rs @@ -172,16 +172,13 @@ pub struct DialogTurnData { /// Persisted dialog turn kind. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] +#[derive(Default)] pub enum DialogTurnKind { + #[default] UserDialog, ManualCompaction, } -impl Default for DialogTurnKind { - fn default() -> Self { - Self::UserDialog - } -} impl DialogTurnKind { pub fn is_model_visible(self) -> bool { @@ -380,6 +377,7 @@ pub struct SessionTranscriptIndexEntry { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] +#[derive(Default)] pub struct SessionTranscriptExportOptions { #[serde(default)] pub tools: bool, @@ -391,16 +389,6 @@ pub struct SessionTranscriptExportOptions { pub turns: Option>, } -impl Default for SessionTranscriptExportOptions { - fn default() -> Self { - Self { - tools: false, - tool_inputs: false, - thinking: false, - turns: None, - } - } -} #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] diff --git a/src/crates/core/src/service/snapshot/file_lock_manager.rs b/src/crates/core/src/service/snapshot/file_lock_manager.rs index e60cff98..c1207121 100644 --- a/src/crates/core/src/service/snapshot/file_lock_manager.rs +++ b/src/crates/core/src/service/snapshot/file_lock_manager.rs @@ -2,7 +2,7 @@ use crate::service::snapshot::types::{SnapshotError, SnapshotResult}; use log::{debug, info, warn}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::time::SystemTime; use tokio::sync::RwLock; @@ -203,7 +203,7 @@ impl FileLockManager { /// Adds an item to the waiting queue. async fn add_to_waiting_queue( &self, - file_path: &PathBuf, + file_path: &Path, session_id: &str, tool_name: &str, ) -> SnapshotResult<()> { @@ -216,7 +216,7 @@ impl FileLockManager { }; waiting_queue - .entry(file_path.clone()) + .entry(file_path.to_path_buf()) .or_insert_with(Vec::new) .push(queue_item); diff --git a/src/crates/core/src/service/snapshot/isolation_manager.rs b/src/crates/core/src/service/snapshot/isolation_manager.rs index 88a61095..381e5741 100644 --- a/src/crates/core/src/service/snapshot/isolation_manager.rs +++ b/src/crates/core/src/service/snapshot/isolation_manager.rs @@ -1,11 +1,10 @@ -use crate::service::snapshot::types::{SnapshotError, SnapshotResult}; +use crate::service::snapshot::types::{SnapshotError, SnapshotResult}; use log::{debug, info}; use std::fs::{self, OpenOptions}; use std::io::Write; use std::path::{Path, PathBuf}; /// Git isolation manager - pub struct IsolationManager { bitfun_dir: PathBuf, workspace_dir: PathBuf, @@ -98,13 +97,12 @@ impl IsolationManager { /// Verifies no Git operations are impacted. async fn verify_no_git_operations(&self) -> SnapshotResult<()> { let git_dir = self.workspace_dir.join(".git"); - if git_dir.exists() { - if self.bitfun_dir.starts_with(&git_dir) { + if git_dir.exists() + && self.bitfun_dir.starts_with(&git_dir) { return Err(SnapshotError::GitIsolationFailure( ".bitfun directory should not be inside .git directory".to_string(), )); } - } self.verify_isolation_integrity().await?; diff --git a/src/crates/core/src/service/snapshot/manager.rs b/src/crates/core/src/service/snapshot/manager.rs index 45506467..55dd1f21 100644 --- a/src/crates/core/src/service/snapshot/manager.rs +++ b/src/crates/core/src/service/snapshot/manager.rs @@ -366,7 +366,7 @@ impl Tool for WrappedTool { } fn user_facing_name(&self) -> String { - format!("{}", self.original_tool.user_facing_name()) + self.original_tool.user_facing_name().to_string() } async fn is_enabled(&self) -> bool { @@ -413,11 +413,11 @@ impl Tool for WrappedTool { options: &crate::agentic::tools::framework::ToolRenderOptions, ) -> String { let original_message = self.original_tool.render_tool_use_message(input, options); - format!("{}", original_message) + original_message.to_string() } fn render_tool_use_rejected_message(&self) -> String { - format!("{}", self.original_tool.render_tool_use_rejected_message()) + self.original_tool.render_tool_use_rejected_message().to_string() } fn render_tool_result_message(&self, output: &Value) -> String { diff --git a/src/crates/core/src/service/snapshot/service.rs b/src/crates/core/src/service/snapshot/service.rs index 9c386b49..575c1278 100644 --- a/src/crates/core/src/service/snapshot/service.rs +++ b/src/crates/core/src/service/snapshot/service.rs @@ -103,6 +103,7 @@ impl SnapshotService { } /// Intercept a tool call before it modifies the file system. + #[allow(clippy::too_many_arguments)] pub async fn intercept_file_modification( &self, session_id: &str, diff --git a/src/crates/core/src/service/snapshot/snapshot_core.rs b/src/crates/core/src/service/snapshot/snapshot_core.rs index c5bcbce8..9b54f6f6 100644 --- a/src/crates/core/src/service/snapshot/snapshot_core.rs +++ b/src/crates/core/src/service/snapshot/snapshot_core.rs @@ -114,6 +114,7 @@ impl SnapshotCore { } /// Start a file operation (before snapshot), returns operation_id. + #[allow(clippy::too_many_arguments)] pub async fn start_file_operation( &mut self, session_id: &str, @@ -856,7 +857,7 @@ impl SnapshotCore { }; let path = self.session_file_path(session_id); let data = - serde_json::to_string_pretty(session).map_err(|e| SnapshotError::Serialization(e))?; + serde_json::to_string_pretty(session).map_err(SnapshotError::Serialization)?; tokio::fs::write(path, data) .await .map_err(SnapshotError::Io)?; diff --git a/src/crates/core/src/service/terminal/src/api.rs b/src/crates/core/src/service/terminal/src/api.rs index 875692e8..ce06f856 100644 --- a/src/crates/core/src/service/terminal/src/api.rs +++ b/src/crates/core/src/service/terminal/src/api.rs @@ -1,4 +1,4 @@ -//! API module - Public interface for terminal operations +//! API module - Public interface for terminal operations //! //! This module provides the public API for external consumers (Tauri, WebSocket, etc.) //! It defines request/response types and the main service interface. @@ -512,7 +512,7 @@ impl TerminalApi { // Tauri-compatible commands (when used with Tauri) // ============================================================================ -/// Module for Tauri command integration +// Module for Tauri command integration // #[cfg(feature = "tauri")] // pub mod tauri_commands { // use super::*; diff --git a/src/crates/core/src/service/terminal/src/session/binding.rs b/src/crates/core/src/service/terminal/src/session/binding.rs index a3d2869a..26222b54 100644 --- a/src/crates/core/src/service/terminal/src/session/binding.rs +++ b/src/crates/core/src/service/terminal/src/session/binding.rs @@ -99,7 +99,7 @@ impl TerminalSessionBinding { format!( "term-{}-{}", &owner_id[..8.min(owner_id.len())], - uuid::Uuid::new_v4().to_string()[..8].to_string() + &uuid::Uuid::new_v4().to_string()[..8] ) }); diff --git a/src/crates/core/src/service/terminal/src/session/manager.rs b/src/crates/core/src/service/terminal/src/session/manager.rs index f0a24e88..a212f2e6 100644 --- a/src/crates/core/src/service/terminal/src/session/manager.rs +++ b/src/crates/core/src/service/terminal/src/session/manager.rs @@ -1,4 +1,4 @@ -//! Session Manager - Manages terminal sessions lifecycle +//! Session Manager - Manages terminal sessions lifecycle use std::collections::HashMap; use std::pin::Pin; @@ -378,6 +378,7 @@ impl SessionManager { } /// Create a new terminal session with shell integration + #[allow(clippy::too_many_arguments)] pub async fn create_session( &self, session_id: Option, @@ -404,6 +405,7 @@ impl SessionManager { } /// Create a new terminal session with optional shell integration + #[allow(clippy::too_many_arguments)] pub async fn create_session_with_options( &self, session_id: Option, diff --git a/src/crates/core/src/service/terminal/src/session/mod.rs b/src/crates/core/src/service/terminal/src/session/mod.rs index 6add098d..07342b87 100644 --- a/src/crates/core/src/service/terminal/src/session/mod.rs +++ b/src/crates/core/src/service/terminal/src/session/mod.rs @@ -29,8 +29,10 @@ use crate::shell::ShellType; /// Terminal session status #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Default)] pub enum SessionStatus { /// Session is starting up + #[default] Starting, /// Session is active and running Active, @@ -44,11 +46,6 @@ pub enum SessionStatus { Terminating, } -impl Default for SessionStatus { - fn default() -> Self { - SessionStatus::Starting - } -} /// Terminal session information #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/crates/core/src/service/terminal/src/shell/integration.rs b/src/crates/core/src/service/terminal/src/shell/integration.rs index 0bf6b317..c573633b 100644 --- a/src/crates/core/src/service/terminal/src/shell/integration.rs +++ b/src/crates/core/src/service/terminal/src/shell/integration.rs @@ -41,8 +41,10 @@ pub enum OscSequence { /// Command execution state #[derive(Debug, Clone, PartialEq)] +#[derive(Default)] pub enum CommandState { /// Waiting for prompt + #[default] Idle, /// Prompt is being displayed Prompt, @@ -67,11 +69,6 @@ impl CommandState { } } -impl Default for CommandState { - fn default() -> Self { - CommandState::Idle - } -} /// Event emitted by shell integration #[derive(Debug, Clone)] @@ -429,14 +426,10 @@ impl ShellIntegration { self.post_command_collecting = true; // Emit event but keep command_id for output collection - let event = if let Some(cmd_id) = &self.current_command_id { - Some(ShellIntegrationEvent::CommandFinished { + let event = self.current_command_id.as_ref().map(|cmd_id| ShellIntegrationEvent::CommandFinished { command_id: cmd_id.clone(), exit_code, - }) - } else { - None - }; + }); self.current_command = None; event diff --git a/src/crates/core/src/service/terminal/src/shell/mod.rs b/src/crates/core/src/service/terminal/src/shell/mod.rs index 8a1bc4ba..6a5da7ee 100644 --- a/src/crates/core/src/service/terminal/src/shell/mod.rs +++ b/src/crates/core/src/service/terminal/src/shell/mod.rs @@ -20,6 +20,7 @@ use serde::{Deserialize, Serialize}; /// Supported shell types #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Default)] pub enum ShellType { /// Bash shell Bash, @@ -30,6 +31,7 @@ pub enum ShellType { /// PowerShell (Windows PowerShell) PowerShell, /// PowerShell Core (cross-platform) + #[default] PowerShellCore, /// Windows CMD Cmd, @@ -131,19 +133,6 @@ impl ShellType { } } -impl Default for ShellType { - fn default() -> Self { - #[cfg(windows)] - { - // Prefer PowerShell Core over Windows PowerShell - ShellType::PowerShellCore - } - #[cfg(not(windows))] - { - ShellType::Bash - } - } -} impl std::fmt::Display for ShellType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/crates/core/src/service/token_usage/service.rs b/src/crates/core/src/service/token_usage/service.rs index a065e1a2..8206b5b8 100644 --- a/src/crates/core/src/service/token_usage/service.rs +++ b/src/crates/core/src/service/token_usage/service.rs @@ -139,6 +139,7 @@ impl TokenUsageService { } /// Record a token usage event + #[allow(clippy::too_many_arguments)] pub async fn record_usage( &self, model_id: String, @@ -351,7 +352,7 @@ impl TokenUsageService { } } - current_date = current_date + Duration::days(1); + current_date += Duration::days(1); } // Filter by model_id, session_id, and subagent flag diff --git a/src/crates/core/src/service/token_usage/types.rs b/src/crates/core/src/service/token_usage/types.rs index 25b974ca..eed1d12e 100644 --- a/src/crates/core/src/service/token_usage/types.rs +++ b/src/crates/core/src/service/token_usage/types.rs @@ -22,6 +22,7 @@ pub struct TokenUsageRecord { /// Aggregated token statistics for a model #[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default)] pub struct ModelTokenStats { pub model_id: String, pub total_input: u64, @@ -39,22 +40,6 @@ pub struct ModelTokenStats { pub last_used: Option>, } -impl Default for ModelTokenStats { - fn default() -> Self { - Self { - model_id: String::new(), - total_input: 0, - total_output: 0, - total_cached: 0, - total_tokens: 0, - session_count: 0, - request_count: 0, - session_ids: HashSet::new(), - first_used: None, - last_used: None, - } - } -} /// Token statistics for a specific session #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/crates/core/src/service/workspace/context_generator.rs b/src/crates/core/src/service/workspace/context_generator.rs index f27c3428..31c6b6cc 100644 --- a/src/crates/core/src/service/workspace/context_generator.rs +++ b/src/crates/core/src/service/workspace/context_generator.rs @@ -111,6 +111,7 @@ impl WorkspaceContextGenerator { } /// Creates a default generator. + #[allow(clippy::should_implement_trait)] pub fn default() -> Self { Self::new(None, Arc::new(FileTreeService::default())) } @@ -432,7 +433,7 @@ impl WorkspaceContextGenerator { .file_tree_service .get_directory_contents(path) .await - .map_err(|e| BitFunError::service(e))?; + .map_err(BitFunError::service)?; let mut structure = String::new(); let dir_name = path_buf @@ -551,14 +552,13 @@ impl WorkspaceContextGenerator { in_package = false; } - if in_package && line.contains('=') { - if line.starts_with("name") + if in_package && line.contains('=') + && (line.starts_with("name") || line.starts_with("description") - || line.starts_with("version") + || line.starts_with("version")) { info.push_str(&format!("{}\n", line)); } - } } if !info.is_empty() { diff --git a/src/crates/core/src/service/workspace/manager.rs b/src/crates/core/src/service/workspace/manager.rs index ac5764fc..e7404758 100644 --- a/src/crates/core/src/service/workspace/manager.rs +++ b/src/crates/core/src/service/workspace/manager.rs @@ -594,7 +594,7 @@ impl WorkspaceInfo { if stats .last_modified .as_ref() - .map_or(true, |last_modified| last_modified < &modified_dt) + .is_none_or(|last_modified| last_modified < &modified_dt) { stats.last_modified = Some(modified_dt); } @@ -1217,7 +1217,7 @@ impl WorkspaceManager { /// Removes a workspace. pub fn remove_workspace(&mut self, workspace_id: &str) -> BitFunResult<()> { - if let Some(_) = self.workspaces.remove(workspace_id) { + if self.workspaces.remove(workspace_id).is_some() { if self.current_workspace_id.as_ref() == Some(&workspace_id.to_string()) { self.current_workspace_id = None; } @@ -1325,9 +1325,10 @@ impl WorkspaceManager { /// Returns manager statistics. pub fn get_statistics(&self) -> WorkspaceManagerStatistics { - let mut stats = WorkspaceManagerStatistics::default(); - - stats.total_workspaces = self.workspaces.len(); + let mut stats = WorkspaceManagerStatistics { + total_workspaces: self.workspaces.len(), + ..WorkspaceManagerStatistics::default() + }; for workspace in self.workspaces.values() { match workspace.status { diff --git a/src/crates/core/src/service/workspace/service.rs b/src/crates/core/src/service/workspace/service.rs index f32af8a7..9ed6de37 100644 --- a/src/crates/core/src/service/workspace/service.rs +++ b/src/crates/core/src/service/workspace/service.rs @@ -1112,8 +1112,8 @@ impl WorkspaceService { workspaces.retain(|_id, ws| { if ws.workspace_kind == WorkspaceKind::Remote { // Check if this remote workspace has the required metadata - let has_ssh_host = ws.metadata.get("sshHost").and_then(|v| v.as_str()).map_or(false, |s| !s.trim().is_empty()); - let has_connection_id = ws.metadata.get("connectionId").and_then(|v| v.as_str()).map_or(false, |s| !s.trim().is_empty()); + let has_ssh_host = ws.metadata.get("sshHost").and_then(|v| v.as_str()).is_some_and(|s| !s.trim().is_empty()); + let has_connection_id = ws.metadata.get("connectionId").and_then(|v| v.as_str()).is_some_and(|s| !s.trim().is_empty()); if !has_ssh_host || !has_connection_id { // Skip this legacy remote workspace info!("Skipping legacy remote workspace without required metadata: id={}, root_path={}", _id, ws.root_path.display()); diff --git a/src/crates/core/src/util/errors.rs b/src/crates/core/src/util/errors.rs index 7db157db..95edc2e5 100644 --- a/src/crates/core/src/util/errors.rs +++ b/src/crates/core/src/util/errors.rs @@ -138,8 +138,7 @@ impl BitFunError { } pub fn serialization>(msg: T) -> Self { - Self::Serialization(serde_json::Error::io(std::io::Error::new( - std::io::ErrorKind::Other, + Self::Serialization(serde_json::Error::io(std::io::Error::other( msg.into(), ))) } @@ -149,7 +148,7 @@ impl BitFunError { } pub fn io>(msg: T) -> Self { - Self::Io(std::io::Error::new(std::io::ErrorKind::Other, msg.into())) + Self::Io(std::io::Error::other(msg.into())) } pub fn cancelled>(msg: T) -> Self { diff --git a/src/crates/core/src/util/json_extract.rs b/src/crates/core/src/util/json_extract.rs index 7c9b2f17..13a9cc39 100644 --- a/src/crates/core/src/util/json_extract.rs +++ b/src/crates/core/src/util/json_extract.rs @@ -1,12 +1,12 @@ -/// Robust JSON extraction from AI model responses. -/// -/// AI models often wrap JSON in markdown code blocks (`` ```json ... ``` ``), -/// or include leading/trailing prose. This module provides a single public -/// helper that handles all common formats and falls back gracefully. -/// -/// When the extracted text is not valid JSON (e.g. the model emitted unescaped -/// quotes inside string values), a best-effort repair pass is attempted before -/// giving up. +//! Robust JSON extraction from AI model responses. +//! +//! AI models often wrap JSON in markdown code blocks (`` ```json ... ``` ``), +//! or include leading/trailing prose. This module provides a single public +//! helper that handles all common formats and falls back gracefully. +//! +//! When the extracted text is not valid JSON (e.g. the model emitted unescaped +//! quotes inside string values), a best-effort repair pass is attempted before +//! giving up. use log::{debug, warn}; @@ -60,7 +60,11 @@ pub fn extract_json_from_ai_response(response: &str) -> Option { // Second pass: attempt repair on each candidate. for candidate in &candidates { if let Some(repaired) = try_repair_json(candidate) { - debug!("JSON repair succeeded (original length={}, repaired length={})", candidate.len(), repaired.len()); + debug!( + "JSON repair succeeded (original length={}, repaired length={})", + candidate.len(), + repaired.len() + ); return Some(repaired); } } @@ -192,7 +196,10 @@ fn try_repair_json(input: &str) -> Option { } fn next_non_whitespace(chars: &[char], start: usize) -> Option { - chars[start..].iter().find(|c| !c.is_ascii_whitespace()).copied() + chars[start..] + .iter() + .find(|c| !c.is_ascii_whitespace()) + .copied() } /// Characters that legitimately follow a closing `"` in JSON. @@ -348,11 +355,15 @@ mod tests { #[test] fn repair_unescaped_chinese_style_quotes() { // AI writes: "headline": "用户问AI"你是什么模型"" — inner quotes are ASCII U+0022 - let input = "```json\n{\"headline\": \"用户问AI\"你是什么模型\"\", \"detail\": \"ok\"}\n```"; + let input = + "```json\n{\"headline\": \"用户问AI\"你是什么模型\"\", \"detail\": \"ok\"}\n```"; let result = extract_json_from_ai_response(input); assert!(result.is_some(), "repair should succeed"); let parsed: serde_json::Value = serde_json::from_str(&result.unwrap()).unwrap(); - assert!(parsed["headline"].as_str().unwrap().contains("你是什么模型")); + assert!(parsed["headline"] + .as_str() + .unwrap() + .contains("你是什么模型")); assert_eq!(parsed["detail"].as_str().unwrap(), "ok"); } @@ -361,7 +372,10 @@ mod tests { // "text": "他说"你好"然后又说"再见"" let input = r#"{"text": "他说"你好"然后又说"再见"", "other": "fine"}"#; let result = extract_json_from_ai_response(input); - assert!(result.is_some(), "repair should handle multiple rogue quotes"); + assert!( + result.is_some(), + "repair should handle multiple rogue quotes" + ); let parsed: serde_json::Value = serde_json::from_str(&result.unwrap()).unwrap(); assert!(parsed["text"].as_str().unwrap().contains("你好")); assert!(parsed["text"].as_str().unwrap().contains("再见")); @@ -392,7 +406,10 @@ mod tests { let result = extract_json_from_ai_response(input); assert!(result.is_some(), "should repair the fun ending JSON"); let parsed: serde_json::Value = serde_json::from_str(&result.unwrap()).unwrap(); - assert!(parsed["headline"].as_str().unwrap().contains("你到底是什么模型")); + assert!(parsed["headline"] + .as_str() + .unwrap() + .contains("你到底是什么模型")); } #[test] @@ -402,7 +419,10 @@ mod tests { let result = extract_json_from_ai_response(input); assert!(result.is_some(), "should repair interaction style JSON"); let parsed: serde_json::Value = serde_json::from_str(&result.unwrap()).unwrap(); - assert!(parsed["narrative"].as_str().unwrap().contains("这个项目是什么")); + assert!(parsed["narrative"] + .as_str() + .unwrap() + .contains("这个项目是什么")); } #[test] diff --git a/src/crates/core/src/util/process_manager.rs b/src/crates/core/src/util/process_manager.rs index 4185b703..65739844 100644 --- a/src/crates/core/src/util/process_manager.rs +++ b/src/crates/core/src/util/process_manager.rs @@ -90,7 +90,7 @@ pub fn create_command>(program: S) -> Command { { let mut cmd = cmd; cmd.creation_flags(CREATE_NO_WINDOW); - return cmd; + cmd } #[cfg(not(windows))] @@ -105,7 +105,7 @@ pub fn create_tokio_command>(program: S) -> TokioComma { let mut cmd = cmd; cmd.creation_flags(CREATE_NO_WINDOW); - return cmd; + cmd } #[cfg(not(windows))] diff --git a/src/crates/events/src/agentic.rs b/src/crates/events/src/agentic.rs index fcf70855..b2c61a9c 100644 --- a/src/crates/events/src/agentic.rs +++ b/src/crates/events/src/agentic.rs @@ -1,4 +1,4 @@ -///! Agentic Events Definition +//! Agentic Events Definition use serde::{Deserialize, Serialize}; use std::time::SystemTime; diff --git a/src/crates/events/src/types.rs b/src/crates/events/src/types.rs index 23f20576..0e20f2ce 100644 --- a/src/crates/events/src/types.rs +++ b/src/crates/events/src/types.rs @@ -5,14 +5,11 @@ use serde::{Deserialize, Serialize}; /// Event priority #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Default)] pub enum EventPriority { Low = 0, + #[default] Normal = 1, High = 2, } -impl Default for EventPriority { - fn default() -> Self { - Self::Normal - } -} diff --git a/src/crates/transport/src/adapters/tauri.rs b/src/crates/transport/src/adapters/tauri.rs index 2fd16f4d..cff31c65 100644 --- a/src/crates/transport/src/adapters/tauri.rs +++ b/src/crates/transport/src/adapters/tauri.rs @@ -1,7 +1,7 @@ -/// Tauri transport adapter -/// -/// Uses Tauri's app.emit() system to send events to frontend -/// Maintains compatibility with current implementation +//! Tauri transport adapter +//! +//! Uses Tauri's app.emit() system to send events to frontend +//! Maintains compatibility with current implementation #[cfg(feature = "tauri-adapter")] use crate::traits::{TextChunk, ToolEventPayload, TransportAdapter}; diff --git a/src/crates/transport/src/events.rs b/src/crates/transport/src/events.rs index 1ca4a4b4..3e5e28fa 100644 --- a/src/crates/transport/src/events.rs +++ b/src/crates/transport/src/events.rs @@ -73,14 +73,11 @@ pub struct BackendEventPayload { /// Event priority #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Default)] pub enum EventPriority { Low = 0, + #[default] Normal = 1, High = 2, } -impl Default for EventPriority { - fn default() -> Self { - Self::Normal - } -}