修复: DeepSeek 400 全量扫描 + 队列 per-conv 隔离
- openai_compat: 扫描所有 assistant 消息剥离 orphan tool_calls(原仅查末条) - queue 加 conversationId 字段,按会话精准 drain - regenerate/editMessage 只清本会话排队消息 - newConversation 保留旧会话排队消息 - AiError 只清出错会话的队列项
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use df_ai::ai_tools::{AiToolRegistry, RiskLevel};
|
||||
use df_ai::provider::ChatMessage;
|
||||
@@ -91,20 +92,24 @@ pub(super) fn should_auto_for_persona(
|
||||
///
|
||||
/// 首批信任工具:write_file / run_command。同会话已批准过同工具+同目录 →
|
||||
/// `TrustKey` 命中,返回 `Some(TrustKey)`;否则返回 `None`(走原审批流程)。
|
||||
pub(super) fn check_trust_hits(
|
||||
///
|
||||
/// BUG-260624-03/P0 重构:签名改 `session_arc: &Arc<Mutex<AiSession>>`,内部短 lock 读
|
||||
/// `session_trust` 后立即 drop,信任查询不持锁,与 process_tool_calls 持锁 await 反模式解耦。
|
||||
pub(super) async fn check_trust_hits(
|
||||
draft: &ToolCallDraft,
|
||||
args: &serde_json::Value,
|
||||
session: &AiSession,
|
||||
session_arc: &Arc<Mutex<AiSession>>,
|
||||
conv_id: &str,
|
||||
) -> Option<TrustKey> {
|
||||
trust_key_for(&draft.name, args)
|
||||
.and_then(|key| {
|
||||
if session.conv_read(conv_id).map(|c| c.session_trust.contains(&key)).unwrap_or(false) {
|
||||
Some(key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
let key = trust_key_for(&draft.name, args)?;
|
||||
// 短 lock 读 session_trust(仅 contains 判定,无 await,纳秒级),命中即返 key
|
||||
let hit = {
|
||||
let session = session_arc.lock().await;
|
||||
session.conv_read(conv_id)
|
||||
.map(|c| c.session_trust.contains(&key))
|
||||
.unwrap_or(false)
|
||||
};
|
||||
if hit { Some(key) } else { None }
|
||||
}
|
||||
|
||||
/// 插入 pending 审批:生成 diff → 重试检测 → insert PendingApproval + 占位 tool_result
|
||||
@@ -112,11 +117,15 @@ pub(super) fn check_trust_hits(
|
||||
///
|
||||
/// **注意**:调用方应在调用前先 +=1 `pending_count`(保持与原 `handle_approval_tool`
|
||||
/// 行为一致——重试 skip 分支也在 `pending_count += 1` 之后返回)。
|
||||
///
|
||||
/// BUG-260624-03/P0 重构:签名改 `session_arc: &Arc<Mutex<AiSession>>`,所有慢操作
|
||||
/// (build_write_file_diff/detect_retry_count/build_approval_reason/audit_tool_call)
|
||||
/// 在锁外 await,仅 `pending_approvals.insert` + `messages.push` 两处纯写改短 lock 段。
|
||||
pub(super) async fn insert_pending_approval(
|
||||
draft: ToolCallDraft,
|
||||
args: serde_json::Value,
|
||||
risk_level: RiskLevel,
|
||||
session: &mut AiSession,
|
||||
session_arc: &Arc<Mutex<AiSession>>,
|
||||
conv_id: &str,
|
||||
audit_repo: &AiToolExecutionRepo,
|
||||
app_handle: &AppHandle,
|
||||
@@ -144,7 +153,11 @@ pub(super) async fn insert_pending_approval(
|
||||
"已跳过重试(同 tool_call_id={} 此前已审批执行过,防 LLM 死循环重试同卡死工具)",
|
||||
draft.id
|
||||
);
|
||||
session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, &skip_msg));
|
||||
// 短 lock 段:push tool_result(纯写,无 await)
|
||||
{
|
||||
let mut session = session_arc.lock().await;
|
||||
session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, &skip_msg));
|
||||
}
|
||||
// L3 emit 双写:重试 guard 跳过 emit Completed 双路发布。
|
||||
let ev = AiChatEvent::AiToolCallCompleted {
|
||||
id: draft.id.clone(),
|
||||
@@ -157,19 +170,24 @@ pub(super) async fn insert_pending_approval(
|
||||
return;
|
||||
}
|
||||
|
||||
session.pending_approvals.insert(draft.id.clone(), PendingApproval {
|
||||
tool_call_id: draft.id.clone(),
|
||||
tool_name: draft.name.clone(),
|
||||
arguments: args.clone(),
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
recovered: false,
|
||||
// 阶段3a:普通 RiskLevel 审批标 kind=Risk{diff}(下沉原 diff 字段)。
|
||||
kind: ApprovalKind::Risk { diff: approval_diff.clone() },
|
||||
retry_count,
|
||||
created_at: Some(std::time::SystemTime::now()),
|
||||
});
|
||||
// 阶段2:占位带 __PENDING__:tc_id 标记,供 sanitize 豁免保留 + 出口断言自愈(防 400 orphan)
|
||||
session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, &pending_placeholder_for(&draft.id)));
|
||||
// 短 lock 段:纯写 pending_approvals.insert + 占位 tool_result push
|
||||
{
|
||||
let mut session = session_arc.lock().await;
|
||||
session.pending_approvals.insert(draft.id.clone(), PendingApproval {
|
||||
tool_call_id: draft.id.clone(),
|
||||
tool_name: draft.name.clone(),
|
||||
arguments: args.clone(),
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
recovered: false,
|
||||
// 阶段3a:普通 RiskLevel 审批标 kind=Risk{diff}(下沉原 diff 字段)。
|
||||
kind: ApprovalKind::Risk { diff: approval_diff.clone() },
|
||||
retry_count,
|
||||
created_at: Some(std::time::SystemTime::now()),
|
||||
});
|
||||
// 阶段2:占位带 __PENDING__:tc_id 标记,供 sanitize 豁免保留 + 出口断言自愈(防 400 orphan)
|
||||
session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, &pending_placeholder_for(&draft.id)));
|
||||
}
|
||||
// 慢操作锁外:拼 reason(DB 读)+ emit + 审计落 pending 纪录(DB 写)
|
||||
let reason = build_approval_reason(&draft.name, &args, risk_level, db).await;
|
||||
// L3 emit 双写:Med/High 风险审批挂起 AiApprovalRequired 双路发布(tunnel 透传 miniapp 弹审批窗)。
|
||||
let ev = AiChatEvent::AiApprovalRequired {
|
||||
@@ -225,13 +243,16 @@ pub(super) async fn detect_retry_count(audit_repo: &AiToolExecutionRepo, tc_id:
|
||||
/// 2. 否则走审批分支:会话信任(`check_trust_hits`)→ F-05 高危去重缓存 → 阶段4 重试 guard →
|
||||
/// `insert_pending_approval`(write_file diff + 挂起 + emit + 审计落 pending 记录)。
|
||||
///
|
||||
/// **不持 session 锁**:调用方(process_tool_calls)在持锁循环内调用本函数。
|
||||
/// BUG-260624-03/P0 重构:签名改 `session_arc: &Arc<Mutex<AiSession>>`,所有 session 访问
|
||||
/// 都改短 lock 段(check_trust_hits/find_cached_high_risk_result 内部短 lock,命中后 push
|
||||
/// tool_result 短 lock 段)。慢操作(audit_tool_call/find_cached_high_risk_result 的 DB 查)
|
||||
/// 全在锁外 await,根治 process_tool_calls 持 session lock 期间 await 慢操作死锁反模式。
|
||||
pub(super) async fn handle_approval_tool(
|
||||
draft: ToolCallDraft,
|
||||
args: serde_json::Value,
|
||||
risk_level: RiskLevel,
|
||||
auto_exec_mode: &str,
|
||||
session: &mut AiSession,
|
||||
session_arc: &Arc<Mutex<AiSession>>,
|
||||
conv_id: &str,
|
||||
_tools_arc: &Arc<AiToolRegistry>,
|
||||
audit_repo: &AiToolExecutionRepo,
|
||||
@@ -254,7 +275,7 @@ pub(super) async fn handle_approval_tool(
|
||||
// 命中后走与 F-05 去重命中相似的「直接执行 + Completed + 审计 decided_by=auto_trust」路径,
|
||||
// 但与 F-05 不同:F-05 复用缓存 tool_result 跳过执行;trust 放行**真实执行工具**
|
||||
// (用户信任同目录同类操作,但仍要看每次的真实结果)。
|
||||
if let Some(key) = check_trust_hits(&draft, &args, session, conv_id) {
|
||||
if let Some(key) = check_trust_hits(&draft, &args, session_arc, conv_id).await {
|
||||
let dir_label = match &key {
|
||||
TrustKey::Write { dir } | TrustKey::Execute { dir } => dir.clone(),
|
||||
};
|
||||
@@ -282,14 +303,20 @@ pub(super) async fn handle_approval_tool(
|
||||
|
||||
// ── Step 3: F-05 高危去重缓存(仅 High) ──
|
||||
if matches!(risk_level, RiskLevel::High) {
|
||||
if let Some((cached, status)) = find_cached_high_risk_result(session, conv_id, audit_repo, &draft.name, &args).await {
|
||||
// find_cached_high_risk_result 内部短 lock 读 messages + 锁外 await DB 查 status,
|
||||
// 返回 (cached_content, status) 时不持锁
|
||||
if let Some((cached, status)) = find_cached_high_risk_result(session_arc, conv_id, audit_repo, &draft.name, &args).await {
|
||||
// 命中:把缓存结果作为新 tool_call_id 的 tool_result 回传,跳过审批
|
||||
tracing::info!(
|
||||
tool = %draft.name,
|
||||
new_tool_call_id = %draft.id,
|
||||
"[F-05] 高危工具去重命中:LLM 重试同命令,复用缓存结果跳过审批(断循环)"
|
||||
);
|
||||
session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, &cached));
|
||||
// 短 lock 段:push tool_result(纯写,无 await)
|
||||
{
|
||||
let mut session = session_arc.lock().await;
|
||||
session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, &cached));
|
||||
}
|
||||
// L3 emit 双写:高危去重命中复用缓存 emit Completed 双路发布。
|
||||
let ev = AiChatEvent::AiToolCallCompleted {
|
||||
id: draft.id.clone(),
|
||||
@@ -307,7 +334,7 @@ pub(super) async fn handle_approval_tool(
|
||||
// ── Step 4: 插入 pending 审批 ──
|
||||
*pending_count += 1;
|
||||
insert_pending_approval(
|
||||
draft, args, risk_level, session, conv_id, audit_repo, app_handle, db,
|
||||
draft, args, risk_level, session_arc, conv_id, audit_repo, app_handle, db,
|
||||
current_message_id,
|
||||
).await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user