From 81e9f1e7db54ca34414abcd668aacad910b7dbbd Mon Sep 17 00:00:00 2001 From: xiaoxixi Date: Fri, 8 May 2026 16:35:21 +0800 Subject: [PATCH] =?UTF-8?q?clippy=20--fix:=20=E5=90=88=E5=B9=B6=E5=B5=8C?= =?UTF-8?q?=E5=A5=97if=E3=80=81=E7=AE=80=E5=8C=96map=5For=E3=80=81?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=86=97=E4=BD=99=E5=BC=95=E7=94=A8=E7=AD=89?= =?UTF-8?q?=E6=9C=BA=E6=A2=B0=E6=80=A7=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent_loop.rs | 4 +-- src/agent/system_prompt.rs | 5 ++- src/channels/cli_chat.rs | 6 ++++ src/channels/feishu.rs | 35 ++++++++---------- src/client/tui/components/command_menu.rs | 2 +- src/client/tui/components/session_list.rs | 3 +- src/gateway/ws.rs | 5 ++- src/logging.rs | 5 ++- src/providers/openai.rs | 12 +++---- src/session/session.rs | 43 ++++++++++------------- src/skills/mod.rs | 23 ++++-------- src/storage/memory.rs | 4 +-- src/tools/bash.rs | 4 +-- src/tools/file_read.rs | 2 -- src/tools/file_write.rs | 8 ++--- src/tools/http_request.rs | 8 ++--- src/tools/schema.rs | 25 ++++++------- 17 files changed, 79 insertions(+), 115 deletions(-) diff --git a/src/agent/agent_loop.rs b/src/agent/agent_loop.rs index 6aed828..1cabec7 100644 --- a/src/agent/agent_loop.rs +++ b/src/agent/agent_loop.rs @@ -162,7 +162,7 @@ impl LoopDetector { .count(); // Warn every warn_every times - if consecutive > 0 && consecutive % self.config.warn_every == 0 { + if consecutive > 0 && consecutive.is_multiple_of(self.config.warn_every) { LoopDetectionResult::Warning(format!( "注意: 工具 '{}' 已连续执行 {} 次,参数相同。如果任务没有进展,请尝试其他方法。", last.name, consecutive @@ -339,7 +339,7 @@ impl AgentLoop { tracing::debug!(history_len = messages.len(), max_iterations = self.max_iterations, "Starting agent process"); // Build and inject system prompt if not present - let has_system = messages.first().map_or(false, |m| m.role == "system"); + let has_system = messages.first().is_some_and(|m| m.role == "system"); if !has_system { let system_prompt = build_system_prompt(&self.workspace_dir, &self.model_name, &self.tools, None, None); #[cfg(debug_assertions)] diff --git a/src/agent/system_prompt.rs b/src/agent/system_prompt.rs index 4b6cce9..bcc6d30 100644 --- a/src/agent/system_prompt.rs +++ b/src/agent/system_prompt.rs @@ -186,14 +186,13 @@ impl PromptSection for UserProfileSection { let mut output = String::from("## 用户配置\n\n"); // Load USER.md from ~/.picobot/USER.md - if let Some(user_config_dir) = get_user_config_dir() { - if let Some(content) = + if let Some(user_config_dir) = get_user_config_dir() + && let Some(content) = load_file_from_dir(&user_config_dir, "USER.md", BOOTSTRAP_MAX_CHARS) { output.push_str(&content); return output; } - } // No USER.md found, return empty String::new() diff --git a/src/channels/cli_chat.rs b/src/channels/cli_chat.rs index 4cb2e34..80f7dd5 100644 --- a/src/channels/cli_chat.rs +++ b/src/channels/cli_chat.rs @@ -32,6 +32,12 @@ pub struct CliChatChannel { clients: Mutex>>, } +impl Default for CliChatChannel { + fn default() -> Self { + Self::new() + } +} + impl CliChatChannel { pub fn new() -> Self { Self { diff --git a/src/channels/feishu.rs b/src/channels/feishu.rs index e328b5f..c6bb47e 100644 --- a/src/channels/feishu.rs +++ b/src/channels/feishu.rs @@ -229,11 +229,10 @@ impl FeishuChannel { // 1. Check cache { let cached = self.tenant_token.read().await; - if let Some(ref token) = *cached { - if Instant::now() < token.refresh_after { + if let Some(ref token) = *cached + && Instant::now() < token.refresh_after { return Ok(token.value.clone()); } - } } // 2. Fetch new token @@ -901,11 +900,10 @@ impl FeishuChannel { let (mut content, media) = self.parse_and_download_message(msg_type, &raw_content, &message_id).await?; // Fetch and prepend quoted message content if this is a reply - if let Some(ref pid) = parent_id { - if let Some(reply_ctx) = self.get_message_content(pid).await { + if let Some(ref pid) = parent_id + && let Some(reply_ctx) = self.get_message_content(pid).await { content = format!("{}\n{}", reply_ctx, content); } - } #[cfg(debug_assertions)] if let Some(ref m) = media { @@ -1296,8 +1294,8 @@ fn parse_post_content(content: &str) -> String { // Fall back: try any dict child if let Some(root_obj) = root.as_object() { for (_key, val) in root_obj { - if let Some(obj) = val.as_object() { - if obj.get("content").and_then(|c| c.as_array()).is_some() { + if let Some(obj) = val.as_object() + && obj.get("content").and_then(|c| c.as_array()).is_some() { parse_block(val, &mut texts); let result = texts.join(""); if !result.trim().is_empty() { @@ -1305,7 +1303,6 @@ fn parse_post_content(content: &str) -> String { } texts.clear(); } - } } } @@ -1329,22 +1326,19 @@ fn extract_interactive_content(content: &str) -> Result<(String, Option, dept None } }) - }) { - if let Some(children) = children_arr.as_object().and_then(|o| o.get("children")).and_then(|c| c.as_array()) { + }) + && let Some(children) = children_arr.as_object().and_then(|o| o.get("children")).and_then(|c| c.as_array()) { collect_list_items(children, lines, depth + 1); } - } } } @@ -2088,7 +2081,7 @@ impl Channel for FeishuChannel { content: Self::strip_thinking_tags(&msg.content), ..msg }; - let receive_id = if msg.chat_id.starts_with("oc_") { &msg.chat_id } else { &msg.reply_to.as_ref().unwrap_or(&msg.chat_id) }; + let receive_id = if msg.chat_id.starts_with("oc_") { &msg.chat_id } else { msg.reply_to.as_ref().unwrap_or(&msg.chat_id) }; let receive_id_type = if msg.chat_id.starts_with("oc_") { "chat_id" } else { "open_id" }; // If no media, use smart format detection diff --git a/src/client/tui/components/command_menu.rs b/src/client/tui/components/command_menu.rs index f496cb7..45e031f 100644 --- a/src/client/tui/components/command_menu.rs +++ b/src/client/tui/components/command_menu.rs @@ -32,7 +32,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) { let alias = cmd.aliases.first().map(|a| a.as_str()).unwrap_or(&cmd.name); ListItem::new(Line::from(vec![ - Span::styled(alias, style.clone()), + Span::styled(alias, style), Span::styled(" - ", Style::default().fg(Color::Gray)), Span::styled(&cmd.description, style), ])) diff --git a/src/client/tui/components/session_list.rs b/src/client/tui/components/session_list.rs index f02ccf2..99d197d 100644 --- a/src/client/tui/components/session_list.rs +++ b/src/client/tui/components/session_list.rs @@ -13,8 +13,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) { .map(|session| { let is_current = app .current_session_id - .as_ref() - .map_or(false, |id| id == &session.session_id); + .as_ref() == Some(&session.session_id); let archived = session.archived_at.is_some(); let mut content = if is_current { diff --git a/src/gateway/ws.rs b/src/gateway/ws.rs index a520a90..40cea34 100644 --- a/src/gateway/ws.rs +++ b/src/gateway/ws.rs @@ -36,11 +36,10 @@ async fn handle_socket(ws: WebSocket, state: Arc) { // Task: forward from receiver to WebSocket tokio::spawn(async move { while let Some(msg) = receiver.recv().await { - if let Ok(text) = serialize_outbound(&msg) { - if ws_sender.send(WsMessage::Text(text.into())).await.is_err() { + if let Ok(text) = serialize_outbound(&msg) + && ws_sender.send(WsMessage::Text(text.into())).await.is_err() { break; } - } } }); diff --git a/src/logging.rs b/src/logging.rs index 4e8bdd8..5aff5af 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -26,11 +26,10 @@ pub fn init_logging() { let log_dir = get_default_log_dir(); // Create log directory if it doesn't exist - if !log_dir.exists() { - if let Err(e) = std::fs::create_dir_all(&log_dir) { + if !log_dir.exists() + && let Err(e) = std::fs::create_dir_all(&log_dir) { eprintln!("Warning: Failed to create log directory {}: {}", log_dir.display(), e); } - } // Create file appender with daily rotation let file_appender = RollingFileAppender::new( diff --git a/src/providers/openai.rs b/src/providers/openai.rs index f3d334a..d1160fe 100644 --- a/src/providers/openai.rs +++ b/src/providers/openai.rs @@ -11,11 +11,10 @@ use std::sync::Arc; use crate::storage::Storage; fn convert_content_blocks(blocks: &[ContentBlock]) -> Value { - if blocks.len() == 1 { - if let ContentBlock::Text { text } = &blocks[0] { + if blocks.len() == 1 + && let ContentBlock::Text { text } = &blocks[0] { return Value::String(text.clone()); } - } Value::Array(blocks.iter().map(|b| match b { ContentBlock::Text { text } => json!({ "type": "text", "text": text }), ContentBlock::ImageUrl { image_url } => { @@ -77,7 +76,7 @@ impl OpenAIProvider { "tool_call_id": m.tool_call_id, "name": m.name, }) - } else if m.role == "assistant" && m.tool_calls.as_ref().map_or(false, |c| !c.is_empty()) { + } else if m.role == "assistant" && m.tool_calls.as_ref().is_some_and(|c| !c.is_empty()) { json!({ "role": m.role, "content": convert_content_blocks(&m.content), @@ -187,12 +186,11 @@ impl LLMProvider for OpenAIProvider { for (i, msg) in msgs.iter().enumerate() { if let Some(content) = msg.get("content").and_then(|c| c.as_array()) { for (j, item) in content.iter().enumerate() { - if item.get("type").and_then(|t| t.as_str()) == Some("image_url") { - if let Some(url_str) = item.get("image_url").and_then(|u| u.get("url")).and_then(|v| v.as_str()) { + if item.get("type").and_then(|t| t.as_str()) == Some("image_url") + && let Some(url_str) = item.get("image_url").and_then(|u| u.get("url")).and_then(|v| v.as_str()) { let prefix: String = url_str.chars().take(20).collect(); tracing::debug!(msg_idx = i, item_idx = j, image_prefix = %prefix, image_url_len = %url_str.len(), "Image in LLM request (first 20 bytes shown)"); } - } } } } diff --git a/src/session/session.rs b/src/session/session.rs index da1b790..2e20853 100644 --- a/src/session/session.rs +++ b/src/session/session.rs @@ -22,7 +22,7 @@ use crate::agent::{AgentLoop, AgentError, ContextCompressor}; use crate::agent::system_prompt::build_system_prompt; use crate::agent::context_compressor::ContextCompressionConfig; use crate::providers::{create_provider, LLMProvider}; -use crate::session::session_id::{UnifiedSessionId, DEFAULT_DIALOG_ID}; +use crate::session::session_id::UnifiedSessionId; use crate::session::events::DialogInfo; use crate::skills::SkillsLoader; use crate::tools::{ToolRegistry, create_default_tools}; @@ -193,8 +193,8 @@ impl Session { self.seq_counter += 1; // Persist to Storage - if persist { - if let Some(ref storage) = self.storage { + if persist + && let Some(ref storage) = self.storage { let msg_meta = crate::storage::message::MessageMeta { id: message.id.clone(), session_id: self.id.to_string(), @@ -214,7 +214,6 @@ impl Session { }; storage.append_message_with_retry(&self.id.to_string(), &msg_meta).await?; } - } // Update in-memory state self.messages.push(message); @@ -411,7 +410,7 @@ impl Session { /// 将当前 session 导出为 markdown 文档并保存到文件 pub fn dump_to_file(&self, system_prompt: &str) -> std::io::Result { - use chrono::{DateTime, Local}; + use chrono::Local; use std::fs; use std::io::Write; @@ -440,7 +439,7 @@ impl Session { let now = Local::now().format("%Y-%m-%d %H:%M:%S"); let mut md = String::new(); - md.push_str(&format!("# Session Dump\n\n")); + md.push_str(&"# Session Dump\n\n".to_string()); md.push_str(&format!("- **Session ID**: `{}`\n", self.id)); md.push_str(&format!("- **Channel**: `{}`\n", self.id.channel)); md.push_str(&format!("- **Chat ID**: `{}`\n", self.id.chat_id)); @@ -473,7 +472,7 @@ impl Session { md.push_str("```\n"); if let Some(ref tool_calls) = msg.tool_calls { - md.push_str(&format!("[Tool Calls]\n")); + md.push_str(&"[Tool Calls]\n".to_string()); for tc in tool_calls { md.push_str(&format!("- {}: {:?}\n", tc.name, tc.arguments)); } @@ -599,11 +598,10 @@ fn repair_tool_call_chains(messages: &mut Vec) { let mut j = i + 1; while j < messages.len() && found < expected_count { if messages[j].role == "tool" { - if let Some(ref tc_id) = messages[j].tool_call_id { - if expected_ids.contains(tc_id.as_str()) { + if let Some(ref tc_id) = messages[j].tool_call_id + && expected_ids.contains(tc_id.as_str()) { found += 1; } - } } else if messages[j].role == "user" || messages[j].role == "assistant" { // Next user/assistant message — stop scanning, chain is broken break; @@ -1031,7 +1029,7 @@ impl SessionManager { self.tools.clone(), Some(self.storage.clone()), String::new(), - format!("新对话"), + "新对话".to_string(), self.memory_manager.clone(), ).await?; @@ -1165,14 +1163,11 @@ impl SessionManager { }; if let Some(ref current_id) = current_id { - match self.storage.get_session(current_id).await { - Ok(_) => { - let parts: Vec<&str> = current_id.split(':').collect(); - if parts.len() == 3 { - return Ok(UnifiedSessionId::new(channel, chat_id, parts[2])); - } + if let Ok(_) = self.storage.get_session(current_id).await { + let parts: Vec<&str> = current_id.split(':').collect(); + if parts.len() == 3 { + return Ok(UnifiedSessionId::new(channel, chat_id, parts[2])); } - Err(_) => {} } } @@ -1310,7 +1305,7 @@ impl SessionManager { let skills_prompt = self.skills_loader.build_skills_prompt(); // Fetch memory context - let memory_context = match self.memory_manager.recall(&content, 5, Some(crate::memory::MemoryCategory::Knowledge)).await { + let memory_context = match self.memory_manager.recall(content, 5, Some(crate::memory::MemoryCategory::Knowledge)).await { Ok(entries) if !entries.is_empty() => { Some(entries.iter() .map(|e| format!("- {}: {}", e.key, e.content)) @@ -1342,11 +1337,10 @@ impl SessionManager { } // Check if we need to generate a title (after 10 user messages) - if session_guard.should_generate_title() { - if let Err(e) = session_guard.generate_title().await { + if session_guard.should_generate_title() + && let Err(e) = session_guard.generate_title().await { tracing::warn!("failed to generate title: {}", e); } - } result.final_response.content }; @@ -1456,11 +1450,10 @@ impl SessionManager { .map_err(|e| AgentError::Other(format!("persist error: {}", e)))?; } - if session_guard.should_generate_title() { - if let Err(e) = session_guard.generate_title().await { + if session_guard.should_generate_title() + && let Err(e) = session_guard.generate_title().await { tracing::warn!("failed to generate title: {}", e); } - } let raw_response = result.final_response.content; let prefix = format!( diff --git a/src/skills/mod.rs b/src/skills/mod.rs index 193580e..777b692 100644 --- a/src/skills/mod.rs +++ b/src/skills/mod.rs @@ -12,6 +12,7 @@ pub struct Skill { pub path: Option, } +#[derive(Default)] struct SkillMarkdownMeta { name: Option, description: Option, @@ -147,22 +148,19 @@ impl SkillsLoader { fn get_dir_mtime(dir: &Path) -> Option { let mut max_mtime = None; - if let Ok(metadata) = std::fs::metadata(dir) { - if let Ok(mtime) = metadata.modified() { + if let Ok(metadata) = std::fs::metadata(dir) + && let Ok(mtime) = metadata.modified() { max_mtime = Some(mtime); } - } if let Ok(entries) = std::fs::read_dir(dir) { for entry in entries.flatten() { let path = entry.path(); - if let Ok(metadata) = std::fs::metadata(&path) { - if let Ok(mtime) = metadata.modified() { - if max_mtime.map_or(true, |current| mtime > current) { + if let Ok(metadata) = std::fs::metadata(&path) + && let Ok(mtime) = metadata.modified() + && max_mtime.is_none_or(|current| mtime > current) { max_mtime = Some(mtime); } - } - } } } @@ -424,15 +422,6 @@ impl Default for SkillsLoader { } } -impl Default for SkillMarkdownMeta { - fn default() -> Self { - Self { - name: None, - description: None, - always: None, - } - } -} /// Extract first non-empty, non-heading line as description fn extract_description(content: &str) -> String { diff --git a/src/storage/memory.rs b/src/storage/memory.rs index 4d17b2d..046ec30 100644 --- a/src/storage/memory.rs +++ b/src/storage/memory.rs @@ -96,7 +96,7 @@ impl super::Storage { .cut(query, true) .into_iter() .filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127)) - .map(|w| w.replace('%', "").replace('_', "")) + .map(|w| w.replace(['%', '_'], "")) .collect(); if !terms.is_empty() { @@ -159,7 +159,7 @@ impl super::Storage { .cut(q, true) .into_iter() .filter(|w| w.len() > 1 || w.bytes().any(|b| b > 127)) - .map(|w| w.replace('%', "").replace('_', "")) + .map(|w| w.replace(['%', '_'], "")) .collect(); if terms.is_empty() { diff --git a/src/tools/bash.rs b/src/tools/bash.rs index 7f03104..32edae7 100644 --- a/src/tools/bash.rs +++ b/src/tools/bash.rs @@ -144,7 +144,7 @@ impl Tool for BashTool { let cwd = self .working_dir .as_ref() - .map(|d| Path::new(d)) + .map(Path::new) .unwrap_or_else(|| Path::new(".")); let result = timeout( @@ -217,7 +217,7 @@ impl BashTool { let stderr_str = String::from_utf8_lossy(&stderr); if !stderr_str.trim().is_empty() { if !output.is_empty() { - output.push_str("\n"); + output.push('\n'); } output.push_str("STDERR:\n"); output.push_str(&stderr_str); diff --git a/src/tools/file_read.rs b/src/tools/file_read.rs index 6eb8ca2..10725f1 100644 --- a/src/tools/file_read.rs +++ b/src/tools/file_read.rs @@ -1,10 +1,8 @@ -use std::io::Read; use std::path::Path; use async_trait::async_trait; use serde_json::json; -use crate::bus::message::ContentBlock; use crate::tools::traits::{Tool, ToolResult}; const MAX_CHARS: usize = 128_000; diff --git a/src/tools/file_write.rs b/src/tools/file_write.rs index 3472c70..0086421 100644 --- a/src/tools/file_write.rs +++ b/src/tools/file_write.rs @@ -113,17 +113,15 @@ impl Tool for FileWriteTool { }; // Create parent directories if needed - if let Some(parent) = resolved.parent() { - if !parent.exists() { - if let Err(e) = std::fs::create_dir_all(parent) { + if let Some(parent) = resolved.parent() + && !parent.exists() + && let Err(e) = std::fs::create_dir_all(parent) { return Ok(ToolResult { success: false, output: String::new(), error: Some(format!("Failed to create parent directory: {}", e)), }); } - } - } match std::fs::write(&resolved, content) { Ok(_) => Ok(ToolResult { diff --git a/src/tools/http_request.rs b/src/tools/http_request.rs index d3b05d3..37a02ae 100644 --- a/src/tools/http_request.rs +++ b/src/tools/http_request.rs @@ -78,15 +78,13 @@ impl HttpRequestTool { if let Some(obj) = headers.as_object() { for (key, value) in obj { - if let Some(str_val) = value.as_str() { - if let Ok(name) = reqwest::header::HeaderName::from_bytes(key.as_bytes()) { - if let Ok(val) = + if let Some(str_val) = value.as_str() + && let Ok(name) = reqwest::header::HeaderName::from_bytes(key.as_bytes()) + && let Ok(val) = reqwest::header::HeaderValue::from_str(str_val) { header_map.insert(name, val); } - } - } } } diff --git a/src/tools/schema.rs b/src/tools/schema.rs index 91bca47..137dc46 100644 --- a/src/tools/schema.rs +++ b/src/tools/schema.rs @@ -114,11 +114,10 @@ impl SchemaCleanr { anyhow::bail!("Schema missing required 'type' field"); } - if let Some(Value::String(t)) = obj.get("type") { - if t == "object" && !obj.contains_key("properties") { + if let Some(Value::String(t)) = obj.get("type") + && t == "object" && !obj.contains_key("properties") { tracing::warn!("Object schema without 'properties' field may cause issues"); } - } Ok(()) } @@ -173,11 +172,10 @@ impl SchemaCleanr { } // Handle anyOf/oneOf simplification - if obj.contains_key("anyOf") || obj.contains_key("oneOf") { - if let Some(simplified) = Self::try_simplify_union(&obj, defs, strategy, ref_stack) { + if (obj.contains_key("anyOf") || obj.contains_key("oneOf")) + && let Some(simplified) = Self::try_simplify_union(&obj, defs, strategy, ref_stack) { return simplified; } - } // Build cleaned object let mut cleaned = Map::new(); @@ -244,14 +242,13 @@ impl SchemaCleanr { return Self::preserve_meta(obj, Value::Object(Map::new())); } - if let Some(def_name) = Self::parse_local_ref(ref_value) { - if let Some(definition) = defs.get(def_name.as_str()) { + if let Some(def_name) = Self::parse_local_ref(ref_value) + && let Some(definition) = defs.get(def_name.as_str()) { ref_stack.insert(ref_value.to_string()); let cleaned = Self::clean_with_defs(definition.clone(), defs, strategy, ref_stack); ref_stack.remove(ref_value); return Self::preserve_meta(obj, cleaned); } - } tracing::warn!("Cannot resolve $ref: {}", ref_value); Self::preserve_meta(obj, Value::Object(Map::new())) @@ -342,16 +339,14 @@ impl SchemaCleanr { if let Some(Value::Null) = obj.get("const") { return true; } - if let Some(Value::Array(arr)) = obj.get("enum") { - if arr.len() == 1 && matches!(arr[0], Value::Null) { + if let Some(Value::Array(arr)) = obj.get("enum") + && arr.len() == 1 && matches!(arr[0], Value::Null) { return true; } - } - if let Some(Value::String(t)) = obj.get("type") { - if t == "null" { + if let Some(Value::String(t)) = obj.get("type") + && t == "null" { return true; } - } } false }