diff --git a/src-tauri/src/commands/ai/audit.rs b/src-tauri/src/commands/ai/audit.rs index 7dda5de..8c31226 100644 --- a/src-tauri/src/commands/ai/audit.rs +++ b/src-tauri/src/commands/ai/audit.rs @@ -1,6 +1,6 @@ //! 工具调用审计 + pending 审批恢复 + 工具调用处理 -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; use serde::Serialize; @@ -531,6 +531,13 @@ 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 分桶, + // 同 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 = HashSet::new(); + tc_list.retain(|(_, draft)| seen_ids.insert(draft.id.clone())); let mut pending_count = 0usize; let audit_repo = AiToolExecutionRepo::new(db);