//! B-260615-09: generating 状态 RAII guard —— 从 agentic.rs 抽离(重构第一批,纯结构搬迁)。 //! //! 行为零变更:仅文件位置移动,逻辑/字段/语义完全保留。 //! 调用方(agentic.rs run_agentic_loop)经 `use super::guard::GeneratingGuard;` 复用。 use std::sync::Arc; use tokio::sync::Mutex; use crate::commands::ai::AiSession; /// generating 复位 RAII guard,取代散布的手动 `session.generating = false`。 /// /// 两路复位: /// - 正常路径:exit 点显式 `reset().await` 即时复位(emit 前调,保证"复位→emit"顺序, /// 前端收事件时后端已可接下一条)。 /// - 异常路径(panic/未走正常 return):Drop 兜底 spawn 复位,防 generating 永真卡死前端。 /// /// 注:try_continue_agent_loop 不用 guard——其 should_continue=false 路径需保持 /// generating=true(审批等待态),全函数 guard 会误复位;该函数单点 provider-Err 复位保持手动。 /// /// F-260616-09 B 批2:guard 持 `conv_id`,复位改写 `session.conv(&conv_id).generating = false` /// (per-conv 真相源)。同时**双写顶层 `session.generating = false`** 作共存期桥接 —— 批2 /// 仅迁移 agentic.rs 路径,IPC(ai_is_generating/ai_chat_send)仍读顶层,故 guard 须双写 /// 保证 IPC 读到正确值(否则前端 ai_is_generating 永远 true 卡死发送)。批4 IPC 迁移后 /// 顶层双写移除。 pub(super) struct GeneratingGuard { session: Arc>, /// guard 所属会话(loop 启动时快照的 conv_id,来自 run_agentic_loop 入参)。 conv_id: String, done: bool, } impl GeneratingGuard { pub(super) fn new(session: Arc>, conv_id: String) -> Self { Self { session, conv_id, done: false } } /// 显式复位 generating=false。emit 前调用保证顺序。幂等。 /// /// 双写:per_conv.conv_id.generating(新真相源)+ 顶层 generating(共存期 IPC 桥接)。 pub(super) async fn reset(&mut self) { if !self.done { let mut session = self.session.lock().await; session.conv(&self.conv_id).generating = false; self.done = true; } } /// 解除 Drop 兜底复位但不复位 generating。审批等待 return 路径调用: /// 保持 generating=true 留 try_continue 续生成,同时 Drop 因 done=true 跳过复位 spawn。 /// (B-260615-26: 修复审批执行后对话不续生成回归) pub(super) fn disarm(&mut self) { self.done = true; } } impl Drop for GeneratingGuard { fn drop(&mut self) { if !self.done { let session = self.session.clone(); let conv_id = self.conv_id.clone(); tauri::async_runtime::spawn(async move { let mut s = session.lock().await; s.conv(&conv_id).generating = false; }); } } }