fix: resolve all clippy warnings across codebase

Fix all clippy errors reported by `cargo clippy --all-targets -- -D warnings`
on Rust 1.93, covering both the original codebase and upstream dev changes.

Changes by category:
- format!() appended to String → write!/writeln! (telegram, discord)
- Redundant field names, unnecessary boolean not (agent/loop_)
- Long numeric literals (wati, nextcloud, telegram, gemini)
- Wildcard match on single variant (security/leak_detector)
- Derivable Default impls (config/schema)
- &Option<T> → Option<&T> or allow (config/schema, config/mod, gateway/api)
- Identical match arms merged (gateway/ws, observability, providers, main, onboard)
- Cast truncation allowed with rationale (discord, lark)
- Unnecessary borrows/returns removed (multiple files)
- Unused imports removed (channels/mod, peripherals/mod, tests)
- MSRV-gated APIs allowed locally (memory/hygiene, tools/shell, tools/screenshot)
- Unnecessary .get().is_none() → !contains_key() (gemini)
- Explicit iteration → reference loop (gateway/api)
- Test-only: useless vec!, field_reassign_with_default, doc indentation

Validated: cargo fmt, cargo clippy --all-targets -- -D warnings, cargo test
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
FlashFamily 2026-02-24 21:26:17 +08:00
parent b36dd3aa81
commit 49e90cf3e4
31 changed files with 124 additions and 140 deletions

View File

