From 3f0839ace165148000761d67b9b2e23f756ce313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Sun, 14 Jun 2026 14:14:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20AI=20=E5=88=A0=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=94=B9=E8=BD=AF=E5=88=A0=20+=20=E8=A1=A5=E5=9B=9E?= =?UTF-8?q?=E6=94=B6=E7=AB=99=E4=B8=89=E5=B7=A5=E5=85=B7=20+=20list=5Fproj?= =?UTF-8?q?ects=20=E6=8E=92=E9=99=A4=E5=9B=9E=E6=94=B6=E7=AB=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - delete_project 由 repo.delete 物理删改 soft_delete(与 UI 删一致) - 新增 restore_project / purge_project / list_trash 三工具(复用 ProjectRepo 回收站方法) - list_projects 改 list_active 排除回收站,防 LLM 看到已删项目继续操作 Sprint 20 审查 ①② + Workflow A 验证发现 list_projects 泄漏 --- src-tauri/src/commands/ai/tool_registry.rs | 45 ++++++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src-tauri/src/commands/ai/tool_registry.rs b/src-tauri/src/commands/ai/tool_registry.rs index 8e4939e..ec070f3 100644 --- a/src-tauri/src/commands/ai/tool_registry.rs +++ b/src-tauri/src/commands/ai/tool_registry.rs @@ -85,7 +85,7 @@ pub fn build_ai_tool_registry(db: &Arc) -> AiToolRegistry { let db = db.clone(); Box::pin(async move { let repo = df_storage::crud::ProjectRepo::new(&db); - let mut items = repo.list_all().await?; + let mut items = repo.list_active().await?; // list_active 排除回收站(deleted_at),防 LLM 看到已软删项目 items.truncate(50); // 防 LLM context 膨胀 Ok(serde_json::to_value(items)?) }) @@ -253,18 +253,57 @@ pub fn build_ai_tool_registry(db: &Arc) -> AiToolRegistry { // ── 高风险 (High) ── registry.register( - "delete_project", "删除项目及其所有关联数据", + "delete_project", "删除项目(移入回收站,可恢复。永久删除用 purge_project)", df_ai::ai_tools::object_schema(vec![("id", "string", true)]), RiskLevel::High, { let db = db.clone(); Box::new(move |args: serde_json::Value| { let db = db.clone(); Box::pin(async move { let id = args["id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 id"))?; let repo = df_storage::crud::ProjectRepo::new(&db); - let deleted = repo.delete(id).await?; + let deleted = repo.soft_delete(id).await?; Ok(serde_json::json!({ "deleted": deleted, "id": id })) }) })}, ); + registry.register( + "restore_project", "从回收站恢复已删除项目", + df_ai::ai_tools::object_schema(vec![("id", "string", true)]), RiskLevel::High, + { let db = db.clone(); Box::new(move |args: serde_json::Value| { + let db = db.clone(); + Box::pin(async move { + let id = args["id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 id"))?; + let repo = df_storage::crud::ProjectRepo::new(&db); + let restored = repo.restore(id).await?; + Ok(serde_json::json!({ "restored": restored, "id": id })) + }) + })}, + ); + registry.register( + "purge_project", "永久删除项目及关联数据(不可恢复)", + df_ai::ai_tools::object_schema(vec![("id", "string", true)]), RiskLevel::High, + { let db = db.clone(); Box::new(move |args: serde_json::Value| { + let db = db.clone(); + Box::pin(async move { + let id = args["id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 id"))?; + let repo = df_storage::crud::ProjectRepo::new(&db); + let purged = repo.purge_with_descendants(id).await?; + Ok(serde_json::json!({ "purged": purged, "id": id })) + }) + })}, + ); + registry.register( + "list_trash", "列出回收站已删除项目", + df_ai::ai_tools::object_schema(vec![]), RiskLevel::Low, + { let db = db.clone(); Box::new(move |_args: serde_json::Value| { + let db = db.clone(); + Box::pin(async move { + let repo = df_storage::crud::ProjectRepo::new(&db); + let mut items = repo.list_deleted().await?; + items.truncate(50); // 防 LLM context 膨胀 + Ok(serde_json::to_value(items)?) + }) + })}, + ); registry.register( "run_workflow", "运行指定的工作流 DAG", df_ai::ai_tools::object_schema(vec![("name", "string", true), ("dag", "object", true)]), RiskLevel::High,