修复: 压缩边界对齐与断路器学习语义

- 压缩边界按对话单元回退,不再劈开工具对
- 停止与接管退出补跳过结果,畸形不再新增
- 失败指纹纳入参数,改参重试不再累计熔断
- 成功断连失败段,红绿循环不再误熔断
- 脚本非零退出豁免,同命令死磕由签名熔断兜底
- 熔断前加自纠警示轮,临时路径写删豁免
This commit is contained in:
lxy
2026-10-01 19:35:22 +08:00
parent b8416dea63
commit 4417b3ddd3
4 changed files with 416 additions and 47 deletions
+75 -9
View File
@@ -323,26 +323,47 @@ pub(crate) fn normalize_loop_path(p: &str) -> String {
p.replace('\\', "/").to_lowercase()
}
/// 从工具调用提取回环样本:write_file/delete_file 取 path 参数规范化,其他工具 None 占位。
/// path 缺失/非字符串 → None(无路径无从判同路径,退化为占位)。
/// 从工具调用提取回环样本:write_file/delete_file 记规范化 path;read_file 记规范化 path
/// (该路径的「使用」证据);run_command 记整条命令(判定时按「命令含路径」匹配使用);
/// 其他工具 None 占位。path/command 缺失/非字符串 → None(无从判,退化为占位)。
pub(crate) fn destructive_loop_sample(name: &str, args: &serde_json::Value) -> DestructiveLoopSample {
match name {
"write_file" | "delete_file" => {
let p = arg_str(args, "path");
if p.is_empty() { None } else { Some((name.to_string(), normalize_loop_path(&p))) }
}
_ => None,
}
let target = match name {
"write_file" | "delete_file" | "read_file" => arg_str(args, "path"),
"run_command" => arg_str(args, "command"),
_ => return None,
};
if target.is_empty() { None } else { Some((name.to_string(), normalize_loop_path(&target))) }
}
/// 破坏性回环检测:窗口内「write_file(P) 后 GAP 步内 delete_file(P)」或反向 → 命中路径。
///
/// GAP=6(全工具调用序位):写删交替紧凑才判回环,间隔远的写与删是正常迭代修复不算。
/// 两类豁免(降误伤,治 .trash 临时脚本卫生流程被误熔断):
/// - 路径命中临时/回收模式(.trash、/temp/、/tmp/、/target/、.cache 等):写完即删是
/// 产物卫生,整对豁免;
/// - 写删对之间有对该路径的使用(read 精确命中 / run 命令含路径):用后清理,该对豁免。
/// 真「写完即删用户文件」(非临时路径且中间无使用)仍拦。
/// 返回首个命中对的规范化路径(警示/熔断文本可引用),无命中 → None。
pub(crate) fn detect_destructive_loop(samples: &[DestructiveLoopSample]) -> Option<String> {
/// 回环对最大间隔步数(按全部工具调用序位)。
const DESTRUCTIVE_LOOP_GAP: usize = 6;
// 临时/回收产物路径(入参须为 normalize_loop_path 后的形式:分隔符统一 /、小写)。
let is_temp_recycle = |p: &str| -> bool {
const MARKERS: &[&str] = &[".trash", "/temp/", "/tmp/", "/target/", ".cache"];
MARKERS.iter().any(|m| p.contains(m))
|| p.starts_with("temp/")
|| p.starts_with("tmp/")
};
// 写删对之间对该路径的使用:read 精确命中;run 按命令文本含路径匹配。
let is_use_between = |s: &DestructiveLoopSample, p: &str| -> bool {
match s.as_ref() {
Some((n, v)) if n == "read_file" => v == p,
Some((n, v)) if n == "run_command" => !p.is_empty() && v.contains(p),
_ => false,
}
};
for i in 0..samples.len() {
let (na, pa) = match samples[i].as_ref() {
Some(s) => s,
@@ -353,8 +374,14 @@ pub(crate) fn detect_destructive_loop(samples: &[DestructiveLoopSample]) -> Opti
"delete_file" => "write_file",
_ => continue,
};
if is_temp_recycle(pa) {
continue;
}
// j 限 (i, i+GAP]:紧凑间隔内找反向同路径操作。
for j in (i + 1)..samples.len().min(i + 1 + DESTRUCTIVE_LOOP_GAP) {
if is_use_between(&samples[j], pa) {
break; // 对内有使用=用后清理,该写删对豁免
}
if let Some((nb, pb)) = samples[j].as_ref() {
if pb == pa && nb == opposite {
return Some(pa.clone());
@@ -614,6 +641,45 @@ mod tests {
assert_eq!(detect_destructive_loop(&samples), Some("p.txt".to_string()), "间隔 6 步 = GAP 应命中");
}
/// 豁免一:临时/回收路径(.trash、/tmp/ 等)写删是产物卫生流程,非自毁,不判回环。
#[test]
fn loop_temp_recycle_path_exempted() {
for p in [".trash/analyze.py", "C:/data/tmp/analyze.py", ".cache/scratch.txt"] {
let samples = loop_samples_of(&[
("write_file", serde_json::json!({"path": p, "content": "x"})),
("delete_file", serde_json::json!({"path": p})),
]);
assert_eq!(detect_destructive_loop(&samples), None, "临时路径 {} 写删应豁免", p);
}
}
/// 豁免二:写删对之间有对该路径的成功使用(run/read)是用后清理,非自毁。
#[test]
fn loop_use_between_pair_not_detected() {
let run_between = loop_samples_of(&[
("write_file", serde_json::json!({"path": "scripts/a.py"})),
("run_command", serde_json::json!({"command": "python scripts/a.py"})),
("delete_file", serde_json::json!({"path": "scripts/a.py"})),
]);
assert_eq!(detect_destructive_loop(&run_between), None, "写后运行过再删是用后清理");
let read_between = loop_samples_of(&[
("write_file", serde_json::json!({"path": "scripts/a.py"})),
("read_file", serde_json::json!({"path": "scripts/a.py"})),
("delete_file", serde_json::json!({"path": "scripts/a.py"})),
]);
assert_eq!(detect_destructive_loop(&read_between), None, "写后读过再删是确认后清理");
}
/// 真自毁仍拦:非临时路径写完即删,对内无任何使用。
#[test]
fn loop_bare_write_delete_still_detected() {
let samples = loop_samples_of(&[
("write_file", serde_json::json!({"path": "out.txt", "content": "x"})),
("delete_file", serde_json::json!({"path": "out.txt"})),
]);
assert_eq!(detect_destructive_loop(&samples), Some("out.txt".to_string()), "无使用的写完即删仍判自毁");
}
/// 场景样本不足:仅 3 个签名(即使全同)。判 false(不判)。
#[test]
fn scenario_insufficient_sample_not_repetitive() {