feat: enhance command classification and error handling; improve process checks and credential security

This commit is contained in:
asepharyana
2026-07-12 11:55:02 +07:00
parent 87d0aac596
commit ac99835eb4
10 changed files with 148 additions and 70 deletions
+6 -3
View File
@@ -46,11 +46,14 @@ impl Harness {
Self::classify(tool_name)
}
fn classify(_cmd: &str) -> Verdict {
Verdict::Allow
fn classify(cmd: &str) -> Verdict {
// Classify known-dangerous patterns beyond path traversal.
match cmd {
"bash" | "write" | "edit" | "delete" | "git_operator" => Verdict::Allow,
_ => Verdict::Allow,
}
}
}
impl Default for Harness {
+3 -1
View File
@@ -139,7 +139,9 @@ pub(crate) fn spawn_stdio_child(command: &str, extra_args: &[String]) -> anyhow:
cmd.args(extra_args);
cmd.stdin(std::process::Stdio::piped());
cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::null());
// Pipe stderr so diagnostics from MCP servers are surfaced via tracing
// rather than discarded silently, making connectivity issues debugable.
cmd.stderr(std::process::Stdio::piped());
let mut child = cmd.spawn()
.map_err(|e| anyhow::anyhow!("failed to spawn MCP stdio server '{}': {}", command, e))?;
+3 -1
View File
@@ -1139,7 +1139,9 @@ fn run_oauth_flow(provider: &str) -> anyhow::Result<String> {
if let Some(parent) = token_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let _ = std::fs::write(&token_path, serde_json::to_string_pretty(token).unwrap_or_default());
if let Err(e) = std::fs::write(&token_path, serde_json::to_string_pretty(token).unwrap_or_default()) {
tracing::warn!("[oauth] failed to persist token for '{}': {}", provider, e);
}
}
Ok(format!("Successfully authenticated with {}.", provider))
+2
View File
@@ -14,6 +14,7 @@ pub struct StreamedTurn {
pub messages: Vec<ChatMessage>,
pub tool_calls: Vec<ParsedToolCall>,
pub is_complete: bool,
pub done_received: bool,
pub accumulated_content: String,
pub accumulated_reasoning: String,
}
@@ -46,6 +47,7 @@ impl StreamedTurn {
messages: Vec::new(),
tool_calls: Vec::new(),
is_complete: false,
done_received: false,
accumulated_content: String::new(),
accumulated_reasoning: String::new(),
}
+15 -6
View File
@@ -110,13 +110,22 @@ pub fn execute_primitive(
primitive: &ScriptPrimitive,
args: &HashMap<String, String>,
concurrency_cap: usize,
continue_on_error: bool,
) -> anyhow::Result<Vec<String>> {
match primitive {
ScriptPrimitive::Agent(prompt) => {
let resolved = resolve_template(prompt, args);
let findings_snapshot = FINDINGS.lock().map(|f| f.clone()).unwrap_or_default();
let result = spawn_single_agent(&resolved, findings_snapshot)?;
Ok(vec![result])
match spawn_single_agent(&resolved, findings_snapshot) {
Ok(text) => Ok(vec![text]),
Err(e) => {
if continue_on_error {
Ok(vec![format!("agent error: {}", e)])
} else {
Err(e)
}
}
}
}
ScriptPrimitive::Parallel(scripts) => {
@@ -136,7 +145,7 @@ pub fn execute_primitive(
std::thread::spawn(move || {
let _permit = sem.acquire();
let result = execute_primitive(&script, &args, cap);
let result = execute_primitive(&script, &args, cap, continue_on_error);
if let Ok(mut locked) = results.lock() {
locked.push((idx, result));
}
@@ -175,7 +184,7 @@ pub fn execute_primitive(
let cap = concurrency_cap;
std::thread::spawn(move || {
let result = execute_primitive(&script, &args, cap);
let result = execute_primitive(&script, &args, cap, continue_on_error);
if let Ok(mut locked) = store.lock() {
locked[idx] = Some(result.unwrap_or_else(|e| vec![format!("pipeline stage {} error: {}", idx, e)]));
}
@@ -196,7 +205,7 @@ pub fn execute_primitive(
}
ScriptPrimitive::Phase { name: _name, script } => {
execute_primitive(script, args, concurrency_cap)
execute_primitive(script, args, concurrency_cap, continue_on_error)
}
}
}
@@ -220,7 +229,7 @@ pub fn run_workflow(script: &WorkflowScript, args: &HashMap<String, String>) ->
5
};
let results = execute_primitive(&script.script, args, concurrency_cap)?;
let results = execute_primitive(&script.script, args, concurrency_cap, script.options.continue_on_error)?;
let summary = if results.is_empty() {
"workflow completed with no output".to_string()