refactor(crates): kernel/generation module split + DeerFlow optimizations + middleware + dead code cleanup
- Split zclaw-kernel/kernel.rs (1486 lines) into 9 domain modules - Split zclaw-kernel/generation.rs (1080 lines) into 3 modules - Add DeerFlow-inspired middleware: DanglingTool, SubagentLimit, ToolError, ToolOutputGuard - Add PromptBuilder for structured system prompt assembly - Add FactStore (zclaw-memory) for persistent fact extraction - Add task builtin tool for agent task management - Driver improvements: Anthropic/OpenAI extended thinking, Gemini safety settings - Replace let _ = with proper log::warn! across SaaS handlers - Remove unused dependency (url) from zclaw-hands
This commit is contained in:
155
crates/zclaw-kernel/src/kernel/approvals.rs
Normal file
155
crates/zclaw-kernel/src/kernel/approvals.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
//! Approval management
|
||||
|
||||
use std::sync::Arc;
|
||||
use serde_json::Value;
|
||||
use zclaw_types::{Result, HandRun, HandRunId, HandRunStatus, TriggerSource};
|
||||
use zclaw_hands::HandContext;
|
||||
|
||||
use super::Kernel;
|
||||
|
||||
impl Kernel {
|
||||
// ============================================================
|
||||
// Approval Management
|
||||
// ============================================================
|
||||
|
||||
/// List pending approvals
|
||||
pub async fn list_approvals(&self) -> Vec<super::ApprovalEntry> {
|
||||
let approvals = self.pending_approvals.lock().await;
|
||||
approvals.iter().filter(|a| a.status == "pending").cloned().collect()
|
||||
}
|
||||
|
||||
/// Get a single approval by ID (any status, not just pending)
|
||||
///
|
||||
/// Returns None if no approval with the given ID exists.
|
||||
pub async fn get_approval(&self, id: &str) -> Option<super::ApprovalEntry> {
|
||||
let approvals = self.pending_approvals.lock().await;
|
||||
approvals.iter().find(|a| a.id == id).cloned()
|
||||
}
|
||||
|
||||
/// Create a pending approval (called when a needs_approval hand is triggered)
|
||||
pub async fn create_approval(&self, hand_id: String, input: serde_json::Value) -> super::ApprovalEntry {
|
||||
let entry = super::ApprovalEntry {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
hand_id,
|
||||
status: "pending".to_string(),
|
||||
created_at: chrono::Utc::now(),
|
||||
input,
|
||||
reject_reason: None,
|
||||
};
|
||||
let mut approvals = self.pending_approvals.lock().await;
|
||||
approvals.push(entry.clone());
|
||||
entry
|
||||
}
|
||||
|
||||
/// Respond to an approval
|
||||
pub async fn respond_to_approval(
|
||||
&self,
|
||||
id: &str,
|
||||
approved: bool,
|
||||
reason: Option<String>,
|
||||
) -> Result<()> {
|
||||
let mut approvals = self.pending_approvals.lock().await;
|
||||
let entry = approvals.iter_mut().find(|a| a.id == id && a.status == "pending")
|
||||
.ok_or_else(|| zclaw_types::ZclawError::NotFound(format!("Approval not found: {}", id)))?;
|
||||
|
||||
entry.status = if approved { "approved".to_string() } else { "rejected".to_string() };
|
||||
if let Some(r) = reason {
|
||||
entry.reject_reason = Some(r);
|
||||
}
|
||||
|
||||
if approved {
|
||||
let hand_id = entry.hand_id.clone();
|
||||
let input = entry.input.clone();
|
||||
drop(approvals); // Release lock before async hand execution
|
||||
|
||||
// Execute the hand in background with HandRun tracking
|
||||
let hands = self.hands.clone();
|
||||
let approvals = self.pending_approvals.clone();
|
||||
let memory = self.memory.clone();
|
||||
let running_hand_runs = self.running_hand_runs.clone();
|
||||
let id_owned = id.to_string();
|
||||
tokio::spawn(async move {
|
||||
// Create HandRun record for tracking
|
||||
let run_id = HandRunId::new();
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
let mut run = HandRun {
|
||||
id: run_id,
|
||||
hand_name: hand_id.clone(),
|
||||
trigger_source: TriggerSource::Manual,
|
||||
params: input.clone(),
|
||||
status: HandRunStatus::Pending,
|
||||
result: None,
|
||||
error: None,
|
||||
duration_ms: None,
|
||||
created_at: now.clone(),
|
||||
started_at: None,
|
||||
completed_at: None,
|
||||
};
|
||||
let _ = memory.save_hand_run(&run).await.map_err(|e| {
|
||||
tracing::warn!("[Approval] Failed to save hand run: {}", e);
|
||||
});
|
||||
run.status = HandRunStatus::Running;
|
||||
run.started_at = Some(chrono::Utc::now().to_rfc3339());
|
||||
let _ = memory.update_hand_run(&run).await.map_err(|e| {
|
||||
tracing::warn!("[Approval] Failed to update hand run (running): {}", e);
|
||||
});
|
||||
|
||||
// Register cancellation flag
|
||||
let cancel_flag = Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||
running_hand_runs.insert(run.id, cancel_flag.clone());
|
||||
|
||||
let context = HandContext::default();
|
||||
let start = std::time::Instant::now();
|
||||
let result = hands.execute(&hand_id, &context, input).await;
|
||||
let duration = start.elapsed();
|
||||
|
||||
// Remove from running map
|
||||
running_hand_runs.remove(&run.id);
|
||||
|
||||
// Update HandRun with result
|
||||
let completed_at = chrono::Utc::now().to_rfc3339();
|
||||
match &result {
|
||||
Ok(res) => {
|
||||
run.status = HandRunStatus::Completed;
|
||||
run.result = Some(res.output.clone());
|
||||
run.error = res.error.clone();
|
||||
}
|
||||
Err(e) => {
|
||||
run.status = HandRunStatus::Failed;
|
||||
run.error = Some(e.to_string());
|
||||
}
|
||||
}
|
||||
run.duration_ms = Some(duration.as_millis() as u64);
|
||||
run.completed_at = Some(completed_at);
|
||||
let _ = memory.update_hand_run(&run).await.map_err(|e| {
|
||||
tracing::warn!("[Approval] Failed to update hand run (completed): {}", e);
|
||||
});
|
||||
|
||||
// Update approval status based on execution result
|
||||
let mut approvals = approvals.lock().await;
|
||||
if let Some(entry) = approvals.iter_mut().find(|a| a.id == id_owned) {
|
||||
match result {
|
||||
Ok(_) => entry.status = "completed".to_string(),
|
||||
Err(e) => {
|
||||
entry.status = "failed".to_string();
|
||||
if let Some(obj) = entry.input.as_object_mut() {
|
||||
obj.insert("error".to_string(), Value::String(format!("{}", e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cancel a pending approval
|
||||
pub async fn cancel_approval(&self, id: &str) -> Result<()> {
|
||||
let mut approvals = self.pending_approvals.lock().await;
|
||||
let entry = approvals.iter_mut().find(|a| a.id == id && a.status == "pending")
|
||||
.ok_or_else(|| zclaw_types::ZclawError::NotFound(format!("Approval not found: {}", id)))?;
|
||||
entry.status = "cancelled".to_string();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user