优化: 狗粮短板收口与patch_file增强

- patch_file old_text 精确未中时空白归一化回退(行首缩进归一+多空白折叠,唯一命中按整行区间替换,warning 注明)
- patch_file anchor end 唯一性校验:多处命中报错并列前两处位置,治实测×2 锚点错位;三模式同传按优先级仲裁(anchor>old_text>replace_lines),warning 注明
- tool_result 压缩 memoization:PerConvState 加 tool_compress_cache(内容哈希→压缩结果),主 loop+子代理双调用点接线,治 369 组同内容反复重压
- 标题采样改两段式:user 首条必进锚定主题+其余按序补足 6 条,治 assistant 首句被当主题
- prompt 注入文件读取纪律段(父会话+子代理同源):read_file 分页/search 直读,禁 run_command 切片读文件;read_file 工具描述与 truncated 提示补大 limit 建议
- 顺带 DRY#1:agentic/mod.rs 三处裸 emit 换正典双写(心跳/破坏性回环熔断/stall 熔断),miniapp 跨端盲区补齐
This commit is contained in:
lxy
2026-10-05 00:35:04 +08:00
parent c83ea0c315
commit 6a50193b34
8 changed files with 380 additions and 79 deletions
+22 -12
View File
@@ -1668,20 +1668,24 @@ async fn prepare_round_inputs(
// 压缩决策经 per_conv.tool_view_decisions 冻结:首次判定后沿用,不再随阈值翻转
// (防同一条历史跨轮在全文/摘要间翻转致前缀断)。写回仅在 conv 存活时做(防惰性建)。
let messages: Vec<ChatMessage> = if TOOL_RESULT_COMPRESS_ENABLED {
let (history_tokens_snapshot, mut view_decisions) = {
let (history_tokens_snapshot, mut view_decisions, mut compress_cache) = {
let session = session_arc.lock().await;
let conv = session.conv_read(conv_id);
(
conv.map(|c| c.messages.history_tokens()).unwrap_or(0),
conv.map(|c| c.tool_view_decisions.clone()).unwrap_or_default(),
conv.map(|c| c.tool_compress_cache.clone()).unwrap_or_default(),
)
};
let messages =
summarize_tool_results(messages, history_tokens_snapshot, &mut view_decisions, conv_id, iteration);
if !view_decisions.is_empty() {
let messages = summarize_tool_results(
messages, history_tokens_snapshot, &mut view_decisions, &mut compress_cache, conv_id, iteration,
);
if !view_decisions.is_empty() || !compress_cache.is_empty() {
let mut session = session_arc.lock().await;
if session.per_conv.contains_key(conv_id) {
session.conv(conv_id).tool_view_decisions = view_decisions;
let conv = session.conv(conv_id);
conv.tool_view_decisions = view_decisions;
conv.tool_compress_cache = compress_cache;
}
}
messages
@@ -2068,8 +2072,9 @@ async fn heartbeat_loop(
_ = heartbeat_rx.changed() => break,
_ = tokio::time::sleep(std::time::Duration::from_secs(20)) => {
last_heartbeat.store(df_types::now_millis() as u64, std::sync::atomic::Ordering::SeqCst);
let _ = app.emit(
"ai-chat-event",
// DRY #1:双写正典(miniapp 端靠心跳判活,只 emit 端看不到)
emit_ai_chat_event(
app,
AiChatEvent::AiHeartbeat { conversation_id: Some(conv_id.to_string()) },
);
}
@@ -2144,7 +2149,8 @@ fn emit_circuit_breaker_tripped(app_handle: &AppHandle, conv_id: &str, max_count
conversation_id: Some(conv_id.to_string()),
}
};
let _ = app_handle.emit("ai-chat-event", ev);
// DRY #1(2026-10-04):裸 emit 换正典双写——原只 emit 未 publish,miniapp 端收不到熔断事件。
emit_ai_chat_event(app_handle, ev);
// P1 bg 化:熔断需人工介入,系统通知补位(条件发送,失败静默)
crate::commands::ai::notify::notify_session_event(
app_handle,
@@ -2277,8 +2283,9 @@ async fn check_stall_breaker(
"[ai] G2 破坏性回环熔断:连续 {} 轮检测到同路径写删回环,疑似自毁循环停止", new_loop_count
);
let path_desc = loop_hit_path.as_deref().unwrap_or("未知路径");
let _ = app_handle.emit(
"ai-chat-event",
// DRY #1:双写正典(熔断求助卡跨端可见)
emit_ai_chat_event(
app_handle,
AiChatEvent::AiHelpRequired {
reason: format!("连续 {} 轮检测到对同一文件({})反复写删,疑似「写完就删」自毁循环已停止", new_loop_count, path_desc),
context: "写文件后又删除同一文件的紧凑交替,可能在反复销毁自己的工作成果而无进展。".into(),
@@ -2307,8 +2314,9 @@ async fn check_stall_breaker(
sample_size = signatures.len(),
"[ai] G2 探索熔断:连续 {} 次检测到重复工具调用,疑似卡住停止", new_count
);
let _ = app_handle.emit(
"ai-chat-event",
// DRY #1:双写正典(熔断求助卡跨端可见)
emit_ai_chat_event(
app_handle,
AiChatEvent::AiHelpRequired {
reason: format!("连续 {} 次检测到重复的工具调用(同样的 grep/读文件反复),疑似卡住已停止", new_count),
context: "工具调用签名高度重复,可能在反复做同一件事而无进展。".into(),
@@ -2572,6 +2580,8 @@ pub(crate) async fn emit_ai_completed_once(
emit_ai_chat_event(app_handle, ev);
return;
}
// 有意不走 emit_ai_chat_event(emit/publish 两端 incomplete 标志分叉,见上注释)——
// 此分支语义就是单路 emit 不 publish,裸写是正确形态,勿"收口"。
let _ = app_handle.emit("ai-chat-event", ev);
if do_publish {
let _ = app_handle.state::<AppState>().ai_event_bus.publish_event(