ci: add GitHub Actions workflows with semantic-release auto-versioning
chore: fix all 702 clippy warnings across codebase - auto-fix 475 via cargo clippy --fix - fix remaining 227 manually: uninlined_format_args, redundant_closure, match_same_arms, underscore_binding, format_push_string, items_after_statements, needless_pass_by_value, clone_on_copy, case_sensitive_extension, single_match/let-else, write_with_newline, and other clippy lints
This commit is contained in:
+40
-41
@@ -7,6 +7,7 @@
|
||||
//! bash exfiltration and destructive-pattern detection) so that subagents
|
||||
//! are not a weaker link than the main agent.
|
||||
|
||||
use std::fmt::Write;
|
||||
use tokio::sync::mpsc;
|
||||
use crate::dto::chat::message::ChatMessage;
|
||||
use crate::dto::provider::request::ToolDef;
|
||||
@@ -143,8 +144,7 @@ fn gate_subagent_tool_call(
|
||||
let reason = args.get("reason").and_then(|v| v.as_str()).unwrap_or("");
|
||||
if reason.trim().len() < MIN_REASON_LEN {
|
||||
return Some(format!(
|
||||
"{} requires a non-trivial 'reason' (>= {} chars) explaining why",
|
||||
tool_name, MIN_REASON_LEN,
|
||||
"{tool_name} requires a non-trivial 'reason' (>= {MIN_REASON_LEN} chars) explaining why",
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -157,9 +157,7 @@ fn gate_subagent_tool_call(
|
||||
let old = args.get("old").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let new = args.get("new").and_then(|v| v.as_str()).unwrap_or("");
|
||||
// For edits, scanning old+new together catches stubs in both
|
||||
return if contains_any(old, STUB_PATTERNS) {
|
||||
Some("content contains stub/placeholder pattern; production code must be fully implemented".to_string())
|
||||
} else if contains_any(new, STUB_PATTERNS) {
|
||||
return if contains_any(old, STUB_PATTERNS) || contains_any(new, STUB_PATTERNS) {
|
||||
Some("content contains stub/placeholder pattern; production code must be fully implemented".to_string())
|
||||
} else if contains_any(new, DENIAL_PATTERNS) {
|
||||
Some("content contains denial/punt pattern; implement properly instead of skipping".to_string())
|
||||
@@ -202,13 +200,13 @@ fn gate_subagent_tool_call(
|
||||
if !is_standard {
|
||||
for pat in EXFIL_PATTERNS {
|
||||
if cmd.contains(pat) {
|
||||
return Some(format!("potential data-exfiltration command blocked (matched '{}')", pat));
|
||||
return Some(format!("potential data-exfiltration command blocked (matched '{pat}')"));
|
||||
}
|
||||
}
|
||||
}
|
||||
for pat in SENSITIVE_PATH_PATTERNS {
|
||||
if cmd.contains(pat) {
|
||||
return Some(format!("refused to read/write sensitive path '{}'", pat));
|
||||
return Some(format!("refused to read/write sensitive path '{pat}'"));
|
||||
}
|
||||
}
|
||||
let dangerous = ["rm -rf /", "rm -rf --no-preserve-root", "rm -rf ~",
|
||||
@@ -216,7 +214,7 @@ fn gate_subagent_tool_call(
|
||||
"chmod -R 000 /", "shutdown ", "poweroff ", "reboot ", "halt "];
|
||||
for pat in &dangerous {
|
||||
if cmd.contains(pat) {
|
||||
return Some(format!("destructive command pattern blocked: {}", pat));
|
||||
return Some(format!("destructive command pattern blocked: {pat}"));
|
||||
}
|
||||
}
|
||||
if contains_any(cmd, STUB_PATTERNS) {
|
||||
@@ -251,7 +249,7 @@ fn generate_workspace_tree(roots: &[std::path::PathBuf]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("Current Workspace Directory Structure:\n");
|
||||
for root in roots {
|
||||
out.push_str(&format!("Root: {}\n", root.display()));
|
||||
writeln!(out, "Root: {}", root.display()).unwrap();
|
||||
let walker = ignore::WalkBuilder::new(root)
|
||||
.hidden(true)
|
||||
.git_ignore(true)
|
||||
@@ -261,9 +259,9 @@ fn generate_workspace_tree(roots: &[std::path::PathBuf]) -> String {
|
||||
let path = entry.path();
|
||||
if let Ok(rel) = path.strip_prefix(root) {
|
||||
if rel.as_os_str().is_empty() { continue; }
|
||||
let is_dir = entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false);
|
||||
let is_dir = entry.file_type().is_some_and(|ft| ft.is_dir());
|
||||
let prefix = if is_dir { "[DIR] " } else { " " };
|
||||
out.push_str(&format!(" {}{}\n", prefix, rel.display()));
|
||||
writeln!(out, " {}{}", prefix, rel.display()).unwrap();
|
||||
count += 1;
|
||||
if count > 1000 {
|
||||
out.push_str(" ... (truncated)\n");
|
||||
@@ -291,7 +289,8 @@ fn generate_workspace_tree(roots: &[std::path::PathBuf]) -> String {
|
||||
///
|
||||
/// Return: the concatenated text output, or an `anyhow::Error` if the LLM
|
||||
/// call fails at any step.
|
||||
pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> anyhow::Result<String> {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn run_subagent(ctx: &SubagentContext, tx: &mpsc::Sender<SubagentEvent>) -> anyhow::Result<String> {
|
||||
let mut output = String::new();
|
||||
let mut messages: Vec<ChatMessage> = Vec::new();
|
||||
|
||||
@@ -328,10 +327,10 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
|
||||
// be cancelled from the parent (mirrors main agent behaviour).
|
||||
if ctx.abort_flag.as_ref().is_some_and(|f| f.load(std::sync::atomic::Ordering::SeqCst)) {
|
||||
let _ = tx.blocking_send(SubagentEvent::StepFailed {
|
||||
_step: step,
|
||||
_error: "subagent aborted by parent".to_string(),
|
||||
step,
|
||||
error: "subagent aborted by parent".to_string(),
|
||||
});
|
||||
anyhow::bail!("subagent aborted by parent at step {}", step);
|
||||
anyhow::bail!("subagent aborted by parent at step {step}");
|
||||
}
|
||||
|
||||
// Use the structured tool-calling API so the LLM can request tools with
|
||||
@@ -340,10 +339,10 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
let _ = tx.blocking_send(SubagentEvent::StepFailed {
|
||||
_step: step,
|
||||
_error: e.to_string(),
|
||||
step,
|
||||
error: e.to_string(),
|
||||
});
|
||||
anyhow::bail!("subagent call failed at step {}: {}", step, e);
|
||||
anyhow::bail!("subagent call failed at step {step}: {e}");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -361,10 +360,10 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
|
||||
// Check abort flag before each tool execution
|
||||
if ctx.abort_flag.as_ref().is_some_and(|f| f.load(std::sync::atomic::Ordering::SeqCst)) {
|
||||
let _ = tx.blocking_send(SubagentEvent::StepFailed {
|
||||
_step: step,
|
||||
_error: "subagent aborted by parent during tool execution".to_string(),
|
||||
step,
|
||||
error: "subagent aborted by parent during tool execution".to_string(),
|
||||
});
|
||||
anyhow::bail!("subagent aborted by parent during tool call at step {}", step);
|
||||
anyhow::bail!("subagent aborted by parent during tool call at step {step}");
|
||||
}
|
||||
|
||||
let tool_name = &tool_call.function.name;
|
||||
@@ -373,28 +372,28 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
|
||||
let generally_allowed = ctx.allowed_tools.is_empty() || explicitly_allowed;
|
||||
|
||||
let _ = tx.blocking_send(SubagentEvent::ToolCall {
|
||||
_tool: tool_name.clone(),
|
||||
_args: args.clone(),
|
||||
tool: tool_name.clone(),
|
||||
args: args.clone(),
|
||||
});
|
||||
|
||||
// Level 1: allowlist check — is this tool even permitted?
|
||||
if !generally_allowed {
|
||||
let msg = format!("tool '{}' not allowed for this subagent", tool_name);
|
||||
let msg = format!("tool '{tool_name}' not allowed for this subagent");
|
||||
messages.push(ChatMessage::tool_result(tool_call.id.clone(), msg.clone()));
|
||||
let _ = tx.blocking_send(SubagentEvent::ToolResult {
|
||||
_tool: tool_name.clone(),
|
||||
_output: msg,
|
||||
tool: tool_name.clone(),
|
||||
output: msg,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Level 2: risky tool check — risky tools require explicit permission
|
||||
if tool_is_risky(tool_name) && !explicitly_allowed {
|
||||
let msg = format!("risky tool '{}' requires explicit permission; not allowed for this subagent", tool_name);
|
||||
let msg = format!("risky tool '{tool_name}' requires explicit permission; not allowed for this subagent");
|
||||
messages.push(ChatMessage::tool_result(tool_call.id.clone(), msg.clone()));
|
||||
let _ = tx.blocking_send(SubagentEvent::ToolResult {
|
||||
_tool: tool_name.clone(),
|
||||
_output: msg,
|
||||
tool: tool_name.clone(),
|
||||
output: msg,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -404,34 +403,34 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
|
||||
// stub/denial/assumption scanning, bash exfiltration, destructive
|
||||
// commands, sensitive path reads).
|
||||
if let Some(block_reason) = gate_subagent_tool_call(tool_name, &args) {
|
||||
let msg = format!("Blocked by subagent gate: {}", block_reason);
|
||||
let msg = format!("Blocked by subagent gate: {block_reason}");
|
||||
messages.push(ChatMessage::tool_result(tool_call.id.clone(), msg.clone()));
|
||||
let _ = tx.blocking_send(SubagentEvent::ToolResult {
|
||||
_tool: tool_name.clone(),
|
||||
_output: msg,
|
||||
tool: tool_name.clone(),
|
||||
output: msg,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
let result = match tools.iter().find(|t| t.name() == tool_name.as_str()) {
|
||||
Some(tool) => tool.run(&tool_ctx, &args),
|
||||
None => Err(anyhow::anyhow!("tool '{}' not found", tool_name)),
|
||||
None => Err(anyhow::anyhow!("tool '{tool_name}' not found")),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(output_text) => {
|
||||
messages.push(ChatMessage::tool_result(tool_call.id.clone(), output_text.clone()));
|
||||
let _ = tx.blocking_send(SubagentEvent::ToolResult {
|
||||
_tool: tool_name.clone(),
|
||||
_output: output_text,
|
||||
tool: tool_name.clone(),
|
||||
output: output_text,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = format!("tool '{}' failed: {}", tool_name, e);
|
||||
let msg = format!("tool '{tool_name}' failed: {e}");
|
||||
messages.push(ChatMessage::tool_result(tool_call.id.clone(), msg.clone()));
|
||||
let _ = tx.blocking_send(SubagentEvent::ToolResult {
|
||||
_tool: tool_name.clone(),
|
||||
_output: msg,
|
||||
tool: tool_name.clone(),
|
||||
output: msg,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -443,8 +442,8 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
|
||||
output.push('\n');
|
||||
}
|
||||
let _ = tx.blocking_send(SubagentEvent::StepCompleted {
|
||||
_step: step,
|
||||
_output: content.clone(),
|
||||
step,
|
||||
output: content.clone(),
|
||||
});
|
||||
// Break only when we got real content; empty means something went wrong
|
||||
if !content.is_empty() {
|
||||
@@ -453,6 +452,6 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
|
||||
}
|
||||
}
|
||||
|
||||
let _ = tx.blocking_send(SubagentEvent::Completed { _output: output.clone() });
|
||||
let _ = tx.blocking_send(SubagentEvent::Completed { output: output.clone() });
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user