Files
zclaw_openfang/crates/zclaw-hands/src/hand.rs
iven 26a833d1c8
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
fix: resolve 17 P2 defects and 5 P3 defects from pre-launch audit
Batch fix covering multiple modules:
- P2-01: HandRegistry Semaphore-based max_concurrent enforcement
- P2-03: Populate toolCount/metricCount from Hand trait methods
- P2-06: heartbeat_update_config minimum interval validation
- P2-07: ReflectionResult used_fallback marker for rule-based fallback
- P2-08/09: identity_propose_change parameter naming consistency
- P2-10: ClassroomMetadata is_placeholder flag for LLM failure
- P2-11: classroomStore userDidCloseDuringGeneration intent tracking
- P2-12: workflowStore pipeline_create sends actionType
- P2-13/14: PipelineInfo step_count + PipelineStepInfo for proper step mapping
- P2-15: Pipe transform support in context.resolve (8 transforms)
- P2-16: Mustache {{...}} → \${...} auto-normalization
- P2-17: SaaSLogin password placeholder 6→8
- P2-19: serialize_skill_md + update_skill preserve tools field
- P2-22: ToolOutputGuard sensitive patterns from warn→block
- P2-23: Mutex::unwrap() → unwrap_or_else in relay/service.rs
- P3-01/03/07/08/09: Various P3 fixes
- DEFECT_LIST.md: comprehensive status sync (43/51 fixed, 8 remaining)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:49:16 +08:00

190 lines
4.8 KiB
Rust

//! Hand definition and types
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use zclaw_types::{Result, AgentId};
/// Hand configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HandConfig {
/// Unique hand identifier
pub id: String,
/// Human-readable name
pub name: String,
/// Hand description
pub description: String,
/// Whether this hand needs approval before execution
#[serde(default)]
pub needs_approval: bool,
/// Required dependencies
#[serde(default)]
pub dependencies: Vec<String>,
/// Input schema
#[serde(default)]
pub input_schema: Option<Value>,
/// Tags for categorization
#[serde(default)]
pub tags: Vec<String>,
/// Whether the hand is enabled
#[serde(default = "default_enabled")]
pub enabled: bool,
/// Maximum concurrent executions for this hand (0 = unlimited)
#[serde(default)]
pub max_concurrent: u32,
/// Timeout in seconds for each execution (0 = use HandContext default)
#[serde(default)]
pub timeout_secs: u64,
}
impl Default for HandConfig {
fn default() -> Self {
Self {
id: String::new(),
name: String::new(),
description: String::new(),
needs_approval: false,
dependencies: Vec::new(),
input_schema: None,
tags: Vec::new(),
enabled: true,
max_concurrent: 0,
timeout_secs: 0,
}
}
}
fn default_enabled() -> bool { true }
/// Hand execution context
#[derive(Debug, Clone)]
pub struct HandContext {
/// Agent ID executing the hand
pub agent_id: AgentId,
/// Working directory
pub working_dir: Option<std::path::PathBuf>,
/// Environment variables
pub env: std::collections::HashMap<String, String>,
/// Timeout in seconds
pub timeout_secs: u64,
/// Callback URL for async results
pub callback_url: Option<String>,
}
impl Default for HandContext {
fn default() -> Self {
Self {
agent_id: AgentId::new(),
working_dir: None,
env: std::collections::HashMap::new(),
timeout_secs: 300,
callback_url: None,
}
}
}
/// Hand execution result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HandResult {
/// Whether execution succeeded
pub success: bool,
/// Output data
pub output: Value,
/// Error message if failed
#[serde(default)]
pub error: Option<String>,
/// Execution duration in milliseconds
#[serde(default)]
pub duration_ms: Option<u64>,
/// Status message
#[serde(default)]
pub status: String,
}
impl HandResult {
pub fn success(output: Value) -> Self {
Self {
success: true,
output,
error: None,
duration_ms: None,
status: "completed".to_string(),
}
}
pub fn error(message: impl Into<String>) -> Self {
Self {
success: false,
output: Value::Null,
error: Some(message.into()),
duration_ms: None,
status: "failed".to_string(),
}
}
pub fn pending(status: impl Into<String>) -> Self {
Self {
success: true,
output: Value::Null,
error: None,
duration_ms: None,
status: status.into(),
}
}
}
/// Hand execution status
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HandStatus {
Idle,
Running,
PendingApproval,
Completed,
Failed,
}
/// Hand trait - autonomous capability
#[async_trait]
pub trait Hand: Send + Sync {
/// Get the hand configuration
fn config(&self) -> &HandConfig;
/// Execute the hand
async fn execute(&self, context: &HandContext, input: Value) -> Result<HandResult>;
/// Check if the hand needs approval
fn needs_approval(&self) -> bool {
self.config().needs_approval
}
/// Check dependencies
fn check_dependencies(&self) -> Result<Vec<String>> {
let missing: Vec<String> = self.config().dependencies.iter()
.filter(|dep| !self.is_dependency_available(dep))
.cloned()
.collect();
Ok(missing)
}
/// Check if a specific dependency is available
fn is_dependency_available(&self, _dep: &str) -> bool {
true // Default implementation
}
/// Get current status
fn status(&self) -> HandStatus {
HandStatus::Idle
}
/// P2-03: Get the number of tools this hand exposes (default: 0)
fn tool_count(&self) -> u32 {
0
}
/// P2-03: Get the number of metrics this hand tracks (default: 0)
fn metric_count(&self) -> u32 {
0
}
}