From 1497b1bba031b61082ed05dd7e9805dc5e8ed13e Mon Sep 17 00:00:00 2001 From: wunianxiao Date: Tue, 2 Jun 2026 09:47:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20YAML=20frontmatter=20=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=20+=20call=5Fcapability=20iPet=20=E5=85=BC=E5=AE=B9=E6=A1=A5?= =?UTF-8?q?=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SKILL.md 解析器支持跳过 YAML frontmatter (---...---) 修复导入的 Claude Code / Agent Skills 的元数据头导致 description 混入元数据的问题 - call_capability 桥接工具:兼容 iPet 遗留的 {name, prompt} 调用格式 按 name 查找 SkillRegistry,委托执行 - 清理 parser 中未使用的 in_desc 变量 --- src/main.rs | 32 ++++++++++++++++++++++++++++++++ src/tools/parser.rs | 17 +++++++++++------ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2eef8bd..50e443c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -275,6 +275,23 @@ async fn cmd_listen( } })); + // call_capability 桥接:iPet 兼容,按 skill 名调用 + tools_list.push(serde_json::json!({ + "type": "function", + "function": { + "name": "call_capability", + "description": "按名称调用一个 skill(等同于直接使用该 skill 的工具名)。name 参数为 skill 的名称,prompt 为自然语言需求", + "parameters": { + "type": "object", + "properties": { + "name": { "type": "string", "description": "要调用的 skill 名称" }, + "prompt": { "type": "string", "description": "给该 skill 的自然语言需求" } + }, + "required": ["name"] + } + } + })); + cfg.tools = Some(tools_list); // 创建 Conversation @@ -331,6 +348,7 @@ async fn cmd_listen( let args = args_json.to_string(); Box::pin(async move { + let user_id = current_user.lock().await.clone(); match n.as_str() { "read_memories" => return Ok(memory_store.read().await), "write_memory" => { @@ -343,6 +361,20 @@ async fn cmd_listen( return Ok(memory_store.write(content).await); } "read_summaries" => return Ok(context::tools::read_summaries(&session).await), + "call_capability" => { + let cap_params: serde_json::Value = serde_json::from_str(&args).unwrap_or_default(); + let cap_name = cap_params.get("name").and_then(|v| v.as_str()).map(|s| s.to_string()); + if let Some(name) = cap_name { + if let Some(ref reg) = reg { + let mut ctx = ExecutionContext::new(&user_id); + if let Some(a) = approval { ctx = ctx.with_approval(a); } + ctx = ctx.with_wechat(send); + let result = reg.execute(&name, cap_params, &ctx).await; + return Ok(result.output); + } + } + return Ok("请提供 name 参数(要调用的 skill 名称)".to_string()); + } _ => {} } let user_id = current_user.lock().await.clone(); diff --git a/src/tools/parser.rs b/src/tools/parser.rs index 4d83f9d..c10616d 100644 --- a/src/tools/parser.rs +++ b/src/tools/parser.rs @@ -38,11 +38,20 @@ pub fn parse_skill(skill_dir: &Path) -> Result { let mut params_fence = 0; let mut execute_fence = 0; let mut desc_lines: Vec<&str> = Vec::new(); - let mut in_desc = false; + let mut frontmatter_fences = 0; - for (i, line) in lines.iter().enumerate() { + for (_i, line) in lines.iter().enumerate() { let trimmed = line.trim(); + // 跳过 YAML frontmatter (---...---) + if trimmed == "---" && frontmatter_fences < 2 { + frontmatter_fences += 1; + continue; + } + if frontmatter_fences == 1 { + continue; + } + // 标题行 → 技能名 if trimmed.starts_with("# ") && !trimmed.starts_with("## ") && name.is_empty() { name = trimmed.trim_start_matches("# ").trim().to_string(); @@ -54,24 +63,20 @@ pub fn parse_skill(skill_dir: &Path) -> Result { let section = trimmed.trim_start_matches("## ").trim().to_lowercase(); match section.as_str() { "risk level" | "risk_level" | "risk" => { - in_desc = false; in_params = false; in_execute = false; } "parameters" | "param" => { - in_desc = false; in_params = true; in_execute = false; } "execute" | "execution" | "command" | "run" => { - in_desc = false; in_params = false; in_execute = true; params_fence = 0; execute_fence = 0; } _ => { - in_desc = false; in_params = false; in_execute = false; }