重构: 巨函数拆分 + 清理历史标记注释 + custom_prompt/停止按钮/tunnel 改进
This commit is contained in:
@@ -20,16 +20,16 @@ use super::{AiChatEvent, AiSession, ApprovalKind, PathAuthRequest, PendingApprov
|
||||
mod utils;
|
||||
pub(crate) use utils::{risk_from_str, risk_str, truncate_chars};
|
||||
|
||||
// diff(audit/diff.rs):AE-2025-03 write_file 审批预览 diff 生成。
|
||||
// diff(audit/diff.rs):write_file 审批预览 diff 生成。
|
||||
// 第五批从本文件抽离,行为零变更。
|
||||
mod diff;
|
||||
|
||||
// path_auth(audit/path_auth.rs):F-260619-03 Phase B/C 路径授权预校验。
|
||||
// path_auth(audit/path_auth.rs):路径授权预校验。
|
||||
// 第六批从本文件抽离,行为零变更。pub(super) use 供 tests 子模块引用 + process_tool_calls 裸名调用。
|
||||
mod path_auth;
|
||||
pub(super) use path_auth::{check_file_tool_auth, FileToolAuthOutcome};
|
||||
|
||||
// approval(audit/approval.rs):F-#97/AE-04/阶段4 审批门控逻辑。
|
||||
// approval(audit/approval.rs):审批门控逻辑。
|
||||
// 第六批从本文件抽离,行为零变更。use 保持 process_tool_calls 裸名调用。
|
||||
mod approval;
|
||||
use approval::{detect_retry_count, handle_approval_tool};
|
||||
@@ -63,8 +63,8 @@ pub use restore::restore_pending_approvals;
|
||||
mod finalize;
|
||||
pub(crate) use finalize::audit_finalize;
|
||||
|
||||
// cache(audit/cache.rs):F-260616-05 高危工具去重缓存 + BUG-2026-07-07/P0-2 只读工具缓存。
|
||||
// 第三批从本文件抽离,行为零变更(P0-2 为新增能力)。
|
||||
// cache(audit/cache.rs):高危工具去重缓存 + 只读工具缓存。
|
||||
// 第三批从本文件抽离,行为零变更。
|
||||
mod cache;
|
||||
pub(super) use cache::{find_cached_readonly_result, pending_placeholder_for};
|
||||
|
||||
@@ -74,7 +74,7 @@ pub(super) use cache::{find_cached_readonly_result, pending_placeholder_for};
|
||||
mod data_change;
|
||||
pub(crate) use data_change::emit_data_changed;
|
||||
|
||||
// idea_source(audit/idea_source.rs):F-260619-04 P2(方案 B)灵感来源消息级溯源补全。
|
||||
// idea_source(audit/idea_source.rs):灵感来源消息级溯源补全。
|
||||
// create_idea 工具执行后,若 AI 未填 source 且有 message_id → 补 conv_msg:{id}(低侵入,不改 handler 接口)。
|
||||
// pub(crate) use 供本文件 process_tool_calls + chat.rs 审批执行路径调用(单点逻辑,多调用点)。
|
||||
mod idea_source;
|
||||
@@ -89,7 +89,7 @@ pub(crate) use idea_source::maybe_fill_idea_source;
|
||||
/// 工具执行 + 心跳保活:execute 期间每 30s emit AiHeartbeat(对齐 stream_recv.rs
|
||||
/// stream_llm select! 心跳语义),execute 完 abort 心跳 task。
|
||||
///
|
||||
/// 根治 BUG-260624-03:工具执行在 stream_llm 之外(本模块),原本无 AiHeartbeat。
|
||||
/// 根治:工具执行在 stream_llm 之外(本模块),原本无 AiHeartbeat。
|
||||
/// 单次 execute 超过前端 STREAM_TIMEOUT_MS(130s)——bash 跑 cargo/测试、read 大文件、
|
||||
/// 全盘 search 等开发长命令——前端 watchdog 误判断流,抛"工具已执行完成后续中断"误报
|
||||
/// (实测:用户报"一边流一边抛",前一轮 delta 文本在屏 + 当前轮工具执行静默 > 130s)。
|
||||
@@ -137,7 +137,7 @@ async fn execute_with_heartbeat(
|
||||
});
|
||||
// RAII:execute 无论 Ok/Err/panic(unwind),_guard drop 自动 stop+abort,无心跳 task 泄漏。
|
||||
let _guard = HeartbeatGuard { stop, handle: heartbeat };
|
||||
// BUG-2026-07-19: tools.execute 无 timeout 时,卡死工具(run_command 长命令/read_file 大文件/同步
|
||||
// tools.execute 无 timeout 时,卡死工具(run_command 长命令/read_file 大文件/同步
|
||||
// 阻塞工具)永久挂起 → process_tool_calls 持 session lock 永久 → guard.reset 等 lock → AiCompleted
|
||||
// 永不发 → 前端"回答完卡住/超时清空"。60s timeout 兜底:超时返错误 tool_result,锁释放,loop 续跑。
|
||||
// 心跳 30s 续命前端 watchdog,60s timeout 覆盖绝大多数工具(run_command 已自带 10s 子超时)。
|
||||
@@ -164,26 +164,26 @@ pub(crate) async fn process_tool_calls(
|
||||
) -> usize {
|
||||
let mut tc_list: Vec<_> = tool_calls_acc.into_iter().collect();
|
||||
tc_list.sort_unstable_by_key(|(i, _)| *i);
|
||||
// B-260616-21 治本兜底:LLM 异常复用同 tool_use.id(stream_recv 按 content_block index 分桶,
|
||||
// 治本兜底:LLM 异常复用同 tool_use.id(stream_recv 按 content_block index 分桶,
|
||||
// 同 id 不同 index draft 可并存 → 每 draft emit AiToolCallStarted 致同 id emit 两次 → 前端 push 两卡,
|
||||
// Completed 按 id 只 update 首张 → 次张残留 running 0行)。process 层按 id 去重——同 id 保留
|
||||
// 最小 index 的首个,丢弃后续,保证 emit Started 的 id 唯一。前端 useAiEvents.ts:205 findToolCall
|
||||
// 守卫双保险。详 docs/02-架构设计/已编号方案/B-260616-21排查方案-2026-06-16.md。
|
||||
// 守卫双保险。
|
||||
let mut seen_ids: HashSet<String> = HashSet::new();
|
||||
tc_list.retain(|(_, draft)| seen_ids.insert(draft.id.clone()));
|
||||
let mut pending_count = 0usize;
|
||||
let audit_repo = AiToolExecutionRepo::new(db);
|
||||
|
||||
// F-260619-04 P1 消息级溯源:取当前 assistant 消息 id。
|
||||
// P1 消息级溯源:取当前 assistant 消息 id。
|
||||
// 调用前 agentic/mod.rs 已把本轮 assistant_with_tools 消息(LLM 返回带 tool_calls 的那条)
|
||||
// push 到 per_conv.messages(audit/mod.rs:882),此处取末条 assistant id 作为本轮工具
|
||||
// 调用所属的溯源 message_id,贯穿所有 audit_tool_call 写入。None 表示无 assistant 消息
|
||||
// (异常路径/老数据无 id),audit 落 message_id=None,展示侧兼容。
|
||||
//
|
||||
// BUG-260624-03/P0 重构:短 lock 段读 current_message_id(纯读,无 await),clone 出来后续传参。
|
||||
// 短 lock 段读 current_message_id(纯读,无 await),clone 出来后续传参。
|
||||
// 整个 process_tool_calls 不再要求调用方持锁,内部所有 session.xxx 访问均短 lock 段,
|
||||
// 慢操作(execute_with_heartbeat/audit_tool_call/detect_retry_count/DB 查)全在锁外 await。
|
||||
// BUG-2026-07-19: current_message_id 读加 200ms timeout(防 session lock 竞争卡死致
|
||||
// current_message_id 读加 200ms timeout(防 session lock 竞争卡死致
|
||||
// process_tool_calls 进不去 emit AiToolCallStarted → 前端工具卡片不呈现 + 45s 看门狗超时)。
|
||||
// 超时用 None(溯源降级,非致命)。治本:消除 session lock 长持有(save clone 段已优化)。
|
||||
let current_message_id: Option<String> = match tokio::time::timeout(
|
||||
@@ -226,7 +226,7 @@ pub(crate) async fn process_tool_calls(
|
||||
})
|
||||
.collect();
|
||||
|
||||
// ── F-260619-03 Phase B/C:文件工具路径授权预校验 ──
|
||||
// ── 文件工具路径授权预校验 ──
|
||||
// 在 RiskLevel 分类前,对文件工具(read/write/list/patch/info/append/delete/rename/search)
|
||||
// 逐条预校验路径授权(persistent + 会话 session_allowed_dirs + 黑名单):
|
||||
// - 任一路径命中黑名单 → Denied:push 错误 tool_result + emit Completed,不挂起(Phase C)
|
||||
@@ -257,7 +257,7 @@ pub(crate) async fn process_tool_calls(
|
||||
// Phase C: Denied 路径 → 硬拒(push 错误 tool_result + emit Completed),不挂起 loop。
|
||||
// 工具返 Err 让 LLM 知路径被禁,自行调整;loop 继续下一轮(不暂停)。
|
||||
for (draft, reason) in path_denied {
|
||||
// BUG-2026-07-07/P1-3:拒绝消息改结构化 JSON,防裸文本破坏 tool role 消息协议
|
||||
// 拒绝消息改结构化 JSON,防裸文本破坏 tool role 消息协议
|
||||
let err_msg = serde_json::json!({
|
||||
"status": "rejected",
|
||||
"reason": "path_blacklist",
|
||||
@@ -294,7 +294,7 @@ pub(crate) async fn process_tool_calls(
|
||||
// 复用审批挂起架构:pending_approvals 以 tool_call_id 为键,ai_authorize_dir IPC remove 后恢复。
|
||||
// pending_count 计入(让 agentic loop 检测到挂起并暂停,等 ai_authorize_dir → try_continue 恢复)。
|
||||
//
|
||||
// BUG-260624-03/P0 重构:detect_retry_count(DB 读)/emit/audit_tool_call(DB 写)均在锁外,
|
||||
// detect_retry_count(DB 读)/emit/audit_tool_call(DB 写)均在锁外,
|
||||
// 仅 push tool_result + pending_approvals.insert 两处纯写改短 lock 段。
|
||||
for (draft, args, req) in path_auth_pending {
|
||||
pending_count += 1;
|
||||
@@ -304,7 +304,7 @@ pub(crate) async fn process_tool_calls(
|
||||
.map(|d| d.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
let path_str = req.raw_paths.first().cloned().unwrap_or_default();
|
||||
// 锁外:阶段4(容错/恢复,开关 df-ai-approval-retry):同 tc_id 重试检测。
|
||||
// 锁外:(容错/恢复,开关 df-ai-approval-retry):同 tc_id 重试检测。
|
||||
let retry_count = detect_retry_count(&audit_repo, &draft.id).await;
|
||||
if retry_count >= 1 {
|
||||
let skip_msg = format!(
|
||||
@@ -348,7 +348,7 @@ pub(crate) async fn process_tool_calls(
|
||||
arguments: args.clone(),
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
recovered: false,
|
||||
// 阶段3a:路径授权挂起标 kind=Path(req)(下沉原 path_auth 字段)。
|
||||
// 路径授权挂起标 kind=Path(req)(下沉原 path_auth 字段)。
|
||||
kind: ApprovalKind::Path(req),
|
||||
retry_count,
|
||||
created_at: Some(std::time::SystemTime::now()),
|
||||
@@ -381,7 +381,7 @@ pub(crate) async fn process_tool_calls(
|
||||
|
||||
// 分类:Low 收集并行执行,Med/High 立即进审批门控(push 占位 tool_result)
|
||||
//
|
||||
// F-260616-05:High risk 在进审批门前先查去重缓存(find_cached_high_risk_result)。
|
||||
// High risk 在进审批门前先查去重缓存(find_cached_high_risk_result)。
|
||||
// 若 LLM 重试同命令(同 tool_name + 同 args,键序无关),命中已落定的旧 tool_result,
|
||||
// 把缓存结果作为新 tool_call_id 的 tool_result 回传 LLM,跳过 insert pending + 跳过审批,
|
||||
// 断「超时→重试→重新审批」循环。Med 不去重(去重易误伤),Low 无审批本就不进此分支。
|
||||
@@ -431,8 +431,8 @@ pub(crate) async fn process_tool_calls(
|
||||
).await;
|
||||
}
|
||||
|
||||
// AE-04 trust-hit 并行执行:execute + 即时 emit 在闭包内(闭包不访问 session,
|
||||
// BUG-260624-03/P0 重构后调用方不再持锁,本段所有 await 完全在锁外),
|
||||
// trust-hit 并行执行:execute + 即时 emit 在闭包内(闭包不访问 session,
|
||||
// 调用方不再持锁,本段所有 await 完全在锁外),
|
||||
// push tool_result / audit 在 join_all 后串行回填(短 lock push + 锁外 audit)。对齐 Low risk 并行模式。
|
||||
// CR-51 修:原 inline .await execute 串行执行每个工具(阻塞期间锁被持有,run_command 慢命令
|
||||
// 阻塞同会话 IPC);改 join_all 并行多工具减少总阻塞时间。P0 重构后锁外并行,根本消除阻塞。
|
||||
@@ -503,13 +503,13 @@ pub(crate) async fn process_tool_calls(
|
||||
// push tool_result / audit 在 join_all 后串行回填(短 lock push + 锁外 audit,与 Med/High 占位拼接)。
|
||||
// join_all 保序——结果顺序 = low_risk 输入顺序 = tc_list 原始 index 顺序,不额外 sort
|
||||
if !low_risk.is_empty() {
|
||||
// BUG-2026-07-07/P0-2:只读幂等工具去重缓存(治 LLM 死循环重调)。
|
||||
// 只读幂等工具去重缓存(治 LLM 死循环重调)。
|
||||
// 实测 9357c27c 会话:LLM 对 read_file(application.xml) 连调 17 次、search_files 连调 13 次,
|
||||
// 每次返回几乎相同结果却反复重调,是 prompt 64 万的直接元凶。此处先查会话内是否已对
|
||||
// 同参只读工具成功执行过,命中则直接回填缓存结果跳过真执行,断 LLM 失忆死循环。
|
||||
// 安全边界见 find_cached_readonly_result 文档(仅白名单只读工具 + 仅 completed 成功结果)。
|
||||
//
|
||||
// BUG-260624-03/P0 重构:find_cached_readonly_result 内部短 lock + 锁外 DB 查,本段不持锁。
|
||||
// find_cached_readonly_result 内部短 lock + 锁外 DB 查,本段不持锁。
|
||||
let mut low_risk_uncached: Vec<(ToolCallDraft, serde_json::Value, RiskLevel)> = Vec::with_capacity(low_risk.len());
|
||||
for (draft, args, risk_level) in low_risk {
|
||||
let cached = find_cached_readonly_result(session_arc, conv_id, &audit_repo, &draft.name, &args).await;
|
||||
@@ -595,7 +595,7 @@ pub(crate) async fn process_tool_calls(
|
||||
Ok(c) => ("completed", c),
|
||||
Err(c) => ("failed", c),
|
||||
};
|
||||
// F-260619-04 P2(方案 B):create_idea source 消息级溯源补全(仅 source 空 + 有 message_id)。
|
||||
// source 消息级溯源补全(仅 source 空 + 有 message_id)。
|
||||
// 注:低风险路径目前 create_idea 不会进(Medium→pending),此处为防御/未来若调级别覆盖。
|
||||
// 仅 Ok(completed) 时补(失败 result 无 idea_id 意义);args 从 draft.args 反解(JSON 原样)。
|
||||
if status == "completed" {
|
||||
@@ -625,7 +625,7 @@ pub(crate) async fn process_tool_calls(
|
||||
}
|
||||
}
|
||||
|
||||
// 阶段3a:单表 pending_approvals 统计 pending 总数(path/risk 合一,kind 区分)。
|
||||
// 单表 pending_approvals 统计 pending 总数(path/risk 合一,kind 区分)。
|
||||
// 短 lock 段:读 pending_approvals 统计(纯读,无 await)
|
||||
let (path_count, risk_count, pending_approvals_len) = {
|
||||
let session = session_arc.lock().await;
|
||||
@@ -648,7 +648,7 @@ mod tests {
|
||||
|
||||
/// grep 走单路径授权申请路径(NeedsAuth),非 search_files 盲拒(Denied)。
|
||||
///
|
||||
/// F-260621:grep 加入 extract_file_tool_paths 单路径分支,未授权路径触发
|
||||
/// grep 加入 extract_file_tool_paths 单路径分支,未授权路径触发
|
||||
/// AiDirAuthRequired 申请(对齐 read_file),不像 search_files 被硬拒。
|
||||
/// 锁定此差异:grep 与 read_file 同款授权弹窗语义。
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user