优化: aichat L2状态机双轨清理+读侧迁移+L3事件总线emit双写
L2 双轨清理:删 guard 本地 state 冗余字段(ConvState 已落 PerConvState 持久化作读侧真相源)。读侧迁移:ai_is_generating/ai_chat_stop 加 CONV_STATE_ENABLED 门控读 conv_state.is_active()。L3 emit 双写:mod.rs 关键事件 AiCompleted/AiError/AiAgentRound 经 ai_event_bus.publish 双写 (EVENT_BUS_ENABLED 门控,与 app.emit 并存非替换,高频 delta 不双写)。 双轨过渡收尾,可一键回退(CONV_STATE_ENABLED off 回退 bool 真相源)。
This commit is contained in:
@@ -26,25 +26,25 @@ use super::conv_state::{self, ConvState};
|
||||
/// (per-conv 唯一真相源)。顶层 `session.generating` 字段已在批4 删除,reset/Drop 仅写 per_conv;
|
||||
/// IPC(ai_is_generating/ai_chat_send 等)亦改读 per_conv,无需双写桥接。
|
||||
///
|
||||
/// L2 统一状态机(渐进第一步,2026-06-21):guard 内嵌 `ConvState` 作**写收敛视图层**。
|
||||
/// L2 统一状态机收尾(2026-06-22):本地 `state: Option<ConvState>` 视图层冗余字段已删除。
|
||||
///
|
||||
/// - `new` 时按 `conv_state::CONV_STATE_ENABLED` 门控,把内部 `state` 从 `Idle` 经守卫迁移到
|
||||
/// `Generating`(非法转换记 warn 不阻断,对齐「机制优先,失败兜底」原则)。
|
||||
/// - `reset` / `drop` 收尾时同步迁移 `Generating → Idle`(状态机视图与 `generating` bool 双轨,
|
||||
/// bool 仍是核心真相源,enum 仅视图)。off 降级:guard 不持/不迁 enum,纯旧 bool 行为(可回退)。
|
||||
/// 背景:批1 曾在 guard 内嵌 `state` 作「写收敛视图层」(双轨过渡),批2 已把 `ConvState` 落到
|
||||
/// `PerConvState.conv_state` 字段持久化(读侧真相源)。批2 同时在 `reset`/`drop` 内读
|
||||
/// `conv.conv_state` 持久化字段做迁移 + emit AiConvStateChanged——故本地 `state` 成纯冗余
|
||||
/// (双源,且本地 state 在 Drop 无 await 上下文无法持久化,语义本就不完整)。
|
||||
///
|
||||
/// **渐进取舍(为什么 enum 放 guard 而非 PerConvState 字段)**:guard 是 loop 生命周期的天然
|
||||
/// 边界(set/reset/Drop 成对),把 enum 绑 guard 即覆盖全部生成态进入/退出点,无需改 PerConvState
|
||||
/// 字段(改字段会牵动所有读写点,属批2+ 范围)。本批 enum 仅作 guard 内部视图,不对外持久化;
|
||||
/// 批2+ 把 enum 提升到 PerConvState.conv_state 字段后,guard 仍可保留作写收敛入口(枚举源切换)。
|
||||
/// 收尾删除后:
|
||||
/// - `new`:读 `conv.conv_state` 持久化字段,若 Idle→Generating 迁移成功则 emit 通知前端
|
||||
/// (入口 `run_agentic_loop` 已独立写持久化迁移但**不 emit**,故此处补 emit 防前端漏收生成态)。
|
||||
/// - `reset`/`drop`:迁移 + emit 均读 `conv.conv_state` 持久化字段(批2 已落地,本批仅删冗余本地块)。
|
||||
/// - `disarm`:不动 ConvState(审批等待是 Generating 内的暂停点)。
|
||||
///
|
||||
/// 开关 `CONV_STATE_ENABLED`(默认 on)门控:off 时跳过 enum 迁移与 emit,纯旧 bool 行为(可回退)。
|
||||
pub(super) struct GeneratingGuard {
|
||||
session: Arc<Mutex<AiSession>>,
|
||||
/// guard 所属会话(loop 启动时快照的 conv_id,来自 run_agentic_loop 入参)。
|
||||
conv_id: String,
|
||||
done: bool,
|
||||
/// L2 状态机视图(CONV_STATE_ENABLED 门控)。None = 开关 off 降级,guard 不持视图。
|
||||
/// 仅 guard 生命周期内的本地视图,不持久化(批2+ 提升至 PerConvState 字段)。
|
||||
state: Option<ConvState>,
|
||||
/// L2 批2 1b:emit AiConvStateChanged 用的 AppHandle(Clone 廉价,run_agentic_loop 入参传入)。
|
||||
/// CONV_STATE_ENABLED 门控:off 时仍持(无副作用),但 new/reset/drop 跳过 emit。
|
||||
app_handle: AppHandle,
|
||||
@@ -52,52 +52,56 @@ pub(super) struct GeneratingGuard {
|
||||
|
||||
impl GeneratingGuard {
|
||||
pub(super) fn new(session: Arc<Mutex<AiSession>>, conv_id: String, app_handle: AppHandle) -> Self {
|
||||
// CONV_STATE_ENABLED 门控:off → state=None,guard 不持/不迁 enum(纯旧 bool 行为)。
|
||||
// on → state 从 Idle 起,入口即经守卫迁移到 Generating(写收敛:状态机入口校验)。
|
||||
let mut state = if conv_state::CONV_STATE_ENABLED {
|
||||
Some(ConvState::Idle)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(s) = state.as_mut() {
|
||||
// Idle → Generating 守卫迁移。正常路径(self-loop 幂等 / 首次进入)合法;理论非法不发生
|
||||
// (新 guard 必 Idle),失败记 warn 不阻断(对齐失败兜底原则)。
|
||||
match s.transition_to(ConvState::Generating) {
|
||||
Ok(new_state) => {
|
||||
*s = new_state;
|
||||
// L2 批2 1b:本地视图迁移成功后 emit ConvState=Generating 通知前端(同步,失败忽略)。
|
||||
// 入口的持久化迁移(PerConvState.conv_state)由 run_agentic_loop 独立写,
|
||||
// 此处仅反映 guard 视图层状态(双轨过渡,enum 视图与 bool 并行)。
|
||||
let _ = app_handle.emit("ai-chat-event", AiChatEvent::AiConvStateChanged {
|
||||
conv_state: new_state,
|
||||
conversation_id: Some(conv_id.clone()),
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
conv_id = %conv_id,
|
||||
error = %e,
|
||||
"[ai] guard.new ConvState Idle→Generating 非法(状态机视图层,不阻断核心生成)"
|
||||
);
|
||||
// 状态机层失败:enum 保留 Idle(不写非法态),核心 generating 仍由调用方 set true。
|
||||
// 即 enum 视图与 bool 暂时不同步,但 enum 仅视图不影响核心复位语义。不 emit(避免误报非生成态)。
|
||||
}
|
||||
}
|
||||
// L2 收尾:入口 `run_agentic_loop` 已独立完成 Idle→Generating 的持久化迁移(写 conv.conv_state),
|
||||
// 但**不 emit** AiConvStateChanged。此处补 emit 通知前端进入生成态——读 conv.conv_state
|
||||
// 持久化字段(真相源)做迁移校验,迁移成功则 emit,非法记 warn 不阻断。
|
||||
//
|
||||
// 注:new 是同步函数(无 await),无法持 session 锁读 conv.conv_state;此处经
|
||||
// tauri::async_runtime::spawn 异步补 emit(非阻塞 guard 创建,入口持久化已发生故 emit 有据)。
|
||||
// spawn 失败(运行时不可用,理论场景)仅漏一次 emit,前端 watchdog 兜底(对齐失败不阻断核心生成)。
|
||||
if conv_state::CONV_STATE_ENABLED {
|
||||
let session = session.clone();
|
||||
let app_handle = app_handle.clone();
|
||||
let cid = conv_id.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let new_state = {
|
||||
let mut session = session.lock().await;
|
||||
let conv = session.conv(&cid);
|
||||
match conv.conv_state.transition_to(ConvState::Generating) {
|
||||
Ok(ns) => {
|
||||
conv.conv_state = ns;
|
||||
ns
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
conv_id = %cid,
|
||||
error = %e,
|
||||
"[ai] guard.new ConvState→Generating 非法(持久化层,不阻断核心生成)"
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
// 持久化层迁移成功后 emit 前端(同步,失败忽略)。锁已随作用域 drop,可安全 emit。
|
||||
let _ = app_handle.emit("ai-chat-event", AiChatEvent::AiConvStateChanged {
|
||||
conv_state: new_state,
|
||||
conversation_id: Some(cid),
|
||||
});
|
||||
});
|
||||
}
|
||||
Self { session, conv_id, done: false, state, app_handle }
|
||||
Self { session, conv_id, done: false, app_handle }
|
||||
}
|
||||
|
||||
/// 显式复位 generating=false。emit 前调用保证顺序。幂等。
|
||||
///
|
||||
/// 仅写 per_conv.conv_id.generating(唯一真相源)。
|
||||
/// L2:同步迁移 `ConvState → Idle`(状态机视图,CONV_STATE_ENABLED 门控)。
|
||||
/// L2:同步迁移 `conv.conv_state → Idle`(持久化层,CONV_STATE_ENABLED 门控)。
|
||||
pub(super) async fn reset(&mut self) {
|
||||
if !self.done {
|
||||
let mut session = self.session.lock().await;
|
||||
let conv = session.conv(&self.conv_id);
|
||||
conv.generating = false;
|
||||
// L2 批2:ConvState 持久化(Generating→Idle 写收敛,CONV_STATE_ENABLED 门控)。
|
||||
// 与本地 state 视图双写(本地保留批1,持久化为读侧/前端;1b 清理本地)。
|
||||
if conv_state::CONV_STATE_ENABLED {
|
||||
match conv.conv_state.transition_to(ConvState::Idle) {
|
||||
Ok(ns) => {
|
||||
@@ -117,19 +121,6 @@ impl GeneratingGuard {
|
||||
}
|
||||
self.done = true;
|
||||
}
|
||||
// 状态机视图同步:Generating → Idle(正常收敛退出)。仅记日志验证迁移合法(批2+ 接入
|
||||
// PerConvState.conv_state 字段后此处写字段持久化)。done=true 后再次 reset 幂等,enum 已 Idle
|
||||
// 自环通过。开关 off → state=None 跳过(纯旧 bool 行为)。
|
||||
if let Some(s) = self.state.as_mut() {
|
||||
match s.transition_to(ConvState::Idle) {
|
||||
Ok(new_state) => *s = new_state,
|
||||
Err(e) => tracing::warn!(
|
||||
conv_id = %self.conv_id,
|
||||
error = %e,
|
||||
"[ai] guard.reset ConvState →Idle 非法(状态机视图层,不阻断核心复位)"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 解除 Drop 兜底复位但不复位 generating。审批等待 return 路径调用:
|
||||
@@ -145,19 +136,10 @@ impl GeneratingGuard {
|
||||
|
||||
impl Drop for GeneratingGuard {
|
||||
fn drop(&mut self) {
|
||||
// 状态机视图同步(panic/异常退出路径):未 done 即异常退出,enum 应从 Generating→Idle 收敛。
|
||||
// 仅记日志(无 await 上下文,不写持久化——批2+ 接入字段后此处可写)。
|
||||
// 未 done 即异常退出(panic/未走正常 return):核心 generating 经 spawn 复位,
|
||||
// ConvState 持久化迁移 + emit 也在同一 spawn 内异步完成(无 await 上下文,对齐核心复位路径)。
|
||||
// 开关 off / done=true(正常 reset/disarm 已走) → 跳过。
|
||||
if !self.done {
|
||||
if let Some(s) = self.state.as_mut() {
|
||||
if let Err(e) = s.transition_to(ConvState::Idle) {
|
||||
tracing::warn!(
|
||||
conv_id = %self.conv_id,
|
||||
error = %e,
|
||||
"[ai] guard.drop ConvState →Idle 非法(异常退出兜底,核心 generating 仍 spawn 复位)"
|
||||
);
|
||||
}
|
||||
}
|
||||
let session = self.session.clone();
|
||||
let conv_id = self.conv_id.clone();
|
||||
// L2 批2 1b:Drop 无 await 上下文,持久化迁移 + emit 在 spawn 内异步完成(对齐核心复位 spawn 路径)。
|
||||
@@ -184,21 +166,20 @@ impl Drop for GeneratingGuard {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 单元测试(guard 内嵌 ConvState 视图层)
|
||||
// 单元测试(guard ConvState 迁移语义,纯逻辑路径验证)
|
||||
// ============================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// guard.new 默认迁移 Idle→Generating,内部视图应为 Generating(CONV_STATE_ENABLED on)。
|
||||
/// guard.new 路径:持久化层 Idle→Generating 迁移(读 conv.conv_state)。
|
||||
///
|
||||
/// 注:不构造真实 AiSession(lock/Mutex 建会话状态成本高),仅校验 state 字段的迁移语义——
|
||||
/// new 内部 transition_to 是纯函数,直接断言逻辑路径。此处用相同的纯函数路径验证 guard 语义。
|
||||
/// 注:不构造真实 AiSession(lock/Mutex 建会话状态成本高),仅校验迁移语义——
|
||||
// guard 内部 transition_to 是纯函数,直接断言逻辑路径。
|
||||
#[test]
|
||||
fn test_guard_new_migrates_to_generating() {
|
||||
// 模拟 guard.new 内部状态机迁移路径(纯逻辑,无 session):
|
||||
// CONV_STATE_ENABLED on → Idle → Generating。
|
||||
// CONV_STATE_ENABLED on → Idle → Generating(读侧持久化字段迁移路径)。
|
||||
assert!(conv_state::CONV_STATE_ENABLED, "开关默认 on,测试假设");
|
||||
let mut s = ConvState::Idle;
|
||||
s = s.transition_to(ConvState::Generating).unwrap();
|
||||
@@ -216,7 +197,7 @@ mod tests {
|
||||
/// 异常 Drop 路径同样 Generating→Idle(兜底),验证迁移合法。
|
||||
#[test]
|
||||
fn test_guard_drop_path_migrates_to_idle() {
|
||||
// guard.drop 异常路径(!done)迁移 Generating→Idle。
|
||||
// guard.drop 异常路径(!done)迁移 Generating→Idle(读持久化字段)。
|
||||
let mut s = ConvState::Generating;
|
||||
s = s.transition_to(ConvState::Idle).unwrap();
|
||||
assert_eq!(s, ConvState::Idle);
|
||||
|
||||
Reference in New Issue
Block a user