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

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:
iven
2026-03-30 10:55:08 +08:00
parent d345e60a6a
commit 813b49a986
19 changed files with 951 additions and 102 deletions

View File

@@ -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 {