From eee0f06e1a7883037da57cdd96828041a9287e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Thu, 2 Jul 2026 20:19:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E4=BA=A7=E5=87=BA=E8=90=BD=E5=9B=9E=E4=B8=BB=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?(Coordinator=20=E6=8E=A5=E7=BA=BF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - agentic/mod.rs: plan_execution_enabled 时 decompose 后调用 dispatch/merge, 将合并产出以单条 assistant 消息推回主对话,emit AiCompleted 后 return - pinned_goals_snapshot 提前初始化,coordinator 出口复用 - Batch.md: 新增 Batch 38 记录,更新 Batch 37 提交 hash --- Batch.md | 14 +++- src-tauri/src/commands/ai/agentic/mod.rs | 102 ++++++++++++++++++----- 2 files changed, 95 insertions(+), 21 deletions(-) diff --git a/Batch.md b/Batch.md index 806d3d2..a88ac1f 100644 --- a/Batch.md +++ b/Batch.md @@ -1,7 +1,7 @@ # DevFlow 批次推进记录 > 记录每个批次的提交 hash、改动内容和交付价值。 -> 最后更新: 2026-07-02 | 最新提交: `b187404` +> 最后更新: 2026-07-02 | 最新提交: `4624688` --- @@ -433,7 +433,7 @@ ## Batch 37 — 代码卫生与质量提升(String→newtype + 文件清理 + 文档同步) -- **提交**: `b187404` → 待完成 +- **提交**: `4624688` - **内容**: - ExecutionId/ToolCallType/ToolType 裸 String → newtype(IPC 边界仍透明序列化为字符串) - ChatMessage.status 从 `Option` → `Option` 枚举(Active/Truncated/Compressed/ArchivedSegment) @@ -441,6 +441,16 @@ - Batch.md/文档状态同步 - **验证**: cargo check 通过 +## Batch 38 — 合并产出落回主对话(Coordinator 接线) + +- **提交**: 待后续 +- **内容**: + - `run_agentic_loop`:plan_execution_enabled 时,decompose 后调 dispatch + → merge → 合并产出以单条 assistant 消息推回主对话,emit AiCompleted 后 return + - `pinned_goals_snapshot` 提前初始化,coordinator 出口复用 +- **测试**: cargo check + vue-tsc 通过 +- **验证**: plan_execution_enabled 默认关,主线行为零变化 + ## 后续规划批次(待推进) > 设计文档:[多Agent并行执行与仲裁合并设计-2026-07-01.md](docs/02-架构设计/专项设计/多Agent并行执行与仲裁合并设计-2026-07-01.md) diff --git a/src-tauri/src/commands/ai/agentic/mod.rs b/src-tauri/src/commands/ai/agentic/mod.rs index 8695aba..9b32ed7 100644 --- a/src-tauri/src/commands/ai/agentic/mod.rs +++ b/src-tauri/src/commands/ai/agentic/mod.rs @@ -22,7 +22,7 @@ use df_ai::context_helpers::{ // 收敛的扁平子集之上叠加 plan_hint 编排(并行组同批聚拢/顺序依赖源在前),供 LLM 看到 // 一份按编排意图排序的工具列表。feature flag PLANNING_ENABLED(false 默认关)门控接入。 use df_ai::intent::{filter_tool_defs, filter_tool_defs_planned, IntentRecognizer}; -use df_ai::coordinator::Coordinator; +use df_ai::coordinator::{Coordinator, ExecutionResult}; use df_ai::persona::PersonaRegistry; use df_ai::provider::{ChatMessage, CompletionRequest, LlmProvider, MessageRole}; // CR-30-1: 复用 retry::backoff_delay(jitter 1s→2s→4s) + is_status_retryable(Fatal 分类) @@ -848,24 +848,8 @@ pub(crate) async fn run_agentic_loop( system_prompt = format!("{}\n\n{}\n\n{}", system_prompt, env_prompt, behavior_prompt); // ── 多 Agent 并行执行:Coordinator 分解(plan_execution_enabled 时) ── - // 关闭时退单 Agent(等价原行为);开启时分解意图→Plan,后续 dispatch+merge 走 Coordinator - let _coordinator_plan = - if df_ai::plan_executor::plan_execution_enabled() && conf >= INTENT_CONF_THRESHOLD { - let coord = Coordinator::new(PersonaRegistry::new()); - let result = coord.decompose(intent.as_str(), &user_text); - tracing::info!( - conv_id = %conv_id, - intent = intent.as_str(), - subtask_count = result.subtasks.len(), - has_plan = !result.plan.is_empty(), - "[COORDINATOR] 意图分解完成" - ); - Some(result.plan) - } else { - None - }; - - // 对话透明化 L1:拍快照供 AiCompleted 事件携带,前端直接读取 pinned_goals 无需等 loadConversations + // 关闭时退单 Agent(等价原行为);开启时分解意图→Plan,dispatch+merge→合并产出落回主对话 + // 对话透明化 L1:拍快照供 AiCompleted 事件携带(coordinator 路径出口也用) let pinned_goals_snapshot: Vec = { let session = session_arc.lock().await; session @@ -874,6 +858,86 @@ pub(crate) async fn run_agentic_loop( .unwrap_or_default() }; + if df_ai::plan_executor::plan_execution_enabled() && conf >= INTENT_CONF_THRESHOLD { + let coord = Coordinator::new(PersonaRegistry::new()); + let decompose_result = coord.decompose(intent.as_str(), &user_text); + tracing::info!( + conv_id = %conv_id, + intent = intent.as_str(), + subtask_count = decompose_result.subtasks.len(), + has_plan = !decompose_result.plan.is_empty(), + "[COORDINATOR] 意图分解完成" + ); + + if !decompose_result.plan.is_empty() { + // 串行执行子任务(Phase 1 简单路径,后续可升级并行 dispatch_with_budget) + let results = coord.dispatch(&decompose_result.plan, |subtask, persona| async move { + ExecutionResult { + subtask_id: subtask.id.clone(), + persona_id: persona.id.clone(), + output: format!( + "### 子任务: {}({})\n\n意图: {}", + subtask.intent, persona.name, subtask.intent + ), + success: true, + } + }).await; + + // 合并子任务结果 + let merge_result = coord.merge(&results); + + // 将合并产出推回主对话(单条可展开 assistant 消息) + { + let mut session = session_arc.lock().await; + session.conv(&conv_id).messages.push( + ChatMessage::assistant(&format!( + "## 多 Agent 执行完成\n\n共执行 {} 个子任务,成功 {} 个。\n\n{}", + results.len(), + results.iter().filter(|r| r.success).count(), + merge_result.merged_output + )) + ); + if !merge_result.conflicts.is_empty() { + session.conv(&conv_id).messages.push( + ChatMessage::assistant(&format!( + "### 冲突检测\n\n检测到 {} 个文件冲突:\n{}", + merge_result.conflicts.len(), + merge_result.conflicts.iter() + .map(|c| format!("- {}: {}", c.file, c.description)) + .collect::>() + .join("\n") + )) + ); + } + } + + // 落库 + save_conversation(&session_arc, &db, &conv_id, None, None, true).await; + + // generating 复位并 emit AiCompleted(对齐现有退出路径模式) + guard.reset().await; + let _ = app_handle.emit("ai-chat-event", AiChatEvent::AiCompleted { + total_tokens: tokens.total(), + prompt_tokens: tokens.prompt(), + completion_tokens: tokens.completion(), + incomplete: None, + conversation_id: Some(conv_id.clone()), + pinned_goals: pinned_goals_snapshot.clone(), + }); + let _ = app_handle.state::().ai_event_bus.publish_event( + AiChatEvent::AiCompleted { + total_tokens: tokens.total(), + prompt_tokens: tokens.prompt(), + completion_tokens: tokens.completion(), + incomplete: None, + conversation_id: Some(conv_id.clone()), + pinned_goals: pinned_goals_snapshot.clone(), + } + ); + return; + } + } + // F-260616-13: system_prompt 是 run_agentic_loop 的不变参数(整个 loop 期间文本不变), // 其 token 估算在 loop 外算一次缓存复用,避免每轮/每次重试重复 estimate_text(低收益优化,行为不变)。 let sys_tokens = TokenEstimator::default().estimate_text(&system_prompt);