From 0a8db6504acd660407b8876c4e71df4a83fd84d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Wed, 24 Jun 2026 00:27:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96:=20=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E9=A1=B5UX=E4=B8=8EAI=E8=87=AA=E5=8A=A8=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=B8=89=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/commands/ai/audit/mod.rs | 57 +++++++-- src/components/settings/AdvancedSection.vue | 134 +++++++++++++++++--- src/components/settings/SettingsNav.vue | 2 +- src/components/settings/searchIndex.ts | 5 +- src/i18n/en/settings.ts | 9 +- src/i18n/zh-CN/settings.ts | 9 +- src/views/Settings.vue | 2 +- 7 files changed, 180 insertions(+), 38 deletions(-) diff --git a/src-tauri/src/commands/ai/audit/mod.rs b/src-tauri/src/commands/ai/audit/mod.rs index 04060c2..2e3466a 100644 --- a/src-tauri/src/commands/ai/audit/mod.rs +++ b/src-tauri/src/commands/ai/audit/mod.rs @@ -427,17 +427,43 @@ pub(crate) async fn process_tool_calls( // 若 LLM 重试同命令(同 tool_name + 同 args,键序无关),命中已落定的旧 tool_result, // 把缓存结果作为新 tool_call_id 的 tool_result 回传 LLM,跳过 insert pending + 跳过审批, // 断「超时→重试→重新审批」循环。Med 不去重(去重易误伤),Low 无审批本就不进此分支。 - let mut low_risk: Vec<(ToolCallDraft, serde_json::Value)> = Vec::new(); + // F-#97:low_risk 向量携带 risk_level(治 securityReview blocker :710 审计失真)。 + // 原 Vec 仅 (draft,args),回填审计硬编码 RiskLevel::Low;#97 让 Med/High(all/medium 模式) + // 也进此向量,需透传真实等级,审计方可追溯「all 模式执行了多少高危命令」。 + let mut low_risk: Vec<(ToolCallDraft, serde_json::Value, RiskLevel)> = Vec::new(); + // F-#97 自动执行范围三档(2026-06-22):KV df-ai-auto-execute-mode 取值 low/medium/all,默认 low(=现状)。 + // low:仅 Low 自动(等价旧行为);medium:Low+Medium 自动;all:全自动无审批(完全 AI 接管)。 + // 锁内 await KV 快读,不竞争 session 锁(settings 独立 repo)。 + let auto_exec_mode = app_handle + .state::() + .settings + .get("df-ai-auto-execute-mode") + .await + .ok() + .flatten() + .unwrap_or_else(|| "low".to_string()); // trust_hits 收集:AE-04 会话信任命中(同会话已批准同类操作),真实执行但移到锁外 spawn // (对齐 Low risk L673-720 不持锁模式)。命中时此处只 emit toast + 收集,不 .await execute。 // 元组携带 risk_level: 回填审计需原等级(Med/High 才进此分支),避免闭包内再查 registry let mut trust_hits: Vec<(ToolCallDraft, serde_json::Value, String, RiskLevel)> = Vec::new(); for (_, draft, args) in drafts { let risk_level = tools_arc.get(&draft.name).map(|t| t.risk_level).unwrap_or(RiskLevel::High); - match risk_level { - RiskLevel::Low => low_risk.push((draft, args)), - RiskLevel::Medium | RiskLevel::High => { - // AE-2025-04 会话级信任(Session Trust):首批 write_file / run_command, + // F-#97:按 auto_exec_mode 判定是否自动执行。should_auto=true 走 low_risk(自动),false 走原审批块。 + // low(默认):Low→auto, Med/High→审批(等价现状) + // medium:Low/Med→auto, High→审批 + // all:全 auto(完全接管,无审批) + let should_auto = match risk_level { + RiskLevel::Low => true, + RiskLevel::Medium => auto_exec_mode == "medium" || auto_exec_mode == "all", + RiskLevel::High => auto_exec_mode == "all", + }; + if should_auto { + low_risk.push((draft, args, risk_level)); + } else { + // 原 Medium|High 审批分支(trust_key 命中/dedup 缓存/retry guard/pending insert + emit + audit_tool_call) + // 整体保留,逻辑不变。trust_hits 元组携带 risk_level 语义不变:Med 进此处=Medium, + // High 进此处(仅 low/medium 模式)时=High。 + // AE-2025-04 会话级信任(Session Trust):首批 write_file / run_command, // 同会话已批准过同工具+同目录 → TrustKey 命中 → 自动放行(跳过 pending + 二次确认)。 // 命中后走与 F-05 去重命中相似的「直接执行 + Completed + 审计 decided_by=auto_trust」路径, // 但与 F-05 不同:F-05 复用缓存 tool_result 跳过执行;trust 放行**真实执行工具** @@ -559,7 +585,6 @@ pub(crate) async fn process_tool_calls( let _ = app_handle.emit("ai-chat-event", ev.clone()); let _ = app_handle.state::().ai_event_bus.publish_event(ev); audit_tool_call(&audit_repo, conv_id, &draft.id, &draft.name, &draft.args, "pending", risk_level, None, None, current_message_id).await; - } } } @@ -627,8 +652,8 @@ pub(crate) async fn process_tool_calls( // join_all 保序——结果顺序 = low_risk 输入顺序 = tc_list 原始 index 顺序,不额外 sort if !low_risk.is_empty() { // 携带 args + 原始 JSON result(create_idea source 补全需解析 result.id + args.source)。 - let results: Vec<(ToolCallDraft, serde_json::Value, Result)> = - futures::future::join_all(low_risk.into_iter().map(|(draft, args)| { + let results: Vec<(ToolCallDraft, serde_json::Value, RiskLevel, Result)> = + futures::future::join_all(low_risk.into_iter().map(|(draft, args, risk_level)| { let tools = tools_arc.clone(); let app_clone = app_handle.clone(); let conv_clone = conv_id.to_string(); @@ -647,7 +672,7 @@ pub(crate) async fn process_tool_calls( // AR-11(方案A):数据变更工具执行成功后 emit df-data-changed, // 前端 store listen 刷新列表(仅命中映射的工具 emit,见 emit_data_changed)。 emit_data_changed(&app_clone, &draft.name); - (draft, val.clone(), Ok(val.to_string())) + (draft, val.clone(), risk_level, Ok(val.to_string())) } Err(e) => { // AR-6(定向B):Low 工具失败不 emit AiError。 @@ -664,14 +689,14 @@ pub(crate) async fn process_tool_calls( }; let _ = app_clone.emit("ai-chat-event", ev.clone()); let _ = app_clone.state::().ai_event_bus.publish_event(ev); - (draft, serde_json::Value::Null, Err(err_msg)) + (draft, serde_json::Value::Null, risk_level, Err(err_msg)) } } } })).await; // 串行回填 tool_result + 审计(持 session 锁) - for (draft, raw_result, outcome) in results { + for (draft, raw_result, risk_level, outcome) in results { let (status, content) = match outcome { Ok(c) => ("completed", c), Err(c) => ("failed", c), @@ -685,7 +710,15 @@ pub(crate) async fn process_tool_calls( } // F-260616-09 B 批2:写 per_conv.messages。 session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, content.clone())); - audit_tool_call(&audit_repo, conv_id, &draft.id, &draft.name, &draft.args, status, RiskLevel::Low, Some(content), Some("auto"), current_message_id).await; + // F-#97 审计留痕:low_risk 向量中 risk_level != Low 即 mode 放行(Med 仅 medium/all、High 仅 all 才进)。 + // decided_by 区分接管来源,审计表可追溯 all 模式执行了多少高危命令(治 securityReview blocker: + // 原硬编码 RiskLevel::Low 致 Med/High 接管操作全标 Low/auto 失真,等于没记)。 + let decided_by: &str = match risk_level { + RiskLevel::Low => "auto", + RiskLevel::Medium => "auto_takeover_medium", + RiskLevel::High => "auto_takeover_all", + }; + audit_tool_call(&audit_repo, conv_id, &draft.id, &draft.name, &draft.args, status, risk_level, Some(content), Some(decided_by), current_message_id).await; } } diff --git a/src/components/settings/AdvancedSection.vue b/src/components/settings/AdvancedSection.vue index 70842ba..d543f05 100644 --- a/src/components/settings/AdvancedSection.vue +++ b/src/components/settings/AdvancedSection.vue @@ -1,18 +1,21 @@ diff --git a/src/components/settings/SettingsNav.vue b/src/components/settings/SettingsNav.vue index d09dcaf..1561508 100644 --- a/src/components/settings/SettingsNav.vue +++ b/src/components/settings/SettingsNav.vue @@ -54,7 +54,7 @@ // knowledge 知识库(auto_extract/trigger_mode/embedding_*) // performance 性能(global/per-conv concurrency/max-iterations/max-retries) // security 安全(allowed_dirs) -// advanced 高级(autoExecute/logLevel + 连接) +// advanced 高级(autoExecuteMode/logLevel + 连接) // ============================================================ import { computed, ref, watch } from 'vue' diff --git a/src/components/settings/searchIndex.ts b/src/components/settings/searchIndex.ts index 6ff9c8b..d8318c6 100644 --- a/src/components/settings/searchIndex.ts +++ b/src/components/settings/searchIndex.ts @@ -42,7 +42,7 @@ export interface SettingIndexEntry { * performance 性能(global-concurrency/per-conv-concurrency/ * max-iterations/max-retries) * security 安全(allowed_dirs) - * advanced 高级(autoExecute/logLevel + 连接) + * advanced 高级(autoExecuteMode/logLevel + 连接) */ export const SETTINGS_INDEX: SettingIndexEntry[] = [ // ===== 外观 ===== @@ -82,7 +82,8 @@ export const SETTINGS_INDEX: SettingIndexEntry[] = [ { category: 'security', key: 'allowedDirs', labelKey: 'settings.panelAllowedDirs', descKey: 'settings.descAllowedDirs' }, // ===== 高级 ===== - { category: 'advanced', key: 'autoExecute', labelKey: 'settings.labelAutoExecute', descKey: 'settings.descAutoExecute' }, + // F-#97:autoExecute(boolean) → autoExecuteMode(low/medium/all 三档),key 同步更名 + { category: 'advanced', key: 'autoExecuteMode', labelKey: 'settings.labelAutoExecute', descKey: 'settings.descAutoExecute' }, { category: 'advanced', key: 'logLevel', labelKey: 'settings.labelLogLevel', descKey: 'settings.descLogLevel' }, { category: 'advanced', key: 'connection', labelKey: 'settings.panelConnection', descKey: 'settings.emptyConnection' }, ] diff --git a/src/i18n/en/settings.ts b/src/i18n/en/settings.ts index bb8584d..262c72f 100644 --- a/src/i18n/en/settings.ts +++ b/src/i18n/en/settings.ts @@ -111,8 +111,13 @@ export default { aiLanguageZh: '🇨🇳 简体中文', aiLanguageEn: '🇺🇸 English', - labelAutoExecute: 'Auto-execute', - descAutoExecute: 'Let AI auto-execute low-risk actions', + labelAutoExecute: 'AI auto-execute scope', + descAutoExecute: 'Control risk level AI auto-executes (low/medium/all-takeover)', + autoModeLow: 'Low risk (default)', + autoModeMedium: 'Include medium', + autoModeAll: 'Full takeover (no approval)', + autoModeAllConfirmTitle: 'Confirm full AI takeover?', + autoModeAllConfirmContent: 'All actions (including high-risk) will be auto-executed without approval. AI may directly perform dangerous operations (delete files / run arbitrary commands, etc.). Confirm only if you trust the current conversation AI.', labelLogLevel: 'Log level', descLogLevel: 'Verbosity of workflow logs', diff --git a/src/i18n/zh-CN/settings.ts b/src/i18n/zh-CN/settings.ts index 7b7ff62..c50d54a 100644 --- a/src/i18n/zh-CN/settings.ts +++ b/src/i18n/zh-CN/settings.ts @@ -111,8 +111,13 @@ export default { aiLanguageZh: '🇨🇳 简体中文', aiLanguageEn: '🇺🇸 English', - labelAutoExecute: 'AI 自动执行', - descAutoExecute: 'AI 可自动执行低风险操作', + labelAutoExecute: 'AI 自动执行范围', + descAutoExecute: '控制 AI 自动执行操作的风险等级(低/中/全部接管)', + autoModeLow: '低风险(默认)', + autoModeMedium: '含中风险', + autoModeAll: '全部接管(无审批)', + autoModeAllConfirmTitle: '确认完全 AI 接管?', + autoModeAllConfirmContent: '所有操作(含高危)将自动执行,无审批。AI 可能直接执行危险操作(删文件/执行任意命令等),请确认你信任当前对话的 AI。', labelLogLevel: '日志级别', descLogLevel: '工作流日志详细程度', diff --git a/src/views/Settings.vue b/src/views/Settings.vue index 7fca2e4..0d03ef9 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -62,7 +62,7 @@ @toast="showToast" /> - +