@ -387,7 +387,7 @@ fn parse_tool_call_value(value: &serde_json::Value) -> Option<ParsedToolCall> {
return Some(ParsedToolCall {
name,
arguments,
tool_call_id: tool_call_id,
tool_call_id,
});
}
}
@ -409,7 +409,7 @@ fn parse_tool_call_value(value: &serde_json::Value) -> Option<ParsedToolCall> {
Some(ParsedToolCall {
name,
arguments,
tool_call_id: tool_call_id,
tool_call_id,
})
}
@ -1527,7 +1527,13 @@ fn parse_tool_calls(response: &str) -> (String, Vec<ParsedToolCall>) {
// Try to parse the inner content as JSON arguments
let json_values = extract_json_values(inner);
if !json_values.is_empty() {
if json_values.is_empty() {
tracing::warn!(
tool_name = %tool_name,
inner = %inner.chars().take(100).collect::<String>(),
"Found ```tool <name> block but could not parse JSON arguments"
);
} else {
for value in json_values {
let arguments = if value.is_object() {
value
@ -1540,13 +1546,6 @@ fn parse_tool_calls(response: &str) -> (String, Vec<ParsedToolCall>) {
tool_call_id: None,
});
}
} else {
// Log a warning if we found a tool block but couldn't parse arguments
tracing::warn!(
tool_name = %tool_name,
inner = %inner.chars().take(100).collect::<String>(),
"Found ```tool <name> block but could not parse JSON arguments"
);
}
last_end = full_match.end();
}
@ -2625,15 +2624,13 @@ pub(crate) async fn run_tool_call_loop(
ordered_results[*idx] = Some((call.name.clone(), call.tool_call_id.clone(), outcome));
}
for entry in ordered_results {
if let Some((tool_name, tool_call_id, outcome)) = entry {
individual_results.push((tool_call_id, outcome.output.clone()));
let _ = writeln!(
tool_results,
"<tool_result name=\"{}\">\n{}\n</tool_result>",
tool_name, outcome.output
);
}
for (tool_name, tool_call_id, outcome) in ordered_results.into_iter().flatten() {
individual_results.push((tool_call_id, outcome.output.clone()));
let _ = writeln!(
tool_results,
"<tool_result name=\"{}\">\n{}\n</tool_result>",
tool_name, outcome.output
);
}
// Add assistant message with tool calls + tool results to history.

View File

@ -362,6 +362,7 @@ fn split_message_for_discord(message: &str) -> Vec<String> {
chunks
}
#[allow(clippy::cast_possible_truncation)]
fn pick_uniform_index(len: usize) -> usize {
debug_assert!(len > 0);
let upper = len as u64;
@ -389,9 +390,10 @@ fn encode_emoji_for_discord(emoji: &str) -> String {
return emoji.to_string();
}
use std::fmt::Write as _;
let mut encoded = String::new();
for byte in emoji.as_bytes() {
encoded.push_str(&format!("%{byte:02X}"));
write!(encoded, "%{byte:02X}").ok();
}
encoded
}
@ -1366,6 +1368,7 @@ mod tests {
}
#[test]
#[allow(clippy::format_collect)]
fn split_message_many_short_lines() {
// Many short lines should be batched into chunks under the limit
let msg: String = (0..500).map(|i| format!("line {i}\n")).collect();

View File

@ -1503,6 +1503,7 @@ impl LarkChannel {
// WS helper functions
// ─────────────────────────────────────────────────────────────────────────────
#[allow(clippy::cast_possible_truncation)]
fn pick_uniform_index(len: usize) -> usize {
debug_assert!(len > 0);
let upper = len as u64;

View File

@ -42,7 +42,7 @@ pub mod whatsapp_storage;
#[cfg(feature = "whatsapp-web")]
pub mod whatsapp_web;
pub use clawdtalk::{ClawdTalkChannel, ClawdTalkConfig};
pub use clawdtalk::ClawdTalkChannel;
pub use cli::CliChannel;
pub use dingtalk::DingTalkChannel;
pub use discord::DiscordChannel;
@ -1295,9 +1295,7 @@ fn sanitize_tool_json_value(
return None;
}
let Some(object) = value.as_object() else {
return None;
};
let object = value.as_object()?;
if let Some(tool_calls) = object.get("tool_calls").and_then(|value| value.as_array()) {
if !tool_calls.is_empty()
@ -1337,7 +1335,7 @@ fn strip_isolated_tool_json_artifacts(message: &str, known_tool_names: &HashSet<
let mut saw_tool_call_payload = false;
while cursor < message.len() {
let Some(rel_start) = message[cursor..].find(|ch: char| ch == '{' || ch == '[') else {
let Some(rel_start) = message[cursor..].find(['{', '[']) else {
cleaned.push_str(&message[cursor..]);
break;
};
@ -2680,9 +2678,10 @@ struct ConfiguredChannel {
channel: Arc<dyn Channel>,
}
#[allow(unused_variables)]
fn collect_configured_channels(
config: &Config,
_matrix_skip_context: &str,
matrix_skip_context: &str,
) -> Vec<ConfiguredChannel> {
let mut channels = Vec::new();
@ -2767,7 +2766,7 @@ fn collect_configured_channels(
if config.channels_config.matrix.is_some() {
tracing::warn!(
"Matrix channel is configured but this build was compiled without `channel-matrix`; skipping Matrix {}.",
_matrix_skip_context
matrix_skip_context
);
}

View File

@ -429,7 +429,7 @@ mod tests {
"message": {
"actorType": "users",
"actorId": "user_a",
"timestamp": 1735701200123u64,
"timestamp": 1_735_701_200_123_u64,
"message": "hello"
}
});

View File

@ -6,6 +6,7 @@ use async_trait::async_trait;
use directories::UserDirs;
use parking_lot::Mutex;
use reqwest::multipart::{Form, Part};
use std::fmt::Write as _;
use std::path::Path;
use std::sync::{Arc, RwLock};
use std::time::Duration;
@ -100,6 +101,7 @@ fn pick_uniform_index(len: usize) -> usize {
loop {
let value = rand::random::<u64>();
if value < reject_threshold {
#[allow(clippy::cast_possible_truncation)]
return (value % upper) as usize;
}
}
@ -1266,7 +1268,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
if self.mention_only && is_group {
let bot_username = self.bot_username.lock();
if let Some(ref bot_username) = *bot_username {
if !Self::contains_bot_mention(&text, bot_username) {
if !Self::contains_bot_mention(text, bot_username) {
return None;
}
} else {
@ -1301,7 +1303,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
let content = if self.mention_only && is_group {
let bot_username = self.bot_username.lock();
let bot_username = bot_username.as_ref()?;
Self::normalize_incoming_content(&text, bot_username)?
Self::normalize_incoming_content(text, bot_username)?
} else {
text.to_string()
};
@ -1409,7 +1411,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
if i + 1 < len && bytes[i] == b'*' && bytes[i + 1] == b'*' {
if let Some(end) = line[i + 2..].find("**") {
let inner = Self::escape_html(&line[i + 2..i + 2 + end]);
line_out.push_str(&format!("<b>{inner}</b>"));
write!(line_out, "<b>{inner}</b>").unwrap();
i += 4 + end;
continue;
}
@ -1417,7 +1419,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
if i + 1 < len && bytes[i] == b'_' && bytes[i + 1] == b'_' {
if let Some(end) = line[i + 2..].find("__") {
let inner = Self::escape_html(&line[i + 2..i + 2 + end]);
line_out.push_str(&format!("<b>{inner}</b>"));
write!(line_out, "<b>{inner}</b>").unwrap();
i += 4 + end;
continue;
}
@ -1427,7 +1429,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
if let Some(end) = line[i + 1..].find('*') {
if end > 0 {
let inner = Self::escape_html(&line[i + 1..i + 1 + end]);
line_out.push_str(&format!("<i>{inner}</i>"));
write!(line_out, "<i>{inner}</i>").unwrap();
i += 2 + end;
continue;
}
@ -1437,7 +1439,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
if bytes[i] == b'`' && (i == 0 || bytes[i - 1] != b'`') {
if let Some(end) = line[i + 1..].find('`') {
let inner = Self::escape_html(&line[i + 1..i + 1 + end]);
line_out.push_str(&format!("<code>{inner}</code>"));
write!(line_out, "<code>{inner}</code>").unwrap();
i += 2 + end;
continue;
}
@ -1453,9 +1455,8 @@ Allowlist Telegram username (without '@') or numeric user ID.",
if url.starts_with("http://") || url.starts_with("https://") {
let text_html = Self::escape_html(text_part);
let url_html = Self::escape_html(url);
line_out.push_str(&format!(
"<a href=\"{url_html}\">{text_html}</a>"
));
write!(line_out, "<a href=\"{url_html}\">{text_html}</a>")
.unwrap();
i = after_bracket + 1 + paren_end + 1;
continue;
}
@ -1467,7 +1468,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
if i + 1 < len && bytes[i] == b'~' && bytes[i + 1] == b'~' {
if let Some(end) = line[i + 2..].find("~~") {
let inner = Self::escape_html(&line[i + 2..i + 2 + end]);
line_out.push_str(&format!("<s>{inner}</s>"));
write!(line_out, "<s>{inner}</s>").unwrap();
i += 4 + end;
continue;
}
@ -1496,14 +1497,14 @@ Allowlist Telegram username (without '@') or numeric user ID.",
for line in joined.split('\n') {
let trimmed = line.trim();
if trimmed.starts_with("```") {
if !in_code_block {
in_code_block = true;
code_buf.clear();
} else {
if in_code_block {
in_code_block = false;
let escaped = code_buf.trim_end_matches('\n');
// Telegram HTML parse mode supports <pre> and <code>, but not class attributes.
final_out.push_str(&format!("<pre><code>{escaped}</code></pre>\n"));
writeln!(final_out, "<pre><code>{escaped}</code></pre>").unwrap();
code_buf.clear();
} else {
in_code_block = true;
code_buf.clear();
}
} else if in_code_block {
@ -1515,10 +1516,7 @@ Allowlist Telegram username (without '@') or numeric user ID.",
}
}
if in_code_block && !code_buf.is_empty() {
final_out.push_str(&format!(
"<pre><code>{}</code></pre>\n",
code_buf.trim_end()
));
writeln!(final_out, "<pre><code>{}</code></pre>", code_buf.trim_end()).unwrap();
}
final_out.trim_end_matches('\n').to_string()
@ -2749,7 +2747,7 @@ mod tests {
"update_id": 1,
"message": {
"message_id": 99,
"chat": { "id": -100123456 }
"chat": { "id": -100_123_456 }
}
});
@ -3864,7 +3862,10 @@ mod tests {
#[test]
fn telegram_split_many_short_lines() {
let msg: String = (0..1000).map(|i| format!("line {i}\n")).collect();
let msg: String = (0..1000)
.map(|i| format!("line {i}\n"))
.collect::<Vec<_>>()
.concat();
let parts = split_message_for_telegram(&msg);
for part in &parts {
assert!(
@ -4170,7 +4171,7 @@ mod tests {
/// Skipped automatically when `GROQ_API_KEY` is not set.
/// Run: `GROQ_API_KEY=<key> cargo test --lib -- telegram::tests::e2e_live_voice_transcription_and_reply_cache --ignored`
#[tokio::test]
#[ignore]
#[ignore = "requires GROQ_API_KEY"]
async fn e2e_live_voice_transcription_and_reply_cache() {
if std::env::var("GROQ_API_KEY").is_err() {
eprintln!("GROQ_API_KEY not set — skipping live voice transcription test");

View File

@ -335,7 +335,7 @@ mod tests {
"text": "Hello from WATI!",
"waId": "1234567890",
"fromMe": false,
"timestamp": 1705320000u64
"timestamp": 1_705_320_000_u64
});
let msgs = ch.parse_webhook_payload(&payload);
@ -344,7 +344,7 @@ mod tests {
assert_eq!(msgs[0].content, "Hello from WATI!");
assert_eq!(msgs[0].channel, "wati");
assert_eq!(msgs[0].reply_target, "+1234567890");
assert_eq!(msgs[0].timestamp, 1705320000);
assert_eq!(msgs[0].timestamp, 1_705_320_000);
}
#[test]
@ -381,7 +381,7 @@ mod tests {
"message": { "body": "Alt field test" },
"wa_id": "1234567890",
"from_me": false,
"timestamp": 1705320000u64
"timestamp": 1_705_320_000_u64
});
let msgs = ch.parse_webhook_payload(&payload);
@ -396,11 +396,11 @@ mod tests {
let payload = serde_json::json!({
"text": "Test",
"waId": "1234567890",
"timestamp": 1705320000u64
"timestamp": 1_705_320_000_u64
});
let msgs = ch.parse_webhook_payload(&payload);
assert_eq!(msgs[0].timestamp, 1705320000);
assert_eq!(msgs[0].timestamp, 1_705_320_000);
}
#[test]
@ -409,11 +409,11 @@ mod tests {
let payload = serde_json::json!({
"text": "Test",
"waId": "1234567890",
"timestamp": 1705320000000u64
"timestamp": 1_705_320_000_000_u64
});
let msgs = ch.parse_webhook_payload(&payload);
assert_eq!(msgs[0].timestamp, 1705320000);
assert_eq!(msgs[0].timestamp, 1_705_320_000);
}
#[test]
@ -426,7 +426,7 @@ mod tests {
});
let msgs = ch.parse_webhook_payload(&payload);
assert_eq!(msgs[0].timestamp, 1736942400);
assert_eq!(msgs[0].timestamp, 1_736_942_400);
}
#[test]

View File

@ -19,7 +19,7 @@ pub use schema::{
WebFetchConfig, WebSearchConfig, WebhookConfig,
};
pub fn name_and_presence<T: traits::ChannelConfig>(channel: &Option<T>) -> (&'static str, bool) {
pub fn name_and_presence<T: traits::ChannelConfig>(channel: Option<&T>) -> (&'static str, bool) {
(T::name(), channel.is_some())
}

View File

@ -484,7 +484,7 @@ fn parse_skills_prompt_injection_mode(raw: &str) -> Option<SkillsPromptInjection
}
/// Skills loading configuration (`[skills]` section).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct SkillsConfig {
/// Enable loading and syncing the community open-skills repository.
/// Default: `false` (opt-in).
@ -500,16 +500,6 @@ pub struct SkillsConfig {
pub prompt_injection_mode: SkillsPromptInjectionMode,
}
impl Default for SkillsConfig {
fn default() -> Self {
Self {
open_skills_enabled: false,
open_skills_dir: None,
prompt_injection_mode: SkillsPromptInjectionMode::default(),
}
}
}
/// Multimodal (image) handling configuration (`[multimodal]` section).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct MultimodalConfig {
@ -1999,20 +1989,12 @@ impl Default for HooksConfig {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct BuiltinHooksConfig {
/// Enable the command-logger hook (logs tool calls for auditing).
pub command_logger: bool,
}
impl Default for BuiltinHooksConfig {
fn default() -> Self {
Self {
command_logger: false,
}
}
}
// ── Autonomy / Security ──────────────────────────────────────────
/// Autonomy and security policy configuration (`[autonomy]` section).
@ -2591,7 +2573,7 @@ pub struct CustomTunnelConfig {
struct ConfigWrapper<T: ChannelConfig>(std::marker::PhantomData<T>);
impl<T: ChannelConfig> ConfigWrapper<T> {
fn new(_: &Option<T>) -> Self {
fn new(_: Option<&T>) -> Self {
Self(std::marker::PhantomData)
}
}
@ -2667,81 +2649,81 @@ impl ChannelsConfig {
pub fn channels_except_webhook(&self) -> Vec<(Box<dyn super::traits::ConfigHandle>, bool)> {
vec![
(
Box::new(ConfigWrapper::new(&self.telegram)),
Box::new(ConfigWrapper::new(self.telegram.as_ref())),
self.telegram.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.discord)),
Box::new(ConfigWrapper::new(self.discord.as_ref())),
self.discord.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.slack)),
Box::new(ConfigWrapper::new(self.slack.as_ref())),
self.slack.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.mattermost)),
Box::new(ConfigWrapper::new(self.mattermost.as_ref())),
self.mattermost.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.imessage)),
Box::new(ConfigWrapper::new(self.imessage.as_ref())),
self.imessage.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.matrix)),
Box::new(ConfigWrapper::new(self.matrix.as_ref())),
self.matrix.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.signal)),
Box::new(ConfigWrapper::new(self.signal.as_ref())),
self.signal.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.whatsapp)),
Box::new(ConfigWrapper::new(self.whatsapp.as_ref())),
self.whatsapp.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.linq)),
Box::new(ConfigWrapper::new(self.linq.as_ref())),
self.linq.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.wati)),
Box::new(ConfigWrapper::new(self.wati.as_ref())),
self.wati.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.nextcloud_talk)),
Box::new(ConfigWrapper::new(self.nextcloud_talk.as_ref())),
self.nextcloud_talk.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.email)),
Box::new(ConfigWrapper::new(self.email.as_ref())),
self.email.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.irc)),
Box::new(ConfigWrapper::new(self.irc.as_ref())),
self.irc.is_some()
),
(
Box::new(ConfigWrapper::new(&self.lark)),
Box::new(ConfigWrapper::new(self.lark.as_ref())),
self.lark.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.feishu)),
Box::new(ConfigWrapper::new(self.feishu.as_ref())),
self.feishu.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.dingtalk)),
Box::new(ConfigWrapper::new(self.dingtalk.as_ref())),
self.dingtalk.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.qq)),
Box::new(ConfigWrapper::new(self.qq.as_ref())),
self.qq
.as_ref()
.is_some_and(|qq| qq.receive_mode == QQReceiveMode::Websocket)
),
(
Box::new(ConfigWrapper::new(&self.nostr)),
Box::new(ConfigWrapper::new(self.nostr.as_ref())),
self.nostr.is_some(),
),
(
Box::new(ConfigWrapper::new(&self.clawdtalk)),
Box::new(ConfigWrapper::new(self.clawdtalk.as_ref())),
self.clawdtalk.is_some(),
),
]
@ -2750,7 +2732,7 @@ impl ChannelsConfig {
pub fn channels(&self) -> Vec<(Box<dyn super::traits::ConfigHandle>, bool)> {
let mut ret = self.channels_except_webhook();
ret.push((
Box::new(ConfigWrapper::new(&self.webhook)),
Box::new(ConfigWrapper::new(self.webhook.as_ref())),
self.webhook.is_some(),
));
ret
@ -4915,7 +4897,7 @@ async fn sync_directory(path: &Path) -> Result<()> {
dir.sync_all()
.await
.with_context(|| format!("Failed to fsync directory metadata: {}", path.display()))?;
return Ok(());
Ok(())
}
#[cfg(not(unix))]

View File

@ -575,6 +575,7 @@ fn mask_vec_secrets(values: &mut [String]) {
}
}
#[allow(clippy::ref_option)]
fn restore_optional_secret(value: &mut Option<String>, current: &Option<String>) {
if value.as_deref().is_some_and(is_masked_secret) {
*value = current.clone();
@ -692,7 +693,7 @@ fn restore_masked_sensitive_fields(
&current.storage.provider.config.db_url,
);
for (name, agent) in incoming.agents.iter_mut() {
for (name, agent) in &mut incoming.agents {
if let Some(current_agent) = current.agents.get(name) {
restore_optional_secret(&mut agent.api_key, &current_agent.api_key);
}

View File

@ -71,8 +71,7 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) {
while let Some(msg) = socket.recv().await {
let msg = match msg {
Ok(Message::Text(text)) => text,
Ok(Message::Close(_)) => break,
Err(_) => break,
Ok(Message::Close(_)) | Err(_) => break,
_ => continue,
};

View File

@ -109,7 +109,7 @@ pub fn handle_command(cmd: crate::HardwareCommands, _config: &Config) -> Result<
let _ = &cmd;
println!("Hardware discovery requires the 'hardware' feature.");
println!("Build with: cargo build --features hardware");
return Ok(());
Ok(())
}
#[cfg(all(

View File

@ -794,8 +794,7 @@ async fn main() -> Result<()> {
}
match cli.command {
Commands::Onboard { .. } => unreachable!(),
Commands::Completions { .. } => unreachable!(),
Commands::Onboard { .. } | Commands::Completions { .. } => unreachable!(),
Commands::Agent {
message,

View File

@ -324,6 +324,7 @@ fn memory_date_from_filename(filename: &str) -> Option<NaiveDate> {
NaiveDate::parse_from_str(date_part, "%Y-%m-%d").ok()
}
#[allow(clippy::incompatible_msrv)]
fn date_prefix(filename: &str) -> Option<NaiveDate> {
if filename.len() < 10 {
return None;

View File

@ -22,7 +22,6 @@ pub enum RuntimeTraceStorageMode {
impl RuntimeTraceStorageMode {
fn from_raw(raw: &str) -> Self {
match raw.trim().to_ascii_lowercase().as_str() {
"none" => Self::None,
"rolling" => Self::Rolling,
"full" => Self::Full,
_ => Self::None,

View File

@ -1175,8 +1175,7 @@ fn models_endpoint_for_provider(provider_name: &str) -> Option<&'static str> {
"glm-cn" | "bigmodel" => Some("https://open.bigmodel.cn/api/paas/v4/models"),
"zai-cn" | "z.ai-cn" => Some("https://open.bigmodel.cn/api/coding/paas/v4/models"),
_ => match canonical_provider_name(provider_name) {
"openai-codex" => Some("https://api.openai.com/v1/models"),
"openai" => Some("https://api.openai.com/v1/models"),
"openai-codex" | "openai" => Some("https://api.openai.com/v1/models"),
"venice" => Some("https://api.venice.ai/api/v1/models"),
"groq" => Some("https://api.groq.com/openai/v1/models"),
"mistral" => Some("https://api.mistral.ai/v1/models"),
@ -2008,7 +2007,7 @@ fn resolve_interactive_onboarding_mode(
" Existing config found at {}. Select setup mode",
config_path.display()
))
.items(&options)
.items(options)
.default(1)
.interact()?;
@ -2856,8 +2855,7 @@ fn provider_env_var(name: &str) -> &'static str {
match canonical_provider_name(name) {
"openrouter" => "OPENROUTER_API_KEY",
"anthropic" => "ANTHROPIC_API_KEY",
"openai-codex" => "OPENAI_API_KEY",
"openai" => "OPENAI_API_KEY",
"openai-codex" | "openai" => "OPENAI_API_KEY",
"ollama" => "OLLAMA_API_KEY",
"llamacpp" => "LLAMACPP_API_KEY",
"sglang" => "SGLANG_API_KEY",
@ -5831,7 +5829,7 @@ mod tests {
apply_provider_update(
&mut config,
"anthropic".to_string(),
"".to_string(),
String::new(),
"claude-sonnet-4-5-20250929".to_string(),
None,
);

View File

@ -24,8 +24,6 @@ pub mod uno_q_setup;
#[cfg(all(feature = "peripheral-rpi", target_os = "linux"))]
pub mod rpi;
pub use traits::Peripheral;
use crate::config::{Config, PeripheralBoardConfig, PeripheralsConfig};
#[cfg(feature = "hardware")]
use crate::tools::HardwareMemoryMapTool;
@ -228,6 +226,7 @@ pub async fn create_peripheral_tools(config: &PeripheralsConfig) -> Result<Vec<B
}
#[cfg(not(feature = "hardware"))]
#[allow(clippy::unused_async)]
pub async fn create_peripheral_tools(_config: &PeripheralsConfig) -> Result<Vec<Box<dyn Tool>>> {
Ok(Vec::new())
}

View File

@ -710,12 +710,12 @@ impl BedrockProvider {
let after_semi = &rest[semi + 1..];
if let Some(b64) = after_semi.strip_prefix("base64,") {
let format = match mime {
"image/jpeg" | "image/jpg" => "jpeg",
"image/png" => "png",
"image/gif" => "gif",
"image/webp" => "webp",
_ => "jpeg",
};
blocks.push(ContentBlock::Image(ImageWrapper {
image: ImageBlock {
format: format.to_string(),

View File

@ -1375,8 +1375,8 @@ mod tests {
fn oauth_refresh_form_omits_client_credentials_when_missing() {
let form = build_oauth_refresh_form("refresh-token", None, None);
let map: std::collections::HashMap<_, _> = form.into_iter().collect();
assert!(map.get("client_id").is_none());
assert!(map.get("client_secret").is_none());
assert!(!map.contains_key("client_id"));
assert!(!map.contains_key("client_secret"));
}
#[test]
@ -1802,7 +1802,7 @@ mod tests {
let creds: GeminiCliOAuthCreds = serde_json::from_str(json).unwrap();
assert_eq!(creds.access_token.as_deref(), Some("ya29.test-token"));
assert_eq!(creds.refresh_token.as_deref(), Some("1//test-refresh"));
assert_eq!(creds.expiry_date, Some(4102444800000));
assert_eq!(creds.expiry_date, Some(4_102_444_800_000));
assert!(creds.expiry.is_none());
}
@ -1821,7 +1821,7 @@ mod tests {
assert_eq!(creds.id_token.as_deref(), Some("header.payload.sig"));
assert_eq!(creds.client_id.as_deref(), Some("test-client-id"));
assert_eq!(creds.client_secret.as_deref(), Some("test-client-secret"));
assert_eq!(creds.expiry_date, Some(4102444800000));
assert_eq!(creds.expiry_date, Some(4_102_444_800_000));
}
#[test]

View File

@ -281,7 +281,6 @@ fn clamp_reasoning_effort(model: &str, effort: &str) -> String {
return match effort {
"low" | "medium" | "high" => effort.to_string(),
"minimal" => "low".to_string(),
"xhigh" => "high".to_string(),
_ => "high".to_string(),
};
}

View File

@ -311,7 +311,7 @@ mod tests {
assert!(patterns.iter().any(|p| p.contains("Stripe")));
assert!(redacted.contains("[REDACTED"));
}
_ => panic!("Should detect Stripe key"),
LeakResult::Clean => panic!("Should detect Stripe key"),
}
}
@ -324,7 +324,7 @@ mod tests {
LeakResult::Detected { patterns, .. } => {
assert!(patterns.iter().any(|p| p.contains("AWS")));
}
_ => panic!("Should detect AWS key"),
LeakResult::Clean => panic!("Should detect AWS key"),
}
}
@ -342,7 +342,7 @@ MIIEowIBAAKCAQEA0ZPr5JeyVDonXsKhfq...
assert!(patterns.iter().any(|p| p.contains("private key")));
assert!(redacted.contains("[REDACTED_PRIVATE_KEY]"));
}
_ => panic!("Should detect private key"),
LeakResult::Clean => panic!("Should detect private key"),
}
}
@ -356,7 +356,7 @@ MIIEowIBAAKCAQEA0ZPr5JeyVDonXsKhfq...
assert!(patterns.iter().any(|p| p.contains("JWT")));
assert!(redacted.contains("[REDACTED_JWT]"));
}
_ => panic!("Should detect JWT"),
LeakResult::Clean => panic!("Should detect JWT"),
}
}
@ -369,7 +369,7 @@ MIIEowIBAAKCAQEA0ZPr5JeyVDonXsKhfq...
LeakResult::Detected { patterns, .. } => {
assert!(patterns.iter().any(|p| p.contains("PostgreSQL")));
}
_ => panic!("Should detect database URL"),
LeakResult::Clean => panic!("Should detect database URL"),
}
}

View File

@ -114,7 +114,9 @@ impl PromptGuard {
// Normalize score to 0.0-1.0 range (max possible is 6.0, one per category)
let normalized_score = (total_score / 6.0).min(1.0);
if !detected_patterns.is_empty() {
if detected_patterns.is_empty() {
GuardResult::Safe
} else {
match self.action {
GuardAction::Block if max_score > self.sensitivity => {
GuardResult::Blocked(format!(
@ -125,8 +127,6 @@ impl PromptGuard {
}
_ => GuardResult::Suspicious(detected_patterns, normalized_score),
}
} else {
GuardResult::Safe
}
}

View File

@ -200,12 +200,12 @@ fn audit_manifest_file(root: &Path, path: &Path, report: &mut SkillAuditReport)
.push(format!("{rel}: tools[{idx}] is missing a command field."));
}
if kind.eq_ignore_ascii_case("script") || kind.eq_ignore_ascii_case("shell") {
if command.is_some_and(|value| value.trim().is_empty()) {
report
.findings
.push(format!("{rel}: tools[{idx}] has an empty {kind} command."));
}
if (kind.eq_ignore_ascii_case("script") || kind.eq_ignore_ascii_case("shell"))
&& command.is_some_and(|value| value.trim().is_empty())
{
report
.findings
.push(format!("{rel}: tools[{idx}] has an empty {kind} command."));
}
}
}

View File

@ -238,7 +238,7 @@ fn open_skills_enabled_from_sources(
env_override: Option<&str>,
) -> bool {
if let Some(raw) = env_override {
if let Some(enabled) = parse_open_skills_enabled(&raw) {
if let Some(enabled) = parse_open_skills_enabled(raw) {
return enabled;
}
if !raw.trim().is_empty() {

View File

@ -150,6 +150,7 @@ impl ScreenshotTool {
}
/// Read the screenshot file and return base64-encoded result.
#[allow(clippy::incompatible_msrv)]
async fn read_and_encode(output_path: &std::path::Path) -> anyhow::Result<ToolResult> {
// Check file size before reading to prevent OOM on large screenshots
const MAX_RAW_BYTES: u64 = 1_572_864; // ~1.5 MB (base64 expands ~33%)

View File

@ -85,6 +85,7 @@ impl Tool for ShellTool {
})
}
#[allow(clippy::incompatible_msrv)]
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
let command = args
.get("command")

View File

@ -1,3 +1,4 @@
#![allow(clippy::field_reassign_with_default)]
//! TG2: Config Load/Save Round-Trip Tests
//!
//! Prevents: Pattern 2 — Config persistence & workspace discovery bugs (13% of user bugs).

View File

@ -1,3 +1,4 @@
#![allow(clippy::field_reassign_with_default)]
//! Config Schema Boundary Tests
//!
//! Validates: config defaults, backward compatibility, invalid input rejection,

View File

@ -4,7 +4,9 @@
//! 1. Primary provider (OpenAI Codex) fails
//! 2. Fallback to Gemini is triggered
//! 3. Gemini OAuth tokens are expired (we manually expire them)
//!
//! Then:
//!
//! - Gemini provider's warmup() automatically refreshes the tokens
//! - The fallback request succeeds
//!

View File

@ -12,7 +12,7 @@
//! Run manually: `cargo test provider_vision -- --ignored --nocapture`
use anyhow::Result;
use zeroclaw::providers::{ChatMessage, ChatRequest, Provider, ProviderRuntimeOptions};
use zeroclaw::providers::{ChatMessage, ChatRequest, ProviderRuntimeOptions};
/// Tests that provider supports vision input.
///

View File

@ -292,7 +292,7 @@ fn provider_construction_with_different_auth_styles() {
#[test]
fn chat_messages_maintain_role_sequence() {
let history = vec![
let history = [
ChatMessage::system("You are helpful"),
ChatMessage::user("What is Rust?"),
ChatMessage::assistant("Rust is a systems programming language"),
@ -309,7 +309,7 @@ fn chat_messages_maintain_role_sequence() {
#[test]
fn chat_messages_with_tool_calls_maintain_sequence() {
let history = vec![
let history = [
ChatMessage::system("You are helpful"),
ChatMessage::user("Search for Rust"),
ChatMessage::assistant("I'll search for that"),