From 4dee7066316baac72202c90ec0e83a04cf326005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Fri, 19 Jun 2026 04:53:01 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84:=20audit=E7=AC=AC=E4=BA=94?= =?UTF-8?q?=E6=89=B9utils/diff=E6=94=B6=E5=B0=BE(strategy=C2=B7audit?= =?UTF-8?q?=E6=8B=86=E5=88=86=E5=AE=8C=E6=88=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建 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其他会话改动) --- src-tauri/src/commands/ai/audit/diff.rs | 29 +++++++++++ src-tauri/src/commands/ai/audit/mod.rs | 63 +++++------------------- src-tauri/src/commands/ai/audit/utils.rs | 39 +++++++++++++++ 3 files changed, 81 insertions(+), 50 deletions(-) create mode 100644 src-tauri/src/commands/ai/audit/diff.rs create mode 100644 src-tauri/src/commands/ai/audit/utils.rs diff --git a/src-tauri/src/commands/ai/audit/diff.rs b/src-tauri/src/commands/ai/audit/diff.rs new file mode 100644 index 0000000..f8273c5 --- /dev/null +++ b/src-tauri/src/commands/ai/audit/diff.rs @@ -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 { + 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 + } +} diff --git a/src-tauri/src/commands/ai/audit/mod.rs b/src-tauri/src/commands/ai/audit/mod.rs index 653074a..0968cbc 100644 --- a/src-tauri/src/commands/ai/audit/mod.rs +++ b/src-tauri/src/commands/ai/audit/mod.rs @@ -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 { - 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, } -/// 截断字符串到 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 { - 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( diff --git a/src-tauri/src/commands/ai/audit/utils.rs b/src-tauri/src/commands/ai/audit/utils.rs new file mode 100644 index 0000000..8119558 --- /dev/null +++ b/src-tauri/src/commands/ai/audit/utils.rs @@ -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 { + 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 +}