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:
parent
b36dd3aa81
commit
49e90cf3e4
@ -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.
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -429,7 +429,7 @@ mod tests {
|
||||
"message": {
|
||||
"actorType": "users",
|
||||
"actorId": "user_a",
|
||||
"timestamp": 1735701200123u64,
|
||||
"timestamp": 1_735_701_200_123_u64,
|
||||
"message": "hello"
|
||||
}
|
||||
});
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
|
||||
@ -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))]
|
||||
|
||||
@ -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(
|
||||
¤t.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, ¤t_agent.api_key);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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%)
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
//! Config Schema Boundary Tests
|
||||
//!
|
||||
//! Validates: config defaults, backward compatibility, invalid input rejection,
|
||||
|
||||
@ -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
|
||||
//!
|
||||
|
||||
@ -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.
|
||||
///
|
||||
|
||||
@ -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"),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user