refactor: 删除外置 skills 系统,全部工具转为内置

删除:
- resources/skills/ (9个外置 skill, bash/python/node 脚本)
- src/tools/registry.rs (SkillRegistry 加载/执行)
- src/tools/parser.rs (SKILL.md 解析)
- src/skills_importer.rs (skills 导入)
- cli.rs Skills 子命令
- main.rs cmd_skills / global_skills_dir

保留:
- src/tools/builtin.rs (内置工具注册 + 审批流程)
- src/tools/weather.rs (天气查询)
- src/tools/skill.rs (精简为 SkillSpec/RiskLevel/SkillResult/ExecutionContext)
- src/tools/approval.rs (审批管理器)

main.rs 简化: 移除 SkillRegistry 加载, executor 只走 BuiltinRegistry
This commit is contained in:
2026-06-03 10:28:28 +08:00
parent afc43ce692
commit 4d149982c5
29 changed files with 354 additions and 1651 deletions
-322
View File
@@ -1,322 +0,0 @@
use super::approval::{ApprovalDecision, ApprovalManager};
use crate::tools::parser::parse_skill;
use crate::tools::skill::*;
use std::future::Future;
use std::pin::Pin;
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use tokio::process::Command;
/// 技能注册表
pub struct SkillRegistry {
skills: HashMap<String, Arc<Skill>>,
}
impl SkillRegistry {
pub fn new() -> Self {
Self {
skills: HashMap::new(),
}
}
/// 从 system_dir 和 user_dir 加载所有技能
pub async fn load(system_dir: &str, user_dir: &str) -> (Self, Vec<String>) {
let mut registry = Self::new();
let mut errors = Vec::new();
// 加载系统 skills
if Path::new(system_dir).exists() {
registry.load_from_dir(system_dir, SkillSource::System, &mut errors);
}
// 加载全局 skills (~/.ias/skills/)
if let Ok(home) = std::env::var("HOME") {
let global_dir = format!("{}/.ias/skills", home);
if Path::new(&global_dir).exists() {
registry.load_from_dir(&global_dir, SkillSource::User, &mut errors);
}
}
// 加载用户 skills(项目目录)
if Path::new(user_dir).exists() {
registry.load_from_dir(user_dir, SkillSource::User, &mut errors);
}
(registry, errors)
}
fn load_from_dir(&mut self, dir: &str, source: SkillSource, errors: &mut Vec<String>) {
let dir_path = Path::new(dir);
let entries = match std::fs::read_dir(dir_path) {
Ok(e) => e,
Err(e) => {
errors.push(format!("读取目录 {} 失败: {}", dir, e));
return;
}
};
for entry in entries.flatten() {
let path = entry.path();
if !path.is_dir() {
continue;
}
let skill_md = path.join("SKILL.md");
if !skill_md.exists() {
continue;
}
match parse_skill(&path) {
Ok(mut skill) => {
skill.source = source.clone();
let name = skill.spec.name.clone();
// user 同名覆盖 system
if source == SkillSource::User && self.skills.contains_key(&name) {
tracing::info!("用户技能覆盖系统技能: {}", name);
}
self.skills.insert(name, Arc::new(skill));
}
Err(e) => {
errors.push(format!("{}: {}", path.display(), e));
}
}
}
}
/// 列出所有技能 specs(给 LLM function calling
pub fn list_specs(&self) -> Vec<&SkillSpec> {
let mut specs: Vec<&SkillSpec> = self.skills.values().map(|s| &s.spec).collect();
specs.sort_by(|a, b| a.name.cmp(&b.name));
specs
}
/// 按风险等级过滤
pub fn list_specs_by_risk(&self, level: RiskLevel) -> Vec<&SkillSpec> {
self.skills
.values()
.filter(|s| s.spec.risk_level == level)
.map(|s| &s.spec)
.collect()
}
/// 按名称查找技能
pub fn get(&self, name: &str) -> Option<&Skill> {
self.skills.get(name).map(|s| s.as_ref())
}
/// 获取技能数量
pub fn count(&self) -> usize {
self.skills.len()
}
/// 动态注册一个技能(运行时添加,不持久化到文件)
pub fn register_skill(&mut self, spec: SkillSpec, dir: std::path::PathBuf, execute_command: String) {
let skill = Arc::new(Skill::new(spec, SkillSource::User, dir, execute_command));
let name = skill.spec.name.clone();
self.skills.insert(name, skill);
}
// ─── 执行 ───
/// 执行技能(含审批流程)
pub async fn execute(
&self,
name: &str,
params: serde_json::Value,
ctx: &ExecutionContext,
) -> SkillResult {
let skill = match self.skills.get(name) {
Some(s) => s.clone(),
None => {
return SkillResult::error(format!("技能不存在: {}", name));
}
};
// High 风险 → 审批流程(透明模式)
if skill.spec.risk_level.is_high() {
match approval_flow(&skill, ctx).await {
ApprovalResult::Approved => {
// 继续执行
}
ApprovalResult::Rejected => {
return SkillResult::rejected(name);
}
ApprovalResult::Expired => {
return SkillResult::expired(name);
}
ApprovalResult::Error(msg) => {
return SkillResult::error(msg);
}
}
}
// 执行技能
run_skill(&skill, params, ctx).await
}
}
// ─── 审批流程 ───
enum ApprovalResult {
Approved,
Rejected,
Expired,
Error(String),
}
async fn approval_flow(skill: &Skill, ctx: &ExecutionContext) -> ApprovalResult {
let manager = match ctx.approval_manager.as_ref() {
Some(m) => m.clone(),
None => return ApprovalResult::Approved, // 测试模式
};
// 创建审批请求(生成确认码 + 注册通道)
let (code, rx) = match manager.create(&ctx.user_id, &skill.spec.name).await {
Ok(pair) => pair,
Err(e) => return ApprovalResult::Error(e),
};
tracing::debug!("审批确认码已生成: {}...", &code[..2]);
// 发送确认消息给用户
let msg = format!(
"⚠️ 操作确认\n\n技能:{}\n\n确认码:{}\n\n回复确认码以继续\n回复 0 或「取消」以取消\n(5分钟内有效,最多3次尝试)",
skill.spec.name, code
);
if let Some(ref send) = ctx.send_wechat {
if let Err(e) = send(&ctx.user_id, &msg).await {
return ApprovalResult::Error(format!("发送确认消息失败: {}", e));
}
}
// 等待用户确认(通过 oneshot channel,由消息循环驱动)
match tokio::time::timeout(Duration::from_secs(300), rx).await {
Ok(Ok(ApprovalDecision::Approved)) => ApprovalResult::Approved,
Ok(Ok(ApprovalDecision::Rejected)) => ApprovalResult::Rejected,
Ok(Ok(ApprovalDecision::Expired)) => ApprovalResult::Expired,
Ok(Err(_)) => ApprovalResult::Error("审批通道异常".to_string()),
Err(_) => ApprovalResult::Expired,
}
}
// ─── 技能执行器 ───
async fn run_skill(skill: &Skill, params: serde_json::Value, _ctx: &ExecutionContext) -> SkillResult {
let timeout = Duration::from_secs(skill.spec.timeout_secs);
let cmd_str = &skill.execute_command;
let parts: Vec<&str> = cmd_str.split_whitespace().collect();
if parts.is_empty() {
return SkillResult::error("执行命令为空");
}
let mut cmd = Command::new(parts[0]);
for arg in &parts[1..] {
cmd.arg(arg);
}
cmd.current_dir(&skill.dir);
tracing::debug!(
"执行命令: {:?} (cwd={:?})",
&cmd_str, &skill.dir
);
// 通过 stdin 传入参数
let params_json = serde_json::to_string(&params).unwrap_or_default();
cmd.stdin(std::process::Stdio::piped());
cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::piped());
cmd.kill_on_drop(true);
let mut child = match cmd.spawn() {
Ok(c) => c,
Err(e) => return SkillResult::error(format!("启动进程失败: {}", e)),
};
// 写入参数到 stdin
if let Some(mut stdin) = child.stdin.take() {
use tokio::io::AsyncWriteExt;
let _ = stdin.write_all(params_json.as_bytes()).await;
// 关闭 stdin 让进程知道输入结束
drop(stdin);
}
// 等待完成(带超时)
let result = tokio::time::timeout(timeout, child.wait_with_output()).await;
match result {
Ok(Ok(output)) => {
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
let exit_code = output.status.code().unwrap_or(-1);
tracing::debug!(
"🖥️ {} exit={} out={:.200} err={:.100}",
skill.spec.name, exit_code,
if stdout.is_empty() { "(空)" } else { &stdout },
if stderr.is_empty() { "(空)" } else { &stderr }
);
if output.status.success() {
// 尝试解析为 JSON
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&stdout) {
SkillResult::ok_with_data(stdout, json)
} else {
SkillResult::ok(stdout)
}
} else {
let msg = if !stderr.is_empty() {
format!("退出码 {}: {}", output.status.code().unwrap_or(-1), stderr)
} else if !stdout.is_empty() {
format!("退出码 {}: {}", output.status.code().unwrap_or(-1), stdout)
} else {
format!("退出码 {}", output.status.code().unwrap_or(-1))
};
SkillResult::error(msg)
}
}
Ok(Err(e)) => SkillResult::error(format!("进程错误: {}", e)),
Err(_) => SkillResult::error(format!("执行超时 ({}s)", skill.spec.timeout_secs)),
}
}
// ─── 执行上下文 ───
/// 发送微信消息的回调: (user_id, text) -> Result(异步)
pub type WechatSender =
Arc<dyn Fn(&str, &str) -> Pin<Box<dyn Future<Output = Result<(), String>> + Send>> + Send + Sync>;
pub struct ExecutionContext {
pub user_id: String,
pub db: Option<Arc<sqlx::PgPool>>,
/// 审批管理器
pub approval_manager: Option<Arc<ApprovalManager>>,
/// 发送微信消息的回调
pub send_wechat: Option<WechatSender>,
}
impl ExecutionContext {
pub fn new(user_id: impl Into<String>) -> Self {
Self {
user_id: user_id.into(),
db: None,
approval_manager: None,
send_wechat: None,
}
}
pub fn with_db(mut self, db: Arc<sqlx::PgPool>) -> Self {
self.db = Some(db);
self
}
pub fn with_approval(mut self, mgr: Arc<ApprovalManager>) -> Self {
self.approval_manager = Some(mgr);
self
}
pub fn with_wechat(mut self, send: WechatSender) -> Self {
self.send_wechat = Some(send);
self
}
}