feat: 新增技能编排引擎和工作流构建器组件
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
refactor: 统一Hands系统常量到单个源文件 refactor: 更新Hands中文名称和描述 fix: 修复技能市场在连接状态变化时重新加载 fix: 修复身份变更提案的错误处理逻辑 docs: 更新多个功能文档的验证状态和实现位置 docs: 更新Hands系统文档 test: 添加测试文件验证工作区路径
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
//! Capability manager
|
||||
|
||||
use dashmap::DashMap;
|
||||
use zclaw_types::{AgentId, Capability, CapabilitySet, Result, ZclawError};
|
||||
use zclaw_types::{AgentId, Capability, CapabilitySet, Result};
|
||||
|
||||
/// Manages capabilities for all agents
|
||||
pub struct CapabilityManager {
|
||||
@@ -53,7 +53,7 @@ impl CapabilityManager {
|
||||
}
|
||||
|
||||
/// Validate capabilities don't exceed parent's
|
||||
pub fn validate(&self, capabilities: &[Capability]) -> Result<()> {
|
||||
pub fn validate(&self, _capabilities: &[Capability]) -> Result<()> {
|
||||
// TODO: Implement capability validation
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -157,11 +157,98 @@ impl Default for KernelConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Default skills directory (./skills relative to cwd)
|
||||
/// Default skills directory
|
||||
///
|
||||
/// Discovery order:
|
||||
/// 1. ZCLAW_SKILLS_DIR environment variable (if set)
|
||||
/// 2. Compile-time known workspace path (CARGO_WORKSPACE_DIR or relative from manifest dir)
|
||||
/// 3. Current working directory/skills (for development)
|
||||
/// 4. Executable directory and multiple levels up (for packaged apps)
|
||||
fn default_skills_dir() -> Option<std::path::PathBuf> {
|
||||
std::env::current_dir()
|
||||
// 1. Check environment variable override
|
||||
if let Ok(dir) = std::env::var("ZCLAW_SKILLS_DIR") {
|
||||
let path = std::path::PathBuf::from(&dir);
|
||||
eprintln!("[default_skills_dir] ZCLAW_SKILLS_DIR env: {} (exists: {})", path.display(), path.exists());
|
||||
if path.exists() {
|
||||
return Some(path);
|
||||
}
|
||||
// Even if it doesn't exist, respect the env var
|
||||
return Some(path);
|
||||
}
|
||||
|
||||
// 2. Try compile-time known paths (works for cargo build/test)
|
||||
// CARGO_MANIFEST_DIR is the crate directory (crates/zclaw-kernel)
|
||||
// We need to go up to find the workspace root
|
||||
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
eprintln!("[default_skills_dir] CARGO_MANIFEST_DIR: {}", manifest_dir.display());
|
||||
|
||||
// Go up from crates/zclaw-kernel to workspace root
|
||||
if let Some(workspace_root) = manifest_dir.parent().and_then(|p| p.parent()) {
|
||||
let workspace_skills = workspace_root.join("skills");
|
||||
eprintln!("[default_skills_dir] Workspace skills: {} (exists: {})", workspace_skills.display(), workspace_skills.exists());
|
||||
if workspace_skills.exists() {
|
||||
return Some(workspace_skills);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Try current working directory first (for development)
|
||||
if let Ok(cwd) = std::env::current_dir() {
|
||||
let cwd_skills = cwd.join("skills");
|
||||
eprintln!("[default_skills_dir] Checking cwd: {} (exists: {})", cwd_skills.display(), cwd_skills.exists());
|
||||
if cwd_skills.exists() {
|
||||
return Some(cwd_skills);
|
||||
}
|
||||
|
||||
// Also try going up from cwd (might be in desktop/src-tauri)
|
||||
let mut current = cwd.as_path();
|
||||
for i in 0..6 {
|
||||
if let Some(parent) = current.parent() {
|
||||
let parent_skills = parent.join("skills");
|
||||
eprintln!("[default_skills_dir] CWD Level {}: {} (exists: {})", i, parent_skills.display(), parent_skills.exists());
|
||||
if parent_skills.exists() {
|
||||
return Some(parent_skills);
|
||||
}
|
||||
current = parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Try executable's directory and multiple levels up
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
eprintln!("[default_skills_dir] Current exe: {}", exe.display());
|
||||
if let Some(exe_dir) = exe.parent().map(|p| p.to_path_buf()) {
|
||||
// Same directory as exe
|
||||
let exe_skills = exe_dir.join("skills");
|
||||
eprintln!("[default_skills_dir] Checking exe dir: {} (exists: {})", exe_skills.display(), exe_skills.exists());
|
||||
if exe_skills.exists() {
|
||||
return Some(exe_skills);
|
||||
}
|
||||
|
||||
// Go up multiple levels to handle Tauri dev builds
|
||||
let mut current = exe_dir.as_path();
|
||||
for i in 0..6 {
|
||||
if let Some(parent) = current.parent() {
|
||||
let parent_skills = parent.join("skills");
|
||||
eprintln!("[default_skills_dir] EXE Level {}: {} (exists: {})", i, parent_skills.display(), parent_skills.exists());
|
||||
if parent_skills.exists() {
|
||||
return Some(parent_skills);
|
||||
}
|
||||
current = parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Fallback to current working directory/skills (may not exist)
|
||||
let fallback = std::env::current_dir()
|
||||
.ok()
|
||||
.map(|cwd| cwd.join("skills"))
|
||||
.map(|cwd| cwd.join("skills"));
|
||||
eprintln!("[default_skills_dir] Fallback to: {:?}", fallback);
|
||||
fallback
|
||||
}
|
||||
|
||||
impl KernelConfig {
|
||||
@@ -334,7 +421,7 @@ impl KernelConfig {
|
||||
Self {
|
||||
database_url: default_database_url(),
|
||||
llm,
|
||||
skills_dir: None,
|
||||
skills_dir: default_skills_dir(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
use crate::generation::{Classroom, GeneratedScene, SceneContent, SceneType, SceneAction};
|
||||
use super::{ExportOptions, ExportResult, Exporter, sanitize_filename};
|
||||
use zclaw_types::Result;
|
||||
use zclaw_types::ZclawError;
|
||||
|
||||
/// HTML exporter
|
||||
pub struct HtmlExporter {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
//! without external dependencies. For more advanced features, consider using
|
||||
//! a dedicated library like `pptx-rs` or `office` crate.
|
||||
|
||||
use crate::generation::{Classroom, GeneratedScene, SceneContent, SceneType, SceneAction};
|
||||
use crate::generation::{Classroom, GeneratedScene, SceneContent, SceneAction};
|
||||
use super::{ExportOptions, ExportResult, Exporter, sanitize_filename};
|
||||
use zclaw_types::{Result, ZclawError};
|
||||
use std::collections::HashMap;
|
||||
@@ -211,7 +211,7 @@ impl PptxExporter {
|
||||
|
||||
/// Generate title slide XML
|
||||
fn generate_title_slide(&self, classroom: &Classroom) -> String {
|
||||
let objectives = classroom.objectives.iter()
|
||||
let _objectives = classroom.objectives.iter()
|
||||
.map(|o| format!("- {}", o))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
@@ -9,9 +9,8 @@ use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
use futures::future::join_all;
|
||||
use zclaw_types::{AgentId, Result, ZclawError};
|
||||
use zclaw_types::Result;
|
||||
use zclaw_runtime::{LlmDriver, CompletionRequest, CompletionResponse, ContentBlock};
|
||||
use zclaw_hands::{WhiteboardAction, SpeechAction, QuizAction};
|
||||
|
||||
/// Generation stage
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@@ -132,38 +132,103 @@ impl Kernel {
|
||||
.map(|p| p.clone())
|
||||
.unwrap_or_else(|| "You are a helpful AI assistant.".to_string());
|
||||
|
||||
// Inject skill information
|
||||
// Inject skill information with categories
|
||||
if !skills.is_empty() {
|
||||
prompt.push_str("\n\n## Available Skills\n\n");
|
||||
prompt.push_str("You have access to the following skills that can help with specific tasks. ");
|
||||
prompt.push_str("Use the `execute_skill` tool with the skill_id to invoke them:\n\n");
|
||||
prompt.push_str("You have access to specialized skills. Analyze user intent and autonomously call `execute_skill` with the appropriate skill_id.\n\n");
|
||||
|
||||
for skill in skills {
|
||||
prompt.push_str(&format!(
|
||||
"- **{}**: {}",
|
||||
skill.id.as_str(),
|
||||
skill.description
|
||||
));
|
||||
// Group skills by category based on their ID patterns
|
||||
let categories = self.categorize_skills(&skills);
|
||||
|
||||
// Add trigger words if available
|
||||
if !skill.triggers.is_empty() {
|
||||
for (category, category_skills) in categories {
|
||||
prompt.push_str(&format!("### {}\n", category));
|
||||
for skill in category_skills {
|
||||
prompt.push_str(&format!(
|
||||
" (Triggers: {})",
|
||||
skill.triggers.join(", ")
|
||||
"- **{}**: {}",
|
||||
skill.id.as_str(),
|
||||
skill.description
|
||||
));
|
||||
prompt.push('\n');
|
||||
}
|
||||
prompt.push('\n');
|
||||
}
|
||||
|
||||
prompt.push_str("\n### When to use skills:\n");
|
||||
prompt.push_str("- When the user's request matches a skill's trigger words\n");
|
||||
prompt.push_str("- When you need specialized expertise for a task\n");
|
||||
prompt.push_str("- When the task would benefit from a structured workflow\n");
|
||||
prompt.push_str("### When to use skills:\n");
|
||||
prompt.push_str("- **IMPORTANT**: You should autonomously decide when to use skills based on your understanding of the user's intent.\n");
|
||||
prompt.push_str("- Do not wait for explicit skill names - recognize the need and act.\n");
|
||||
prompt.push_str("- Match user's request to the most appropriate skill's domain.\n");
|
||||
prompt.push_str("- If multiple skills could apply, choose the most specialized one.\n\n");
|
||||
prompt.push_str("### Example:\n");
|
||||
prompt.push_str("User: \"分析腾讯财报\" → Intent: Financial analysis → Call: execute_skill(\"finance-tracker\", {...})\n");
|
||||
}
|
||||
|
||||
prompt
|
||||
}
|
||||
|
||||
/// Categorize skills into logical groups
|
||||
///
|
||||
/// Priority:
|
||||
/// 1. Use skill's `category` field if defined in SKILL.md
|
||||
/// 2. Fall back to pattern matching for backward compatibility
|
||||
fn categorize_skills<'a>(&self, skills: &'a [zclaw_skills::SkillManifest]) -> Vec<(String, Vec<&'a zclaw_skills::SkillManifest>)> {
|
||||
let mut categories: std::collections::HashMap<String, Vec<&zclaw_skills::SkillManifest>> = std::collections::HashMap::new();
|
||||
|
||||
// Fallback category patterns for skills without explicit category
|
||||
let fallback_patterns = [
|
||||
("开发工程", vec!["senior-developer", "frontend-developer", "backend-architect", "ai-engineer", "devops-automator", "rapid-prototyper", "lsp-index-engineer"]),
|
||||
("测试质量", vec!["api-tester", "evidence-collector", "reality-checker", "performance-benchmarker", "test-results-analyzer", "accessibility-auditor", "code-review"]),
|
||||
("安全合规", vec!["security-engineer", "legal-compliance-checker", "agentic-identity-trust"]),
|
||||
("数据分析", vec!["analytics-reporter", "finance-tracker", "data-analysis", "sales-data-extraction-agent", "data-consolidation-agent", "report-distribution-agent"]),
|
||||
("项目管理", vec!["senior-pm", "project-shepherd", "sprint-prioritizer", "experiment-tracker", "feedback-synthesizer", "trend-researcher", "agents-orchestrator"]),
|
||||
("设计UX", vec!["ui-designer", "ux-architect", "ux-researcher", "visual-storyteller", "image-prompt-engineer", "whimsy-injector", "brand-guardian"]),
|
||||
("内容营销", vec!["content-creator", "chinese-writing", "executive-summary-generator", "social-media-strategist"]),
|
||||
("社交平台", vec!["twitter-engager", "instagram-curator", "tiktok-strategist", "reddit-community-builder", "zhihu-strategist", "xiaohongshu-specialist", "wechat-official-account", "growth-hacker", "app-store-optimizer"]),
|
||||
("运营支持", vec!["studio-operations", "studio-producer", "support-responder", "workflow-optimizer", "infrastructure-maintainer", "tool-evaluator"]),
|
||||
("XR/空间计算", vec!["visionos-spatial-engineer", "macos-spatial-metal-engineer", "xr-immersive-developer", "xr-interface-architect", "xr-cockpit-interaction-specialist", "terminal-integration-specialist"]),
|
||||
("基础工具", vec!["web-search", "file-operations", "shell-command", "git", "translation", "feishu-docs"]),
|
||||
];
|
||||
|
||||
// Categorize each skill
|
||||
for skill in skills {
|
||||
// Priority 1: Use skill's explicit category
|
||||
if let Some(ref category) = skill.category {
|
||||
if !category.is_empty() {
|
||||
categories.entry(category.clone()).or_default().push(skill);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: Fallback to pattern matching
|
||||
let skill_id = skill.id.as_str();
|
||||
let mut categorized = false;
|
||||
|
||||
for (category, patterns) in &fallback_patterns {
|
||||
if patterns.iter().any(|p| skill_id.contains(p) || *p == skill_id) {
|
||||
categories.entry(category.to_string()).or_default().push(skill);
|
||||
categorized = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Put uncategorized skills in "其他"
|
||||
if !categorized {
|
||||
categories.entry("其他".to_string()).or_default().push(skill);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to ordered vector
|
||||
let mut result: Vec<(String, Vec<_>)> = categories.into_iter().collect();
|
||||
result.sort_by(|a, b| {
|
||||
// Sort by predefined order
|
||||
let order = ["开发工程", "测试质量", "安全合规", "数据分析", "项目管理", "设计UX", "内容营销", "社交平台", "运营支持", "XR/空间计算", "基础工具", "其他"];
|
||||
let a_idx = order.iter().position(|&x| x == a.0).unwrap_or(99);
|
||||
let b_idx = order.iter().position(|&x| x == b.0).unwrap_or(99);
|
||||
a_idx.cmp(&b_idx)
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Spawn a new agent
|
||||
pub async fn spawn_agent(&self, config: AgentConfig) -> Result<AgentId> {
|
||||
let id = config.id;
|
||||
|
||||
@@ -19,3 +19,6 @@ pub use config::*;
|
||||
pub use director::*;
|
||||
pub use generation::*;
|
||||
pub use export::{ExportFormat, ExportOptions, ExportResult, Exporter, export_classroom};
|
||||
|
||||
// Re-export hands types for convenience
|
||||
pub use zclaw_hands::{HandRegistry, HandContext, HandResult, HandConfig, Hand, HandStatus};
|
||||
|
||||
Reference in New Issue
Block a user