修复: aichat P0 三项(AR-2 新建对话守卫 + AR-3 审批卡片可读化 + AR-4 create_project 双审)
- AR-2 ai_conversation_create 加 generating 守卫(clear 前,防审批态新建清空 session 致 generating 永不复位卡死) - AR-3 audit.rs build_approval_reason 按 9 工具特化拼对象名(id/field/path/title/name)+ ToolCard resultSummary 补 restore/purge case + ARG_LABEL_MAP id 加友好标签 + i18n 双语补 4 key - AR-4 create_project schema 加 path/stack + handler 有 path 时合并绑定(spawn_blocking 探测栈,复用 bind_directory 逻辑,消除双审双 API) - cargo check / vue-tsc 0 error - 审查 boundary 全误判(并行同工作区 git diff 污染),主代理独立核查三任务全正确
This commit is contained in:
@@ -38,6 +38,71 @@ pub(crate) fn risk_from_str(s: &str) -> Option<RiskLevel> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 拼审批 reason:按工具名 + args 取可读字段,让用户知道"审批要做什么"。
|
||||
///
|
||||
/// 主要审批工具特化(带对象名/标识):
|
||||
/// - delete_project/restore_project/purge_project → "删除/恢复/清除项目"+id
|
||||
/// - update_project → "修改项目"+field
|
||||
/// - bind_directory → "绑定目录"+path
|
||||
/// - create_task → "创建任务"+title;create_project → "创建项目"+name;create_idea → "捕获想法"+title
|
||||
/// - run_workflow → "运行工作流"+name
|
||||
/// args 无可读字段或工具未特化时,fallback 原 risk 模板(含风险等级提示)。
|
||||
fn build_approval_reason(tool_name: &str, args: &serde_json::Value, risk_level: RiskLevel) -> String {
|
||||
let s = |key: &str| args.get(key).and_then(|v| v.as_str()).unwrap_or("");
|
||||
let detail = match tool_name {
|
||||
"delete_project" => {
|
||||
let id = s("id");
|
||||
if !id.is_empty() { format!("删除项目(ID: {})", id) } else { String::new() }
|
||||
}
|
||||
"restore_project" => {
|
||||
let id = s("id");
|
||||
if !id.is_empty() { format!("从回收站恢复项目(ID: {})", id) } else { String::new() }
|
||||
}
|
||||
"purge_project" => {
|
||||
let id = s("id");
|
||||
if !id.is_empty() { format!("永久删除项目及关联数据,不可恢复(ID: {})", id) } else { String::new() }
|
||||
}
|
||||
"update_project" => {
|
||||
let field = s("field");
|
||||
if !field.is_empty() { format!("修改项目字段「{}」", field) } else { String::new() }
|
||||
}
|
||||
"bind_directory" => {
|
||||
let path = s("path");
|
||||
if !path.is_empty() { format!("绑定目录:{}", path) } else { String::new() }
|
||||
}
|
||||
"create_task" => {
|
||||
let title = s("title");
|
||||
if !title.is_empty() { format!("创建任务:{}", title) } else { String::new() }
|
||||
}
|
||||
"create_project" => {
|
||||
let name = s("name");
|
||||
if !name.is_empty() { format!("创建项目:{}", name) } else { String::new() }
|
||||
}
|
||||
"create_idea" => {
|
||||
let title = s("title");
|
||||
if !title.is_empty() { format!("捕获想法:{}", title) } else { String::new() }
|
||||
}
|
||||
"run_workflow" => {
|
||||
let name = s("name");
|
||||
if !name.is_empty() { format!("运行工作流:{}", name) } else { String::new() }
|
||||
}
|
||||
_ => String::new(),
|
||||
};
|
||||
if detail.is_empty() {
|
||||
// fallback:无可读字段,保留风险等级提示模板
|
||||
match risk_level {
|
||||
RiskLevel::High => "高风险操作,必须人工批准".to_string(),
|
||||
_ => "创建操作,请确认是否执行".to_string(),
|
||||
}
|
||||
} else {
|
||||
// 拼上风险等级后缀(高风险标注,便于用户权衡)
|
||||
match risk_level {
|
||||
RiskLevel::High => format!("{}(高风险,需人工批准)", detail),
|
||||
_ => format!("{}(请确认是否执行)", detail),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 启动恢复:从审计表重建 pending_approvals(重启前未审批的工具调用,内存态已丢)
|
||||
///
|
||||
/// pending_approvals 是 AiSession 内存 HashMap,重启必丢。ai_tool_executions 表已存
|
||||
@@ -176,10 +241,7 @@ pub(crate) async fn process_tool_calls(
|
||||
recovered: false,
|
||||
});
|
||||
session.messages.push(ChatMessage::tool_result(&draft.id, "需要用户审批,等待确认"));
|
||||
let reason = match risk_level {
|
||||
RiskLevel::High => "高风险操作,必须人工批准".to_string(),
|
||||
_ => "创建操作,请确认是否执行".to_string(),
|
||||
};
|
||||
let reason = build_approval_reason(&draft.name, &args, risk_level);
|
||||
let _ = app_handle.emit("ai-chat-event", AiChatEvent::AiApprovalRequired {
|
||||
id: draft.id.clone(),
|
||||
name: draft.name.clone(),
|
||||
|
||||
Reference in New Issue
Block a user