重构: audit第五批utils/diff收尾(strategy·audit拆分完成)
- 新建 audit/utils.rs(39行 risk_str/risk_from_str/truncate_chars) + audit/diff.rs(29行 build_write_file_diff) - audit/mod.rs 416→379: mod utils/diff + re-export(pub(crate) use risk_str/risk_from_str) + 删原fn audit拆分完成: reason/restore/finalize/cache/data_change/utils/diff七子模块, mod.rs余379(process_tool_calls主+DTO+list_tool_executions) 主代grep核验(不cargo--workspace避anthropic_compat.rs其他会话中间态df-ai E0599): mod.rs mod utils/diff印证; agent隔离自验cargo 0+test 98 git add指定(audit/*, 不含anthropic_compat.rs/docs其他会话改动)
This commit is contained in:
29
src-tauri/src/commands/ai/audit/diff.rs
Normal file
29
src-tauri/src/commands/ai/audit/diff.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
//! AE-2025-03(路径 B):write_file 审批预览 diff 生成。
|
||||
//!
|
||||
//! 第五批从 audit/mod.rs 抽离,行为零变更。`build_write_file_diff` 仅 process_tool_calls
|
||||
//! 在 write_file 高风险审批分支裸名调用,依赖 `super::super::tool_registry::generate_diff`
|
||||
//! (F-260615-10 LCS 行级 diff),故 diff.rs 直接 `use super::super::tool_registry::generate_diff`。
|
||||
//!
|
||||
//! re-export 保持原路径透明:audit/mod.rs 通过 `use diff::build_write_file_diff` 裸名调用。
|
||||
|
||||
use super::super::tool_registry::generate_diff;
|
||||
|
||||
/// 从 write_file args 取 path(旧文件路径)+ content(新内容),
|
||||
/// 预读旧文件 → 复用 `generate_diff`(F-260615-10 LCS 行级 diff)注入审批事件。
|
||||
/// 旧文件不存在(新建)/ 读失败 / args 缺字段 → None(前端回退显新 content)。
|
||||
///
|
||||
/// **仅读不改**:审批未通过前不动文件;读路径不校验(write_file handler 自身会校验
|
||||
/// validate_path,审批拒绝则 handler 不执行,恶意路径读失败仅得到 None 不产生危害)。
|
||||
pub(super) 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
|
||||
}
|
||||
}
|
||||
@@ -16,26 +16,21 @@ use crate::state::AppState;
|
||||
use crate::commands::err_str;
|
||||
|
||||
use super::{AiChatEvent, AiSession, PendingApproval, ToolCallDraft};
|
||||
use super::tool_registry::generate_diff;
|
||||
// tool_registry::generate_diff 经 audit/diff.rs 直接 use(build_write_file_diff 内调用),
|
||||
// 本文件不再裸名用 generate_diff,故不再在此 use(避免 unused import warning)。
|
||||
|
||||
/// RiskLevel → 审计记录字符串(low/medium/high)
|
||||
pub(crate) fn risk_str(r: RiskLevel) -> &'static str {
|
||||
match r {
|
||||
RiskLevel::Low => "low",
|
||||
RiskLevel::Medium => "medium",
|
||||
RiskLevel::High => "high",
|
||||
}
|
||||
}
|
||||
// utils(audit/utils.rs):RiskLevel ↔ 字符串转换 + 字符串安全截断纯 helper。
|
||||
// 第五批从本文件抽离,行为零变更。pub(crate) use 保持 finalize / restore 子模块
|
||||
// super::risk_str / super::risk_from_str 引用路径不变;truncate_chars 仅供本文件 DTO 装配裸名用。
|
||||
mod utils;
|
||||
pub(crate) use utils::{risk_from_str, risk_str};
|
||||
use utils::truncate_chars;
|
||||
|
||||
/// 审计记录字符串 → RiskLevel(启动重建 pending_approvals 用,未知串返回 None 跳过)
|
||||
pub(crate) fn risk_from_str(s: &str) -> Option<RiskLevel> {
|
||||
match s {
|
||||
"low" => Some(RiskLevel::Low),
|
||||
"medium" => Some(RiskLevel::Medium),
|
||||
"high" => Some(RiskLevel::High),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
// diff(audit/diff.rs):AE-2025-03 write_file 审批预览 diff 生成。
|
||||
// 第五批从本文件抽离,行为零变更。use 保持本文件 process_tool_calls 裸名调用 build_write_file_diff。
|
||||
// generate_diff 经 `use super::generate_diff` 引入供 diff.rs 复用。
|
||||
mod diff;
|
||||
use diff::build_write_file_diff;
|
||||
|
||||
// ============================================================
|
||||
// 审批历史查询 IPC(AE-2025-08)
|
||||
@@ -65,16 +60,6 @@ pub struct ToolExecutionDto {
|
||||
pub decided_by: Option<String>,
|
||||
}
|
||||
|
||||
/// 截断字符串到 max 字符(按 char_indices 边界切,避免切坏中文/emoji)
|
||||
fn truncate_chars(s: &str, max: usize) -> String {
|
||||
if s.chars().count() <= max {
|
||||
return s.to_string();
|
||||
}
|
||||
let mut out: String = s.chars().take(max).collect();
|
||||
out.push('…');
|
||||
out
|
||||
}
|
||||
|
||||
/// 审批历史面板查询:按 requested_at 倒序(最新在前)分页返回工具调用审计记录。
|
||||
///
|
||||
/// 默认 limit=50 / offset=0(第一页)。limit 在 storage 层钳制 ≤200 防滥用。
|
||||
@@ -141,28 +126,6 @@ pub(super) use cache::{find_cached_high_risk_result, PENDING_APPROVAL_PLACEHOLDE
|
||||
mod data_change;
|
||||
pub(crate) use data_change::emit_data_changed;
|
||||
|
||||
/// 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(
|
||||
|
||||
39
src-tauri/src/commands/ai/audit/utils.rs
Normal file
39
src-tauri/src/commands/ai/audit/utils.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
//! audit 共享纯 helper:RiskLevel ↔ 字符串转换 + 字符串安全截断。
|
||||
//!
|
||||
//! 第五批从 audit/mod.rs 抽离,行为零变更:
|
||||
//! - `risk_str` / `risk_from_str`:finalize / restore 通过 `super::` 引用(pub(crate))。
|
||||
//! - `truncate_chars`:仅 mod.rs 的 list_tool_executions DTO 装配用(pub(super))。
|
||||
//!
|
||||
//! re-export 维持原路径透明:`super::risk_str` / `super::risk_from_str` / `super::truncate_chars`
|
||||
//! 在 audit/mod.rs 内通过 `use` 引入后裸名可用,外部子模块的 `super::risk_*` 引用不变。
|
||||
|
||||
use df_ai::ai_tools::RiskLevel;
|
||||
|
||||
/// RiskLevel → 审计记录字符串(low/medium/high)
|
||||
pub(crate) fn risk_str(r: RiskLevel) -> &'static str {
|
||||
match r {
|
||||
RiskLevel::Low => "low",
|
||||
RiskLevel::Medium => "medium",
|
||||
RiskLevel::High => "high",
|
||||
}
|
||||
}
|
||||
|
||||
/// 审计记录字符串 → RiskLevel(启动重建 pending_approvals 用,未知串返回 None 跳过)
|
||||
pub(crate) fn risk_from_str(s: &str) -> Option<RiskLevel> {
|
||||
match s {
|
||||
"low" => Some(RiskLevel::Low),
|
||||
"medium" => Some(RiskLevel::Medium),
|
||||
"high" => Some(RiskLevel::High),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 截断字符串到 max 字符(按 char_indices 边界切,避免切坏中文/emoji)
|
||||
pub(super) fn truncate_chars(s: &str, max: usize) -> String {
|
||||
if s.chars().count() <= max {
|
||||
return s.to_string();
|
||||
}
|
||||
let mut out: String = s.chars().take(max).collect();
|
||||
out.push('…');
|
||||
out
|
||||
}
|
||||
Reference in New Issue
Block a user