feat(hands): implement 4 new Hands and fix BrowserHand registration
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
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
- Add ResearcherHand: DuckDuckGo search, web fetch, report generation - Add CollectorHand: data collection, aggregation, multiple output formats - Add ClipHand: video processing (trim, convert, thumbnail, concat) - Add TwitterHand: Twitter/X automation (tweet, retweet, like, search) - Fix BrowserHand not registered in Kernel (critical bug) - Add HandError variant to ZclawError enum - Update documentation: 9/11 Hands implemented (82%) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,13 +3,47 @@
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use zclaw_types::{AgentConfig, AgentId, AgentInfo, Event, Result};
|
||||
use async_trait::async_trait;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::registry::AgentRegistry;
|
||||
use crate::capabilities::CapabilityManager;
|
||||
use crate::events::EventBus;
|
||||
use crate::config::KernelConfig;
|
||||
use zclaw_memory::MemoryStore;
|
||||
use zclaw_runtime::{AgentLoop, LlmDriver, ToolRegistry};
|
||||
use zclaw_runtime::{AgentLoop, LlmDriver, ToolRegistry, tool::SkillExecutor};
|
||||
use zclaw_skills::SkillRegistry;
|
||||
use zclaw_hands::{HandRegistry, HandContext, HandResult, hands::{BrowserHand, SlideshowHand, SpeechHand, QuizHand, WhiteboardHand, ResearcherHand, CollectorHand, ClipHand, TwitterHand}};
|
||||
|
||||
/// Skill executor implementation for Kernel
|
||||
pub struct KernelSkillExecutor {
|
||||
skills: Arc<SkillRegistry>,
|
||||
}
|
||||
|
||||
impl KernelSkillExecutor {
|
||||
pub fn new(skills: Arc<SkillRegistry>) -> Self {
|
||||
Self { skills }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SkillExecutor for KernelSkillExecutor {
|
||||
async fn execute_skill(
|
||||
&self,
|
||||
skill_id: &str,
|
||||
agent_id: &str,
|
||||
session_id: &str,
|
||||
input: Value,
|
||||
) -> Result<Value> {
|
||||
let context = zclaw_skills::SkillContext {
|
||||
agent_id: agent_id.to_string(),
|
||||
session_id: session_id.to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let result = self.skills.execute(&zclaw_types::SkillId::new(skill_id), &context, input).await?;
|
||||
Ok(result.output)
|
||||
}
|
||||
}
|
||||
|
||||
/// The ZCLAW Kernel
|
||||
pub struct Kernel {
|
||||
@@ -19,6 +53,9 @@ pub struct Kernel {
|
||||
events: EventBus,
|
||||
memory: Arc<MemoryStore>,
|
||||
driver: Arc<dyn LlmDriver>,
|
||||
skills: Arc<SkillRegistry>,
|
||||
skill_executor: Arc<KernelSkillExecutor>,
|
||||
hands: Arc<HandRegistry>,
|
||||
}
|
||||
|
||||
impl Kernel {
|
||||
@@ -35,6 +72,31 @@ impl Kernel {
|
||||
let capabilities = CapabilityManager::new();
|
||||
let events = EventBus::new();
|
||||
|
||||
// Initialize skill registry
|
||||
let skills = Arc::new(SkillRegistry::new());
|
||||
|
||||
// Scan skills directory if configured
|
||||
if let Some(ref skills_dir) = config.skills_dir {
|
||||
if skills_dir.exists() {
|
||||
skills.add_skill_dir(skills_dir.clone()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize hand registry with built-in hands
|
||||
let hands = Arc::new(HandRegistry::new());
|
||||
hands.register(Arc::new(BrowserHand::new())).await;
|
||||
hands.register(Arc::new(SlideshowHand::new())).await;
|
||||
hands.register(Arc::new(SpeechHand::new())).await;
|
||||
hands.register(Arc::new(QuizHand::new())).await;
|
||||
hands.register(Arc::new(WhiteboardHand::new())).await;
|
||||
hands.register(Arc::new(ResearcherHand::new())).await;
|
||||
hands.register(Arc::new(CollectorHand::new())).await;
|
||||
hands.register(Arc::new(ClipHand::new())).await;
|
||||
hands.register(Arc::new(TwitterHand::new())).await;
|
||||
|
||||
// Create skill executor
|
||||
let skill_executor = Arc::new(KernelSkillExecutor::new(skills.clone()));
|
||||
|
||||
// Restore persisted agents
|
||||
let persisted = memory.list_agents().await?;
|
||||
for agent in persisted {
|
||||
@@ -48,6 +110,9 @@ impl Kernel {
|
||||
events,
|
||||
memory,
|
||||
driver,
|
||||
skills,
|
||||
skill_executor,
|
||||
hands,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -128,6 +193,7 @@ impl Kernel {
|
||||
self.memory.clone(),
|
||||
)
|
||||
.with_model(&model)
|
||||
.with_skill_executor(self.skill_executor.clone())
|
||||
.with_max_tokens(agent_config.max_tokens.unwrap_or_else(|| self.config.max_tokens()))
|
||||
.with_temperature(agent_config.temperature.unwrap_or_else(|| self.config.temperature()));
|
||||
|
||||
@@ -173,6 +239,7 @@ impl Kernel {
|
||||
self.memory.clone(),
|
||||
)
|
||||
.with_model(&model)
|
||||
.with_skill_executor(self.skill_executor.clone())
|
||||
.with_max_tokens(agent_config.max_tokens.unwrap_or_else(|| self.config.max_tokens()))
|
||||
.with_temperature(agent_config.temperature.unwrap_or_else(|| self.config.temperature()));
|
||||
|
||||
@@ -202,6 +269,57 @@ impl Kernel {
|
||||
pub fn config(&self) -> &KernelConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Get the skills registry
|
||||
pub fn skills(&self) -> &Arc<SkillRegistry> {
|
||||
&self.skills
|
||||
}
|
||||
|
||||
/// List all discovered skills
|
||||
pub async fn list_skills(&self) -> Vec<zclaw_skills::SkillManifest> {
|
||||
self.skills.list().await
|
||||
}
|
||||
|
||||
/// Refresh skills from a directory
|
||||
pub async fn refresh_skills(&self, dir: Option<std::path::PathBuf>) -> Result<()> {
|
||||
if let Some(path) = dir {
|
||||
self.skills.add_skill_dir(path).await?;
|
||||
} else if let Some(ref skills_dir) = self.config.skills_dir {
|
||||
self.skills.add_skill_dir(skills_dir.clone()).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute a skill with the given ID and input
|
||||
pub async fn execute_skill(
|
||||
&self,
|
||||
id: &str,
|
||||
context: zclaw_skills::SkillContext,
|
||||
input: serde_json::Value,
|
||||
) -> Result<zclaw_skills::SkillResult> {
|
||||
self.skills.execute(&zclaw_types::SkillId::new(id), &context, input).await
|
||||
}
|
||||
|
||||
/// Get the hands registry
|
||||
pub fn hands(&self) -> &Arc<HandRegistry> {
|
||||
&self.hands
|
||||
}
|
||||
|
||||
/// List all registered hands
|
||||
pub async fn list_hands(&self) -> Vec<zclaw_hands::HandConfig> {
|
||||
self.hands.list().await
|
||||
}
|
||||
|
||||
/// Execute a hand with the given input
|
||||
pub async fn execute_hand(
|
||||
&self,
|
||||
hand_id: &str,
|
||||
input: serde_json::Value,
|
||||
) -> Result<HandResult> {
|
||||
// Use default context (agent_id will be generated)
|
||||
let context = HandContext::default();
|
||||
self.hands.execute(hand_id, &context, input).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Response from sending a message
|
||||
|
||||
Reference in New Issue
Block a user