修复: aichat P2确定性bug批(audit吞错落日志+switch/delete占位终态化)

finalize: audit_tool_call insert 用 let _ = 吞 DB 错改 match Err tracing::error(对齐 audit_finalize:82模式,控制流不变不阻断,审计写失败落日志可确诊)
conversation+chat: switch/delete retain pending 前补 finalize_pending_placeholders 占位终态化(对齐 stop/clear/force_send,防占位 tool_result 残留下次发送喂 LLM);chat.rs finalize_pending_placeholders 可见性 fn→pub(crate)
cargo check --workspace 零错
This commit is contained in:
2026-06-24 17:18:54 +08:00
parent 7f6aa1e782
commit a7dbd50a4d
3 changed files with 20 additions and 3 deletions

View File

@@ -32,7 +32,7 @@ pub(crate) async fn audit_tool_call(
message_id: Option<&str>,
) {
let executed_at = if decided_by.is_some() { Some(now_millis()) } else { None };
let _ = repo
if let Err(e) = repo
.insert(AiToolExecutionRecord {
id: new_id(),
conversation_id: Some(conv_id.to_string()),
@@ -51,7 +51,16 @@ pub(crate) async fn audit_tool_call(
executed_at,
decided_by: decided_by.map(|s| s.to_string()),
})
.await;
.await
{
tracing::error!(
"audit_tool_call: 写审计记录失败(conv={}, tool_call_id={}, tool={}): {}",
conv_id,
tool_call_id,
tool_name,
e
);
}
}
/// 审批后更新审计记录状态(按 tool_call_id 定位 pending 记录,回填 status/decided_by=human/executed_at/result

View File

@@ -51,7 +51,7 @@ use super::super::{AiChatEvent, ApprovalKind, SessionState};
/// 终态化策略:每条 pending 审批按其自身 `conversation_id` 终态化到对应 conv 的 per_conv.messages;
/// conv_id 入参作 fallback(审批无 conversation_id 的无主审批 R-9 异常数据,终态化到入参 conv,
/// 入参也空则跳过——审计仍记,占位不残留内存因 pending 即将被 clear/retain)。
fn finalize_pending_placeholders(session: &mut super::super::AiSession, conv_id: &str, final_text: &str) {
pub(crate) fn finalize_pending_placeholders(session: &mut super::super::AiSession, conv_id: &str, final_text: &str) {
// SW-260618-02: 先 clone pending 的 tool_call_id(借用在此结束),再可变借 messages。
// 必须整体借 &mut session 在函数体内做 disjoint field borrow —— 调用方若分别传
// &mut messages + &pending 两个引用,函数参数列表不做 disjoint 推断会触发 E0502(2026-06-18 主代修)。

View File

@@ -312,6 +312,10 @@ pub async fn ai_conversation_switch(
// 仅清空目标对话自身的挂起审批,保留其他对话的(防 init 重建的内存 HashMap 被清空,
// 重启恢复链路:restore_pending_approvals(init 重建) → switchConversation(此处不清目标对话的)
// → ai_pending_tool_calls 查询 → ai_approve 落库)。
// SW-260618-02(7-ipc-approval 配套 stop/clear/force_send 终态化):切走目标 conv 前,
// 先把其 pending 审批占位 tool_result 终态化为"会话已切换",防占位残留下次发送喂给 LLM。
// 须在 retain 前(遍历即将被丢弃的条目)——chat.rs:44 硬契约,顺序不能反。
super::chat::finalize_pending_placeholders(&mut *session, &conversation_id, "会话已切换");
// 阶段3a 单真相源合并:单表 retain(kind 不区分,清本 conv 保留其他)。
session.pending_approvals.retain(|_, a| a.conversation_id.as_deref() != Some(&conversation_id));
// 释放 session lock 再做 async provider 查询 + spawn(避免持锁 await DB)
@@ -357,6 +361,10 @@ pub async fn ai_conversation_delete(
// 删除任意对话(含非活跃)都应清理其积压审批:挂起审批是会话级 HashMap,
// 非活跃对话的恢复审批(recovered,conversation_id 指向被删对话)若不 retain 清理,
// 会永久残留死审批条目。对齐 ai_conversation_switch 的 retain 口径(仅清目标对话,保留其他)。
// SW-260618-02(7-ipc-approval 配套 stop/clear/force_send 终态化):删除目标 conv 前,
// 先把其 pending 审批占位 tool_result 终态化为"会话已删除",防占位残留下次发送喂给 LLM。
// 须在 retain 前(遍历即将被丢弃的条目)——chat.rs:44 硬契约,顺序不能反。
super::chat::finalize_pending_placeholders(&mut *session, &conversation_id, "会话已删除");
// 阶段3a 单真相源合并:单表 retain(kind 不区分)。
session.pending_approvals.retain(|_, a| a.conversation_id.as_deref() != Some(&conversation_id));
// F-260616-09 B 批4:删除 conv 时移除其 per_conv 条目(设计 §4.1 conv 存在性判据依赖此,