工具系统重构与定时任务管理

1. 工具抽象体系
   - 新增 Tool trait + ToolRegistry 注册表
   - 三种工具类型:ApiTool / StdioTool / ShellTool
   - 全局 REGISTRY (LazyLock) 作为主入口
   - BuiltinRegistry 简化为兼容层

2. 定时任务管理工具
   - 新增 manage_scheduled_tasks 工具(ApiTool)
   - 支持 list/add/update/delete/toggle 五种操作
   - 通过 LazyLock 延迟初始化数据库连接池

3. 定时任务分流
   - 新增 task_type 字段(model / system)
   - 模型可自由管理 model 任务
   - system 任务只读,修改操作被拒绝

4. CLI 支持
   - 新增 ias task 子命令(list/add/update/delete/toggle)

5. 清理
   - 删除废弃的 ias-auth 独立二进制
   - SkillResult 增加 Deserialize derive
   - 所有调用点更新为 crate::tools::xxx 新入口
This commit is contained in:
2026-06-10 16:00:47 +08:00
parent f7d0781090
commit 5a54368437
17 changed files with 1271 additions and 257 deletions
+160
View File
@@ -243,3 +243,163 @@ pub async fn load_summaries(pool: &PgPool, user_id: &str, limit: i64) -> Result<
.map_err(|e| format!("查询摘要失败: {}", e))?;
Ok(rows.into_iter().map(|(t,)| t).collect())
}
// ─── 定时任务 ───
#[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)]
pub struct ScheduledTask {
pub id: uuid::Uuid,
pub name: String,
pub description: String,
pub user_id: Option<String>,
pub command: String,
pub shell: String,
pub cwd: String,
pub interval_seconds: i32,
pub enabled: bool,
pub next_run_at: DateTime<Utc>,
pub last_run_at: Option<DateTime<Utc>>,
pub last_status: Option<String>,
pub task_type: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
/// 列出所有定时任务
pub async fn list_scheduled_tasks(pool: &PgPool) -> Result<Vec<ScheduledTask>, String> {
let tasks = sqlx::query_as::<_, ScheduledTask>(
r#"
SELECT id, name, description, user_id, command, shell, cwd,
interval_seconds, enabled, next_run_at, last_run_at,
last_status, task_type, created_at, updated_at
FROM scheduled_tasks
ORDER BY next_run_at ASC
"#,
)
.fetch_all(pool)
.await
.map_err(|e| format!("查询定时任务失败: {}", e))?;
Ok(tasks)
}
/// 添加定时任务
pub async fn add_scheduled_task(
pool: &PgPool,
name: &str,
user_id: &str,
command: &str,
interval_seconds: i32,
description: &str,
shell: &str,
cwd: &str,
task_type: &str,
) -> Result<uuid::Uuid, String> {
let id = uuid::Uuid::new_v4();
sqlx::query(
r#"
INSERT INTO scheduled_tasks (id, name, description, user_id, command, shell, cwd, interval_seconds, task_type)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
"#,
)
.bind(id)
.bind(name)
.bind(description)
.bind(user_id)
.bind(command)
.bind(shell)
.bind(cwd)
.bind(interval_seconds)
.bind(task_type)
.execute(pool)
.await
.map_err(|e| format!("添加定时任务失败: {}", e))?;
Ok(id)
}
/// 更新定时任务(只更新非 None 的字段)
pub async fn update_scheduled_task(
pool: &PgPool,
id: &uuid::Uuid,
name: Option<&str>,
command: Option<&str>,
interval_seconds: Option<i32>,
description: Option<&str>,
shell: Option<&str>,
cwd: Option<&str>,
) -> Result<bool, String> {
let mut sets = Vec::new();
let mut idx = 1u32;
if name.is_some() {
sets.push(format!("name = ${}", idx)); idx += 1;
}
if command.is_some() {
sets.push(format!("command = ${}", idx)); idx += 1;
}
if interval_seconds.is_some() {
sets.push(format!("interval_seconds = ${}", idx)); idx += 1;
}
if description.is_some() {
sets.push(format!("description = ${}", idx)); idx += 1;
}
if shell.is_some() {
sets.push(format!("shell = ${}", idx)); idx += 1;
}
if cwd.is_some() {
sets.push(format!("cwd = ${}", idx)); idx += 1;
}
if sets.is_empty() {
return Ok(false);
}
sets.push("updated_at = NOW()".to_string());
let sql = format!(
"UPDATE scheduled_tasks SET {} WHERE id = ${}",
sets.join(", "),
idx
);
let mut query = sqlx::query(&sql).bind(id);
if let Some(v) = name { query = query.bind(v); }
if let Some(v) = command { query = query.bind(v); }
if let Some(v) = interval_seconds { query = query.bind(v); }
if let Some(v) = description { query = query.bind(v); }
if let Some(v) = shell { query = query.bind(v); }
if let Some(v) = cwd { query = query.bind(v); }
let rows = query
.execute(pool)
.await
.map_err(|e| format!("更新定时任务失败: {}", e))?;
Ok(rows.rows_affected() > 0)
}
/// 删除定时任务
pub async fn delete_scheduled_task(pool: &PgPool, id: &uuid::Uuid) -> Result<bool, String> {
let rows = sqlx::query("DELETE FROM scheduled_tasks WHERE id = $1")
.bind(id)
.execute(pool)
.await
.map_err(|e| format!("删除定时任务失败: {}", e))?;
Ok(rows.rows_affected() > 0)
}
/// 切换定时任务启用状态
pub async fn toggle_scheduled_task(pool: &PgPool, id: &uuid::Uuid) -> Result<(bool, bool), String> {
let row = sqlx::query_as::<_, (bool,)>(
"UPDATE scheduled_tasks SET enabled = NOT enabled, updated_at = NOW() WHERE id = $1 RETURNING enabled",
)
.bind(id)
.fetch_optional(pool)
.await
.map_err(|e| format!("切换定时任务状态失败: {}", e))?;
match row {
Some((enabled,)) => Ok((true, enabled)),
None => Ok((false, false)),
}
}