修复: DeepSeek 400 全量扫描 + 队列 per-conv 隔离

- openai_compat: 扫描所有 assistant 消息剥离 orphan tool_calls(原仅查末条)
- queue 加 conversationId 字段,按会话精准 drain
- regenerate/editMessage 只清本会话排队消息
- newConversation 保留旧会话排队消息
- AiError 只清出错会话的队列项
This commit is contained in:
lxy
2026-07-20 00:19:50 +08:00
parent 42efb31bbf
commit e9e3578d26
59 changed files with 2875 additions and 1330 deletions
+10 -16
View File
@@ -45,7 +45,7 @@ pub mod stream_recv;
pub mod title;
pub mod tool_registry;
use agentic::conv_state::ConvState;
use agentic::conv_state::{ConvState, ConvStateStore};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
@@ -558,18 +558,13 @@ impl AiSession {
/// `ai_chat_stop`(commands/chat.rs) 改调本方法替代各自手写的 has_pending,
/// 收敛两处状态机判定到单一入口。
///
/// 兜底/回退:若本方法判定异常需快速回退手写 has_pending,调用方注释已保留原
/// 三路组合(generating + path_auth + risk)字面量,改一行即可切回。
pub fn session_state(&self, conv_id: &str) -> SessionState {
// 阶段3a 单真相源合并后:两类挂起(path_auth + risk)合一进 pending_approvals,
// kind 字段区分语义但状态机只看「有无挂起」,单表 any 一次即可(消除原双表 OR 合并判断)。
/// B-Phase3:conv_state 读侧切 ConvStateStore(单源收敛)。
pub fn session_state(&self, conv_id: &str, conv_states: &ConvStateStore) -> SessionState {
let has_pending = self.pending_approvals.values()
.any(|a| a.conversation_id.as_deref() == Some(conv_id));
if has_pending {
SessionState::AwaitingApproval
} else if self.conv_read(conv_id).map(|c| c.conv_state.is_active()).unwrap_or(false) {
// 读侧(双轨收口批2):改读 conv_state.is_active()(Generating+Compressed),判断是否在
// 活跃生成。is_active() 与原 generating 语义一致(压缩期间 loop 仍活跃,应判为 Streaming)。
} else if conv_states.is_active(conv_id) {
SessionState::Streaming
} else {
SessionState::Idle
@@ -613,7 +608,8 @@ mod tests_f09_per_conv {
fn test_per_conv_state_new() {
let s = PerConvState::new();
// 批3 收口:generating bool 已退役,生成态初值校验改读 conv_state(应为 Idle)。
assert_eq!(s.conv_state, ConvState::Idle, "conv_state 初值应为 Idle");
// B-Phase3:conv_state 已迁 ConvStateStore,PerConvState 不再持有。
// conv_state 由 guard.new(Idle→Generating)经 conv_states.transition 迁移。
assert_eq!(s.iteration_used, 0, "iteration_used 初值应为 0");
assert!(s.agent_language.is_none(), "agent_language 初值应为 None");
assert!(s.model_override.is_none(), "model_override 初值应为 None");
@@ -644,7 +640,8 @@ mod tests_f09_per_conv {
let ptr_first = {
let s = session.conv("conv-a");
// 批3 收口:generating bool 已退役,首建会话生成态应为 Idle。
assert_eq!(s.conv_state, ConvState::Idle);
// B-Phase3:conv_state 已迁 ConvStateStore。
// conv_state 初值由 guard/入口保证,PerConvState 不再持有。
assert_eq!(s.iteration_used, 0);
s as *const PerConvState
};
@@ -816,10 +813,7 @@ mod tests_f09_per_conv {
pub struct PerConvState {
/// 对话历史(ContextManager:会话级消息真相源,裁剪仅影响发送视图)
pub messages: ContextManager,
/// L2 统一状态机:对话生命周期状态(单一真相源,批2 持久化供读侧/前端消费)
/// 写收敛:经 GeneratingGuard/入口 transition_to 守卫迁移,非直接赋值。
/// 批3 双轨收口:generating bool 已退役,此 enum 成为生成态唯一真相源。
pub conv_state: ConvState,
// B-Phase3:conv_state 字段已迁到 ConvStateStore(无锁真相源),此字段已删
/// G1 目标钉扎真相源:从 LLM 工具调用推理的目标列表(内容态字段,与生命周期态正交)。
///
/// 治 R1(目标消息被压缩/compressed 物理出局,sanitize step0 过滤 is_active 致目标丢失):
@@ -907,7 +901,7 @@ impl PerConvState {
pub fn new() -> Self {
Self {
messages: ContextManager::new(ContextConfig::default()),
conv_state: ConvState::Idle,
pinned_goals: Vec::new(),
stop_flag: Arc::new(AtomicBool::new(false)),
notify: Arc::new(tokio::sync::Notify::new()),