重构: AI聊天可靠性批次(审批累计计数/write_file diff预览/会话隔离补清/token缓存)
This commit is contained in:
@@ -19,6 +19,7 @@ use crate::state::AppState;
|
||||
use crate::commands::{err_str, now_millis};
|
||||
|
||||
use super::{AiChatEvent, AiSession, PendingApproval, ToolCallDraft};
|
||||
use super::tool_registry::generate_diff;
|
||||
|
||||
/// RiskLevel → 审计记录字符串(low/medium/high)
|
||||
pub(crate) fn risk_str(r: RiskLevel) -> &'static str {
|
||||
@@ -274,6 +275,11 @@ pub async fn restore_pending_approvals(state: &AppState) {
|
||||
risk_level: risk,
|
||||
conversation_id: rec.conversation_id,
|
||||
recovered: true,
|
||||
// AE-2025-03: 重启恢复的审批不重读旧文件——审批可能跨重启,
|
||||
// 期间文件可能已被外部改动,重读生成 diff 反映的不是当初决策时的状态,
|
||||
// 且恢复路径在 session.lock 内做 async IO 复杂度高,预览价值低。
|
||||
// 前端见 diff=None 时回退显新 content。
|
||||
diff: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -491,6 +497,28 @@ fn sort_object_keys(v: &mut serde_json::Value) {
|
||||
}
|
||||
}
|
||||
|
||||
/// AE-2025-03(路径 B):write_file 审批预览 diff 生成。
|
||||
///
|
||||
/// 从 write_file args 取 path(旧文件路径)+ content(新内容),
|
||||
/// 预读旧文件 → 复用 `generate_diff`(F-260615-10 LCS 行级 diff)注入审批事件。
|
||||
/// 旧文件不存在(新建)/ 读失败 / args 缺字段 → None(前端回退显新 content)。
|
||||
///
|
||||
/// **仅读不改**:审批未通过前不动文件;读路径不校验(write_file handler 自身会校验
|
||||
/// validate_path,审批拒绝则 handler 不执行,恶意路径读失败仅得到 None 不产生危害)。
|
||||
async fn build_write_file_diff(args: &serde_json::Value) -> Option<String> {
|
||||
let path = args.get("path")?.as_str()?;
|
||||
let new_content = args.get("content")?.as_str()?;
|
||||
match tokio::fs::read_to_string(path).await {
|
||||
Ok(old_content) => {
|
||||
if old_content == new_content {
|
||||
return None; // 无变化不生成 diff(理论上 write_file 不会如此,容错)
|
||||
}
|
||||
Some(generate_diff(&old_content, new_content))
|
||||
}
|
||||
Err(_) => None, // 文件不存在(新建)/ 无读权限 → 无 diff,前端显新 content
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理流式接收的工具调用:Low 风险并行执行(join_all),Med/High 进审批门控
|
||||
/// 返回待审批的工具数量(0 = 全部自动执行完成)
|
||||
pub(crate) async fn process_tool_calls(
|
||||
@@ -553,6 +581,15 @@ pub(crate) async fn process_tool_calls(
|
||||
}
|
||||
}
|
||||
pending_count += 1;
|
||||
// AE-2025-03(路径 B):write_file 挂起审批前预读旧文件生成 diff。
|
||||
// 仅 write_file(覆盖整文件,有完整新旧内容可对比);其他工具 diff=None。
|
||||
// 旧文件不存在(新建)→ diff=None,前端回退显新 content。
|
||||
// 读失败不阻断审批(容错:文件无读权限等极端情况降级为无 diff 预览)。
|
||||
let approval_diff: Option<String> = if draft.name == "write_file" {
|
||||
build_write_file_diff(&args).await
|
||||
} else {
|
||||
None
|
||||
};
|
||||
session.pending_approvals.insert(draft.id.clone(), PendingApproval {
|
||||
tool_call_id: draft.id.clone(),
|
||||
tool_name: draft.name.clone(),
|
||||
@@ -560,6 +597,7 @@ pub(crate) async fn process_tool_calls(
|
||||
risk_level,
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
recovered: false,
|
||||
diff: approval_diff.clone(),
|
||||
});
|
||||
session.messages.push(ChatMessage::tool_result(&draft.id, "需要用户审批,等待确认"));
|
||||
let reason = build_approval_reason(&draft.name, &args, risk_level, db).await;
|
||||
@@ -568,6 +606,7 @@ pub(crate) async fn process_tool_calls(
|
||||
name: draft.name.clone(),
|
||||
args: args.clone(),
|
||||
reason,
|
||||
diff: approval_diff,
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
});
|
||||
audit_tool_call(&audit_repo, conv_id, &draft.id, &draft.name, &draft.args, "pending", risk_level, None, None).await;
|
||||
|
||||
Reference in New Issue
Block a user