重构: 后端 df-ai/commands 拆分+df-nodes/workflow 改造+P0 bug 修复
- df-ai: context 历史中毒三档自愈 sanitize_messages(AC3)+anthropic_compat tool_use_id None 跳过(AC1/AC2)+删 router/stream 死码
- df-core: events 加 select_type+decisions 多选审批契约(F-260615-01)
- df-execute: shell run_command 工具复用(F-260615-05)
- df-nodes: human_node 多选校验+2 端到端测(F-01)+取消跳 set_failed(B-03b-R1/R2/R8)
- df-workflow: executor/dag/state cancel 闭环(B-06/07/03a/b)+provider approve options(R-PD-5)
- df-storage: find_path_conflict 抽公共(R-PD-11)+COLS 常量断言
- df-ideas: 删 IdeaPromoter/PromotionPolicy 死码(R-PD-14)
- src-tauri/commands/ai: secret keyring 迁移(FR-S1/R-PD-4)+GeneratingGuard RAII+disarm(B-09/26)+newConversation 软复位(B-10)+stream 心跳/stop select/空回复判错(B-02/04/05/15)+run_command(F-05)+mask audit(AR-3)
- src-tauri/commands/{project,task,workflow,mod,lib,state}: task detail IPC(F-02)+approve decisions+task list 联动(B-29)
- Cargo.lock+Cargo.toml 依赖同步
This commit is contained in:
@@ -26,6 +26,7 @@ pub mod commands;
|
||||
pub mod conversation;
|
||||
pub mod knowledge_inject;
|
||||
pub mod prompt;
|
||||
pub mod secret;
|
||||
pub mod skills;
|
||||
pub mod stream_recv;
|
||||
pub mod title;
|
||||
@@ -84,12 +85,32 @@ pub enum AiChatEvent {
|
||||
AiError { error: String, conversation_id: Option<String> },
|
||||
/// Agent 循环新一轮(前端需新建 assistant 消息)
|
||||
AiAgentRound { round: u32, conversation_id: Option<String> },
|
||||
/// 流式心跳(静默期报活,前端 watchdog reset,区分「LLM 在跑」与「真断」)
|
||||
AiHeartbeat { conversation_id: Option<String> },
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 会话状态(放 mod.rs,各子文件经 use super::* 拿到)
|
||||
// ============================================================
|
||||
|
||||
/// AI 会话读侧状态视图(只读枚举,由 [`AiSession::session_state`] 推导)
|
||||
///
|
||||
/// 这是 **只读视图**,不替代 `AiSession` 上 `generating` / `pending_approvals` /
|
||||
/// `stop_flag` 等字段定义——字段仍是唯一真相源,调用方写状态时继续写字段,
|
||||
/// 读状态时统一走 `session_state()` 收敛判断逻辑(避免散布的 `if generating` /
|
||||
/// `if !pending_approvals.is_empty()` 三路组合在各调用点各自实现)。
|
||||
///
|
||||
/// 优先级:`pending_approvals` 非空(有挂起审批优先报 AwaitingApproval,
|
||||
/// 即使同时在流式)> `generating`(Streaming)> 都不满足(Idle)。
|
||||
pub enum SessionState {
|
||||
/// 空闲:既未生成、也无挂起审批
|
||||
Idle,
|
||||
/// 流式生成中:generating 为 true 且无挂起审批
|
||||
Streaming,
|
||||
/// 等待审批:pending_approvals 非空
|
||||
AwaitingApproval,
|
||||
}
|
||||
|
||||
/// AI 会话内状态(Mutex 保护)
|
||||
pub struct AiSession {
|
||||
/// 对话历史(ContextManager:唯一消息真相源,裁剪仅影响发送视图,不影响持久化)
|
||||
@@ -100,7 +121,17 @@ pub struct AiSession {
|
||||
pub active_conversation_id: Option<String>,
|
||||
/// 活跃对话创建时间(懒创建:首条消息落库前仅存内存,upsert 时用作 created_at)
|
||||
pub active_conv_created_at: Option<String>,
|
||||
/// 挂起的审批(tool_call_id → 审批信息)
|
||||
/// 挂起的审批。
|
||||
///
|
||||
/// **结构**:单层 `HashMap<tool_call_id, PendingApproval>`,按 `tool_call_id`
|
||||
/// 路由审批结果(`tool_call_id` 是路由键)。`PendingApproval.conversation_id`
|
||||
/// 是业务语义(哪个对话产生的审批)而非路由键——IPC 端按 `tool_call_id` 精确命中。
|
||||
///
|
||||
/// **现状**:`ai_pending_tool_calls` 等读取路径按 `conversation_id` 做 O(n) 线性
|
||||
/// 过滤。在典型场景(单对话、少量并发审批)下 n 极小,O(n) 可接受,无需二级索引。
|
||||
///
|
||||
/// **未来**:若多会话并发、审批量显著增大,可考虑加二级索引
|
||||
/// (`conversation_id → Vec<tool_call_id>`)把按会话过滤降到 O(1)。
|
||||
pub pending_approvals: HashMap<String, PendingApproval>,
|
||||
/// 是否正在生成
|
||||
pub generating: bool,
|
||||
@@ -123,6 +154,31 @@ impl AiSession {
|
||||
stop_flag: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 推导会话读侧状态视图(只读,不写任何字段)。
|
||||
///
|
||||
/// 优先级:`pending_approvals` 非空 → [`SessionState::AwaitingApproval`];
|
||||
/// 否则 `generating` → [`SessionState::Streaming`];否则 → [`SessionState::Idle`]。
|
||||
///
|
||||
/// 收敛点:调用方读「会话处于什么阶段」时统一走本方法,替代散布的
|
||||
/// `if !pending_approvals.is_empty() / if generating` 三路组合。
|
||||
///
|
||||
/// # 待替换调用点(本次仅新增视图,不替换调用点)
|
||||
///
|
||||
/// - `agentic.rs:283` — agent loop 入口/恢复处对 generating + 审批的组合判断
|
||||
/// - `commands.rs:250` — `ai_pending_tool_calls` 等读取前的状态门控
|
||||
/// - `commands.rs:451` — 审批提交/会话复位路径的状态判断
|
||||
///
|
||||
/// 上述三处替换由 P0 任务承接,本方法先就位供其切换。
|
||||
pub fn session_state(&self) -> SessionState {
|
||||
if !self.pending_approvals.is_empty() {
|
||||
SessionState::AwaitingApproval
|
||||
} else if self.generating {
|
||||
SessionState::Streaming
|
||||
} else {
|
||||
SessionState::Idle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 待审批的工具调用
|
||||
|
||||
Reference in New Issue
Block a user