重构: AI聊天可靠性批次(审批累计计数/write_file diff预览/会话隔离补清/token缓存)

This commit is contained in:
2026-06-16 19:56:29 +08:00
parent ba7f35552b
commit d00b30f0ba
12 changed files with 238 additions and 45 deletions

View File

@@ -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路径 Bwrite_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_allMed/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路径 Bwrite_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;