修复: 灵感 CRUD 工具补齐 + handler 透传 affected(B-260801-01)
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
//! 灵感类 AI 工具声明式注册(register_idea_tools 2 个:list + create 迁入)。
|
||||
//! 灵感类 AI 工具声明式注册(list + create + update + delete 共 4 个)。
|
||||
//!
|
||||
//! 迁自 `tool_registry.rs::register_idea_tools`(原 2 个 list_ideas/create_idea),
|
||||
//! 改用 `declare_tool!` 宏。
|
||||
//! 改用 `declare_tool!` 宏。新增 update_idea / delete_idea(AI 改/删灵感的专用工具,
|
||||
//! 后端 idea_repo.update_field / soft_delete 早已具备,工具层此前缺失,致 AI 改/删灵感
|
||||
//! 时被迫误用 update_task/delete_task)。
|
||||
//!
|
||||
//! 迁移策略(handler 逻辑零变更):
|
||||
//! - handler body 逐字照搬原 `register_idea_tools` 内 async move 块(逻辑等价),
|
||||
@@ -11,7 +13,7 @@
|
||||
//! - 复用类型/常量:new_id/IdeaStatus/IdeaRecord/now_millis(各 pub(crate) 来源,与原同源) +
|
||||
//! MAX_LIST_RESULTS(super::tool_registry 单真相源)。
|
||||
//!
|
||||
//! 等价性验证:基线测试 `test_build_ai_tool_registry_baseline_tool_count` 仍断言 48 总量 +
|
||||
//! 等价性验证:基线测试 `test_build_ai_tool_registry_baseline_tool_count` 断言 50 总量 +
|
||||
//! 工具名集合稳定(防 rename / 漏注册)。
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -25,11 +27,11 @@ use df_types::types::{new_id, IdeaStatus};
|
||||
use crate::commands::now_millis;
|
||||
use crate::commands::ai::tool_registry::MAX_LIST_RESULTS;
|
||||
|
||||
/// 注册 2 个灵感类工具(list/create)到 `$registry`。
|
||||
/// 注册 4 个灵感类工具(list/create/update/delete)到 `$registry`。
|
||||
///
|
||||
/// 与原手写 register(name, desc, schema, risk, handler) 语义 1:1:
|
||||
/// - name/desc/schema 字符串与 JSON Schema 逐字照搬原定义
|
||||
/// - risk 与原一致(list=Low,create=Medium)
|
||||
/// - risk 与原一致(list=Low,create/update=Medium,delete=High)
|
||||
/// - handler body 与原 async move 块逐字一致(逻辑零变更)
|
||||
///
|
||||
/// 唯一差异:闭包包装改由 `declare_tool!` 宏生成,handler body 直接写业务逻辑。
|
||||
@@ -84,4 +86,50 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
|
||||
Ok(serde_json::json!({ "id": id, "title": title, "status": "draft" }))
|
||||
}
|
||||
);
|
||||
|
||||
// update_idea / delete_idea:AI 改/删灵感的专用工具(对标 update_task / delete_task)。
|
||||
// 后端 idea_repo.update_field / soft_delete 早已具备,工具层此前缺失,致 AI 改/删灵感
|
||||
// 时被迫误用 update_task(任务表)——既改错表又触发 status 收口误报。
|
||||
// 与 update_task 同源:白名单复用 df-storage CRUD(is_allowed_column("ideas", field)),
|
||||
// 双重防御(schema 限制 + 白名单校验)。返回 updated 透传 affected bool(不硬编码 true),
|
||||
// 0 行(id 不存在 / 已删)→ updated=false + title 空,让 AI 知道 id 不对而非假成功。
|
||||
declare_tool!(
|
||||
registry,
|
||||
db: Arc<Database>,
|
||||
"update_idea",
|
||||
"更新灵感的指定字段(title/description/priority/tags/source/score 等),需要提供灵感 ID、字段名和新值。",
|
||||
RiskLevel::Medium,
|
||||
schema: object_schema(vec![("id", "string", true), ("field", "string", true), ("value", "string", true)]),
|
||||
args => {
|
||||
let id = args["id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 id"))?;
|
||||
let field = args["field"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 field"))?;
|
||||
let value = args["value"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 value"))?;
|
||||
// 复用 df-storage CRUD 白名单(按表隔离),与 update_field 校验同源
|
||||
if !df_storage::crud::is_allowed_column("ideas", field) {
|
||||
anyhow::bail!("不允许更新字段 '{}'", field);
|
||||
}
|
||||
let repo = df_storage::crud::IdeaRepo::new(&db);
|
||||
let affected = repo.update_field(id, field, value).await?;
|
||||
// 对齐 update_task:返回 title 供前端卡片友好展示(防裸 UUID 辨识度低)。
|
||||
// 0 行(id 不存在 / 已软删)→ affected=false 且 get_by_id 返 None→title 空,
|
||||
// 透传 affected 让 AI 知道 id 不对而非假成功。
|
||||
let title = repo.get_by_id(id).await?.map(|i| i.title).unwrap_or_default();
|
||||
Ok(serde_json::json!({ "id": id, "title": title, "field": field, "updated": affected }))
|
||||
}
|
||||
);
|
||||
|
||||
declare_tool!(
|
||||
registry,
|
||||
db: Arc<Database>,
|
||||
"delete_idea",
|
||||
"删除灵感(软删除,移入回收站,可恢复)",
|
||||
RiskLevel::Medium,
|
||||
schema: object_schema(vec![("id", "string", true)]),
|
||||
args => {
|
||||
let id = args["id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 id"))?;
|
||||
let repo = df_storage::crud::IdeaRepo::new(&db);
|
||||
let affected = repo.soft_delete(id).await?;
|
||||
Ok(serde_json::json!({ "id": id, "deleted": affected }))
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,8 +52,13 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
|
||||
anyhow::bail!("不允许更新字段 '{}'", field);
|
||||
}
|
||||
let repo = df_storage::crud::ProjectRepo::new(&db);
|
||||
repo.update_field(id, field, value).await?;
|
||||
Ok(serde_json::json!({ "id": id, "field": field, "updated": true }))
|
||||
// 透传 affected:0 行(id 不存在 / 已软删)→ updated=false + name 空,
|
||||
// 让 AI 知道 id 不对而非假成功(对齐 update_idea / update_task)。
|
||||
let updated = repo.update_field(id, field, value).await?;
|
||||
// 返回 name 供前端卡片友好展示(对齐 update_task 返 title);update_field 返 bool 不含
|
||||
// record,单独 get_by_id 取 name;0 行时 get_by_id 返 None→name 空,配合 updated:false。
|
||||
let name = repo.get_by_id(id).await?.map(|p| p.name).unwrap_or_default();
|
||||
Ok(serde_json::json!({ "id": id, "name": name, "field": field, "updated": updated }))
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -177,11 +177,13 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
|
||||
anyhow::bail!("不允许更新字段 '{}'", field);
|
||||
}
|
||||
let repo = df_storage::crud::TaskRepo::new(&db);
|
||||
repo.update_field(id, field, value).await?;
|
||||
// 透传 affected:0 行(id 不存在 / 已软删)→ updated=false + title 空,
|
||||
// 让 AI 知道 id 不对而非假成功(对齐 update_idea)。
|
||||
let updated = repo.update_field(id, field, value).await?;
|
||||
// UX-260619-04: 返回 title 供前端卡片友好展示(对齐 advance_task 返 TaskRecord,
|
||||
// 防裸 UUID id 辨识度低)。update_field 返 bool 不含 record,单独 get_by_id 取 title。
|
||||
let title = repo.get_by_id(id).await?.map(|t| t.title).unwrap_or_default();
|
||||
Ok(serde_json::json!({ "id": id, "title": title, "field": field, "updated": true }))
|
||||
Ok(serde_json::json!({ "id": id, "title": title, "field": field, "updated": updated }))
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -228,9 +228,11 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
|
||||
.map_err(|e| anyhow::anyhow!("content_json 不是合法 JSON: {e}"))?;
|
||||
}
|
||||
let repo = df_storage::crud::TaskRepo::new(&db);
|
||||
repo.update_field(id, "content_json", content_json).await?;
|
||||
// 透传 affected:0 行(id 不存在 / 已软删)→ updated=false + title 空,
|
||||
// 让 AI 知道 id 不对而非假成功(对齐 update_idea / update_task)。
|
||||
let updated = repo.update_field(id, "content_json", content_json).await?;
|
||||
let title = repo.get_by_id(id).await?.map(|t| t.title).unwrap_or_default();
|
||||
Ok(serde_json::json!({ "id": id, "title": title, "field": "content_json", "updated": true }))
|
||||
Ok(serde_json::json!({ "id": id, "title": title, "field": "content_json", "updated": updated }))
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user