feat: P0 KernelClient功能修复 + P1/P2/P3质量改进
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
P0 KernelClient 功能断裂修复: - Skill CUD: registry.rs create/update/delete + serialize_skill_md + kernel proxy - Workflow CUD: pipeline_commands.rs create/update/delete + serde_yaml依赖 - Agent更新: registry update方法 + AgentConfigUpdated事件 + agent_update命令 - Hand流式事件: HandStart/HandEnd变体替换ToolStart/ToolEnd - 后端验证: hand_get/hand_run_status/hand_run_list确认实现完整 - Approval闭环: respond_to_approval后台spawn+5分钟超时轮询 P2/P3 质量改进: - Browser WebDriver: TCP探测ChromeDriver/GeckoDriver/Edge端口替换硬编码true - api-fallbacks: 移除假技能和16个捏造安全层,替换为真实能力映射 - dead_code清理: 移除5个模块级#![allow(dead_code)],删除3个真正死方法, 删除未注册的compactor_compact_llm命令,warnings从8降到3 - 所有变更通过cargo check + tsc --noEmit验证
This commit is contained in:
@@ -171,6 +171,150 @@ impl SkillRegistry {
|
||||
skills.insert(manifest.id.clone(), skill);
|
||||
manifests.insert(manifest.id.clone(), manifest);
|
||||
}
|
||||
|
||||
/// Create a skill from manifest, writing SKILL.md to disk
|
||||
pub async fn create_skill(
|
||||
&self,
|
||||
skills_dir: &std::path::Path,
|
||||
manifest: SkillManifest,
|
||||
) -> Result<()> {
|
||||
let skill_dir = skills_dir.join(manifest.id.as_str());
|
||||
if skill_dir.exists() {
|
||||
return Err(zclaw_types::ZclawError::InvalidInput(
|
||||
format!("Skill directory already exists: {}", skill_dir.display())
|
||||
));
|
||||
}
|
||||
|
||||
// Create directory
|
||||
std::fs::create_dir_all(&skill_dir)
|
||||
.map_err(|e| zclaw_types::ZclawError::StorageError(
|
||||
format!("Failed to create skill directory: {}", e)
|
||||
))?;
|
||||
|
||||
// Write SKILL.md
|
||||
let content = serialize_skill_md(&manifest);
|
||||
std::fs::write(skill_dir.join("SKILL.md"), &content)
|
||||
.map_err(|e| zclaw_types::ZclawError::StorageError(
|
||||
format!("Failed to write SKILL.md: {}", e)
|
||||
))?;
|
||||
|
||||
// Load into registry
|
||||
self.load_skill_from_dir(&skill_dir).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update a skill manifest, rewriting SKILL.md on disk
|
||||
pub async fn update_skill(
|
||||
&self,
|
||||
skills_dir: &std::path::Path,
|
||||
id: &SkillId,
|
||||
updates: SkillManifest,
|
||||
) -> Result<SkillManifest> {
|
||||
// Find existing skill directory
|
||||
let skill_dir = skills_dir.join(id.as_str());
|
||||
if !skill_dir.exists() {
|
||||
return Err(zclaw_types::ZclawError::NotFound(
|
||||
format!("Skill directory not found: {}", skill_dir.display())
|
||||
));
|
||||
}
|
||||
|
||||
// Merge: start from existing manifest, apply updates
|
||||
let existing = self.get_manifest(id).await
|
||||
.ok_or_else(|| zclaw_types::ZclawError::NotFound(
|
||||
format!("Skill not found in registry: {}", id)
|
||||
))?;
|
||||
|
||||
let updated = SkillManifest {
|
||||
id: existing.id.clone(),
|
||||
name: if updates.name.is_empty() { existing.name } else { updates.name },
|
||||
description: if updates.description.is_empty() { existing.description } else { updates.description },
|
||||
version: if updates.version.is_empty() { existing.version } else { updates.version },
|
||||
author: updates.author.or(existing.author),
|
||||
mode: existing.mode,
|
||||
capabilities: if updates.capabilities.is_empty() { existing.capabilities } else { updates.capabilities },
|
||||
input_schema: updates.input_schema.or(existing.input_schema),
|
||||
output_schema: updates.output_schema.or(existing.output_schema),
|
||||
tags: if updates.tags.is_empty() { existing.tags } else { updates.tags },
|
||||
category: updates.category.or(existing.category),
|
||||
triggers: if updates.triggers.is_empty() { existing.triggers } else { updates.triggers },
|
||||
enabled: updates.enabled,
|
||||
};
|
||||
|
||||
// Rewrite SKILL.md
|
||||
let content = serialize_skill_md(&updated);
|
||||
std::fs::write(skill_dir.join("SKILL.md"), &content)
|
||||
.map_err(|e| zclaw_types::ZclawError::StorageError(
|
||||
format!("Failed to write SKILL.md: {}", e)
|
||||
))?;
|
||||
|
||||
// Reload into registry
|
||||
self.remove(id).await;
|
||||
self.load_skill_from_dir(&skill_dir).await?;
|
||||
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
/// Delete a skill: remove directory from disk and unregister
|
||||
pub async fn delete_skill(
|
||||
&self,
|
||||
skills_dir: &std::path::Path,
|
||||
id: &SkillId,
|
||||
) -> Result<()> {
|
||||
let skill_dir = skills_dir.join(id.as_str());
|
||||
if skill_dir.exists() {
|
||||
std::fs::remove_dir_all(&skill_dir)
|
||||
.map_err(|e| zclaw_types::ZclawError::StorageError(
|
||||
format!("Failed to remove skill directory: {}", e)
|
||||
))?;
|
||||
}
|
||||
|
||||
self.remove(id).await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize a SkillManifest into SKILL.md frontmatter format
|
||||
fn serialize_skill_md(manifest: &SkillManifest) -> String {
|
||||
let mut parts = Vec::new();
|
||||
|
||||
// Frontmatter
|
||||
parts.push("---".to_string());
|
||||
parts.push(format!("name: \"{}\"", manifest.name));
|
||||
parts.push(format!("description: \"{}\"", manifest.description));
|
||||
parts.push(format!("version: \"{}\"", manifest.version));
|
||||
parts.push(format!("mode: {}", match manifest.mode {
|
||||
SkillMode::PromptOnly => "prompt-only",
|
||||
SkillMode::Python => "python",
|
||||
SkillMode::Shell => "shell",
|
||||
SkillMode::Wasm => "wasm",
|
||||
SkillMode::Native => "native",
|
||||
}));
|
||||
if !manifest.capabilities.is_empty() {
|
||||
parts.push(format!("capabilities: {}", manifest.capabilities.join(", ")));
|
||||
}
|
||||
if !manifest.tags.is_empty() {
|
||||
parts.push(format!("tags: {}", manifest.tags.join(", ")));
|
||||
}
|
||||
if !manifest.triggers.is_empty() {
|
||||
parts.push("triggers:".to_string());
|
||||
for trigger in &manifest.triggers {
|
||||
parts.push(format!(" - \"{}\"", trigger));
|
||||
}
|
||||
}
|
||||
if let Some(ref cat) = manifest.category {
|
||||
parts.push(format!("category: \"{}\"", cat));
|
||||
}
|
||||
parts.push(format!("enabled: {}", manifest.enabled));
|
||||
parts.push("---".to_string());
|
||||
parts.push(String::new());
|
||||
|
||||
// Body: use description as the skill content
|
||||
parts.push(format!("# {}", manifest.name));
|
||||
parts.push(String::new());
|
||||
parts.push(manifest.description.clone());
|
||||
|
||||
parts.join("\n")
|
||||
}
|
||||
|
||||
impl Default for SkillRegistry {
|
||||
|
||||
Reference in New Issue
Block a user