修复: patch_file 小改动自动放行 + tool_result JSON 压缩 + prompt 项目路径
- C-260627: patch_file ≤5 行自动放行,不阻塞 AI 工作流 - tool_result 压缩: JSON 数组截断到 10 条 + _truncated 标记 - system_prompt: 项目清单增加「目录:」路径信息
This commit is contained in:
@@ -341,6 +341,8 @@ pub const TOOL_RESULT_SUMMARIZE_RATIO: f32 = 0.40;
|
|||||||
pub const TOOL_RESULT_HEAD_LINES: usize = 5;
|
pub const TOOL_RESULT_HEAD_LINES: usize = 5;
|
||||||
/// extract_key_info 保留的尾部行数。
|
/// extract_key_info 保留的尾部行数。
|
||||||
pub const TOOL_RESULT_TAIL_LINES: usize = 5;
|
pub const TOOL_RESULT_TAIL_LINES: usize = 5;
|
||||||
|
/// extract_key_info JSON 数组截断上限(防 tool_result 数组过大撑爆 prompt)。
|
||||||
|
pub const TOOL_RESULT_MAX_ARRAY: usize = 10;
|
||||||
|
|
||||||
/// 判断 tool_result 是否需摘要压缩:content >2KB 或 占比 >40%。
|
/// 判断 tool_result 是否需摘要压缩:content >2KB 或 占比 >40%。
|
||||||
///
|
///
|
||||||
@@ -374,6 +376,29 @@ pub fn should_summarize_tool_result(
|
|||||||
///
|
///
|
||||||
/// `tool_name` 仅用于摘要头注释,不参与内容判断。空 content 返回空字符串。
|
/// `tool_name` 仅用于摘要头注释,不参与内容判断。空 content 返回空字符串。
|
||||||
pub fn extract_key_info(content: &str, tool_name: &str) -> String {
|
pub fn extract_key_info(content: &str, tool_name: &str) -> String {
|
||||||
|
// JSON 感知压缩:识别对象中的大数组并截断
|
||||||
|
if let Ok(mut val) = serde_json::from_str::<serde_json::Value>(content) {
|
||||||
|
if let Some(obj) = val.as_object_mut() {
|
||||||
|
let mut truncated = false;
|
||||||
|
for (_key, field) in obj.iter_mut() {
|
||||||
|
if let Some(arr) = field.as_array() {
|
||||||
|
if arr.len() > TOOL_RESULT_MAX_ARRAY {
|
||||||
|
*field = serde_json::Value::Array(
|
||||||
|
arr.iter().take(TOOL_RESULT_MAX_ARRAY).cloned().collect()
|
||||||
|
);
|
||||||
|
truncated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if truncated {
|
||||||
|
obj.insert("_truncated".into(), serde_json::Value::Bool(true));
|
||||||
|
return serde_json::to_string(&val).unwrap_or_else(|_| content.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// JSON 解析成功但无数组超限 → 原样返回(非 JSON 的走下行行数截断)
|
||||||
|
// 注意:不 return,继续行数判断(如 read_file 的 content 字段虽在 JSON 内但实际包含文件全文,行数压缩仍有价值)
|
||||||
|
}
|
||||||
|
|
||||||
let lines: Vec<&str> = content.lines().collect();
|
let lines: Vec<&str> = content.lines().collect();
|
||||||
if lines.is_empty() {
|
if lines.is_empty() {
|
||||||
return String::new();
|
return String::new();
|
||||||
|
|||||||
@@ -506,11 +506,19 @@ pub(crate) async fn process_tool_calls(
|
|||||||
// low(默认):Low→auto, Med/High→审批(等价现状)
|
// low(默认):Low→auto, Med/High→审批(等价现状)
|
||||||
// medium:Low/Med→auto, High→审批
|
// medium:Low/Med→auto, High→审批
|
||||||
// all:全 auto(完全接管,无审批)
|
// all:全 auto(完全接管,无审批)
|
||||||
let should_auto = match risk_level {
|
let mut should_auto = match risk_level {
|
||||||
RiskLevel::Low => true,
|
RiskLevel::Low => true,
|
||||||
RiskLevel::Medium => auto_exec_mode == "medium" || auto_exec_mode == "all",
|
RiskLevel::Medium => auto_exec_mode == "medium" || auto_exec_mode == "all",
|
||||||
RiskLevel::High => auto_exec_mode == "all",
|
RiskLevel::High => auto_exec_mode == "all",
|
||||||
};
|
};
|
||||||
|
// C-260627: patch_file 小改动(≤5 行)自动放行,不阻塞 AI 工作流
|
||||||
|
if !should_auto && draft.name == "patch_file" {
|
||||||
|
if let Some(text) = args.get("new_text").and_then(|v| v.as_str()) {
|
||||||
|
if text.lines().count() <= 5 {
|
||||||
|
should_auto = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if should_auto {
|
if should_auto {
|
||||||
low_risk.push((draft, args, risk_level));
|
low_risk.push((draft, args, risk_level));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -163,6 +163,9 @@ pub(crate) async fn build_system_prompt_with_excluded(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
prompt.push_str(&format!("- {} ({}): {}\n", p.name, p.status, p.description));
|
prompt.push_str(&format!("- {} ({}): {}\n", p.name, p.status, p.description));
|
||||||
|
if let Some(ref dir) = p.path {
|
||||||
|
prompt.push_str(&format!(" 目录: {}\n", dir));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 机制层注明语(中/英):项目已全部列出,降 list_projects 重复调用
|
// 机制层注明语(中/英):项目已全部列出,降 list_projects 重复调用
|
||||||
prompt.push_str(&projects_listed_note(lang, 20));
|
prompt.push_str(&projects_listed_note(lang, 20));
|
||||||
|
|||||||
@@ -1693,7 +1693,7 @@ fn register_file_tools(
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
registry.register(
|
registry.register(
|
||||||
"patch_file", "局部更新文件内容(三模式互斥)。模式1 old_text:精确匹配原文替换(含空格/缩进,CAS 语义)。模式2 replace_lines:按行号区间 {start,end}(1-based 含首尾)替换,不需原文,配 expected_hash 防行号漂移。模式3 anchor:按首尾子串锚点 {start,end}(大小写敏感,子串匹配)定位行号区间替换,不需完整原文。三模式均需 path+new_text,expected_hash 可选通用。属 Medium 风险操作(修改已有文件),需人工审批。注意:若文件已被外部修改,请先重新 read_file 获取最新内容",
|
"patch_file", "局部更新文件内容(三模式互斥)。模式1 old_text:精确匹配原文替换(含空格/缩进,CAS 语义)。模式2 replace_lines:按行号区间 {start,end}(1-based 含首尾)替换,不需原文,配 expected_hash 防行号漂移。模式3 anchor:按首尾子串锚点 {start,end}(大小写敏感,子串匹配)定位行号区间替换,不需完整原文。三模式均需 path+new_text,expected_hash 可选通用。小改动(≤5 行)自动放行不阻塞,大改动需人工审批。注意:若文件已被外部修改,请先重新 read_file 获取最新内容",
|
||||||
patch_file_schema,
|
patch_file_schema,
|
||||||
RiskLevel::Medium,
|
RiskLevel::Medium,
|
||||||
{ let allowed_dirs = allowed_dirs.clone(); Box::new(move |args: serde_json::Value| {
|
{ let allowed_dirs = allowed_dirs.clone(); Box::new(move |args: serde_json::Value| {
|
||||||
|
|||||||
Reference in New Issue
Block a user