修复: KV 设置读取引号归一(自动执行三档生效)

前端 appSettings.set 以 JSON.stringify 落库带引号,后端裸读比对失败,
自动执行三档(low/medium/all)自上线从未生效静默走审批;脚本黑白名单同坑,读端统一 trim。
This commit is contained in:
lxy
2026-09-29 13:33:48 +08:00
parent dfa38f6259
commit 7cb32d1380
2 changed files with 29 additions and 14 deletions
+26 -12
View File
@@ -297,6 +297,21 @@ pub(crate) async fn process_tool_calls(
} }
let drafts = resolved_drafts; let drafts = resolved_drafts;
// 自动执行范围三档读取(前置到路径授权预校验之前:两处门控共用)。
// KV df-ai-auto-execute-mode 取值 low/medium/all,默认 low;
// low 仅 Low 自动(等价旧行为),medium Low+Medium,all 全自动无审批(完全 AI 接管)。
let auto_exec_mode = app_handle
.state::<crate::state::AppState>()
.settings
.get("df-ai-auto-execute-mode")
.await
.ok()
.flatten()
// 前端 appSettings.set 以 JSON.stringify 落库(字符串值带引号,"all" != all),
// 不归一则 medium/all 档静默失效全走审批(同 prompt.rs custom_prompt 的 trim 先例)。
.map(|v| v.trim_matches('"').to_string())
.unwrap_or_else(|| "low".to_string());
// ── 文件工具路径授权预校验 ── // ── 文件工具路径授权预校验 ──
// 在 RiskLevel 分类前,对文件工具(read/write/list/patch/info/append/delete/rename/search) // 在 RiskLevel 分类前,对文件工具(read/write/list/patch/info/append/delete/rename/search)
// 逐条预校验路径授权(persistent + 会话 session_allowed_dirs + 黑名单): // 逐条预校验路径授权(persistent + 会话 session_allowed_dirs + 黑名单):
@@ -319,7 +334,16 @@ pub(crate) async fn process_tool_calls(
// handler 闭包 read lock 与此处预校验读同一字段,两端授权判定一致。 // handler 闭包 read lock 与此处预校验读同一字段,两端授权判定一致。
match check_file_tool_auth(&draft.name, &args, &allowed_guard) { match check_file_tool_auth(&draft.name, &args, &allowed_guard) {
FileToolAuthOutcome::Authorized => authorized_drafts.push((idx, draft, args)), FileToolAuthOutcome::Authorized => authorized_drafts.push((idx, draft, args)),
FileToolAuthOutcome::NeedsAuth(req) => path_auth_pending.push((draft, args, req)), FileToolAuthOutcome::NeedsAuth(req) => {
// all 档全自动(用户拍板):路径授权弹窗也不打断,与 RiskLevel 门同语义。
// 黑名单 Denied 不受影响——系统敏感目录是绝对安全线,全接管也不放行。
// 放行的 draft 走 authorized_drafts → RiskLevel 分类 → all 放行 → 审计留痕闭环。
if auto_exec_mode == "all" {
authorized_drafts.push((idx, draft, args));
} else {
path_auth_pending.push((draft, args, req));
}
}
FileToolAuthOutcome::Denied(reason) => path_denied.push((draft, reason)), FileToolAuthOutcome::Denied(reason) => path_denied.push((draft, reason)),
} }
} }
@@ -460,16 +484,6 @@ pub(crate) async fn process_tool_calls(
// 原 Vec 仅 (draft,args),回填审计硬编码 RiskLevel::Low;#97 让 Med/High(all/medium 模式) // 原 Vec 仅 (draft,args),回填审计硬编码 RiskLevel::Low;#97 让 Med/High(all/medium 模式)
// 也进此向量,需透传真实等级,审计方可追溯「all 模式执行了多少高危命令」。 // 也进此向量,需透传真实等级,审计方可追溯「all 模式执行了多少高危命令」。
let mut low_risk: Vec<(ToolCallDraft, serde_json::Value, RiskLevel)> = Vec::new(); 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 接管)。
let auto_exec_mode = app_handle
.state::<crate::state::AppState>()
.settings
.get("df-ai-auto-execute-mode")
.await
.ok()
.flatten()
.unwrap_or_else(|| "low".to_string());
// F-#97 诊断:用户报「选 all 仍审批」时辅助定位 KV 是否真落库。 // F-#97 诊断:用户报「选 all 仍审批」时辅助定位 KV 是否真落库。
// df-ai-trace-approve 开关控制(默认开)。常见误以为已生效但实际未点确认按钮致 KV 未落。 // df-ai-trace-approve 开关控制(默认开)。常见误以为已生效但实际未点确认按钮致 KV 未落。
let trace_on = app_handle let trace_on = app_handle
@@ -479,7 +493,7 @@ pub(crate) async fn process_tool_calls(
.await .await
.ok() .ok()
.flatten() .flatten()
.map(|v| v == "true" || v == "1") .map(|v| { let v = v.trim_matches('"'); v == "true" || v == "1" })
.unwrap_or(true); .unwrap_or(true);
if trace_on { if trace_on {
tracing::info!( tracing::info!(
+3 -2
View File
@@ -121,8 +121,9 @@ pub async fn set_script_safety(
pub async fn get_script_safety( pub async fn get_script_safety(
state: State<'_, AppState>, state: State<'_, AppState>,
) -> Result<(String, String), String> { ) -> Result<(String, String), String> {
let wl = state.settings.get("df-script-whitelist").await.map_err(err_str)?.unwrap_or_default(); // appSettings.set(JSON.stringify 带引号)与 settingsApi.set(裸串)双通道写入并存,读端归一
let bl = state.settings.get("df-script-blacklist").await.map_err(err_str)?.unwrap_or_default(); let wl = state.settings.get("df-script-whitelist").await.map_err(err_str)?.map(|v| v.trim_matches('"').to_string()).unwrap_or_default();
let bl = state.settings.get("df-script-blacklist").await.map_err(err_str)?.map(|v| v.trim_matches('"').to_string()).unwrap_or_default();
Ok((wl, bl)) Ok((wl, bl))
} }