新增: 任务工作流 AiAgentNode 直连会话编排

- ai 节点 mode agent 开关派发,默认旧路径零回归
- 完成通知桥注册触发,收敛出口单点投递报告
- 节点超时取消兜底,结果落任务产出
- 会话编排走独立新会话,不劫持活跃面板
This commit is contained in:
lxy
2026-10-01 19:35:22 +08:00
parent 7dcbdb1feb
commit bf0db10ef7
6 changed files with 513 additions and 11 deletions
+74 -2
View File
@@ -201,6 +201,14 @@ pub struct AppState {
/// guard/reset/ai_is_generating 全部零锁操作。PerConvState.conv_state 字段已删。
/// 治卡死连环(AiCompleted 延迟 / 工具后中断 / 第二条进队列同源根因)。
pub conv_states: Arc<crate::commands::ai::agentic::conv_state::ConvStateStore>,
/// 编排会话完成通知桥(conv_id → 完成信号 oneshot 发送端,值=最终 assistant 报告文本)。
///
/// 工作流 AiAgentNode 经编排会话启动通道(chat.rs orchestration_session_spawner)
/// 在 spawn 前注册等待;agentic loop 收敛统一出口(agentic/mod.rs emit_ai_completed_once,
/// epoch 守卫已由调用方保证,仅 owner loop 到达)投递并移除条目。
/// std Mutex:临界区为纯内存 insert/remove,无 await,不持锁跨等待点。
pub agent_completion_bridge:
Arc<std::sync::Mutex<HashMap<String, tokio::sync::oneshot::Sender<String>>>>,
// ── 知识库 ──
/// 知识库 Repo
pub knowledge: KnowledgeRepo,
@@ -396,6 +404,8 @@ impl AppState {
// B-Phase1: ConvStateStore 无锁存储(空构造;双轨期无写点,B-Phase2 写侧切入)。
// 复用上方提前构建的 Arc(spawn_agent ctx 注入同一实例)。
conv_states,
// 编排会话完成通知桥(空表起步,AiAgentNode 会话启动时注册,loop 收敛出口投递)。
agent_completion_bridge: Arc::new(std::sync::Mutex::new(HashMap::new())),
// Phase3 Layer1:tunnel 客户端未连接实例,lib.rs setup 内 connect
tunnel: std::sync::Arc::new(df_tunnel::WsTunnelClient::new()),
knowledge: KnowledgeRepo::new(&db),
@@ -685,6 +695,46 @@ impl AppState {
pub async fn clear_once_allowed_dirs(&self) {
self.allowed_dirs.write().await.once.clear();
}
/// 注册编排会话完成等待(工作流 AiAgentNode 会话 spawn 前调用)。
///
/// 同 conv 重复注册覆盖旧条目(旧 oneshot 发送端随值 drop,等待方收 Err 判定信号丢失)。
/// 锁中毒降级 into_inner 取数据继续(桥内只有内存条目,无一致性风险)。
pub fn register_agent_completion(&self, conv_id: String, tx: tokio::sync::oneshot::Sender<String>) {
let mut map = self
.agent_completion_bridge
.lock()
.unwrap_or_else(|p| p.into_inner());
map.insert(conv_id, tx);
}
/// 是否存在挂起的编排会话等待方(收敛出口短路判据,普通用户会话零后续开销)。
pub fn has_agent_completion_waiter(&self, conv_id: &str) -> bool {
let map = self
.agent_completion_bridge
.lock()
.unwrap_or_else(|p| p.into_inner());
map.contains_key(conv_id)
}
/// 投递编排会话完成通知并移除条目(无等待方 no-op)。
///
/// 条目无论投递成败都移除:接收端已 drop(节点超时/取消)时 send 失败静默,不残留。
/// 返回是否找到等待方(供调用方日志)。
pub fn notify_agent_completion(&self, conv_id: &str, report: String) -> bool {
let taken = self
.agent_completion_bridge
.lock()
.unwrap_or_else(|p| p.into_inner())
.remove(conv_id);
match taken {
Some(tx) => {
let _ = tx.send(report);
true
}
None => false,
}
}
}
/// 构建节点注册表 — 注册内置节点
@@ -706,9 +756,31 @@ fn build_registry(db: Arc<Database>) -> NodeRegistry {
// AiNode(df_nodes::ai_node, impl Node trait):
// 决策 a(AiNode 自审闭环)步骤②:AiNode 持 Arc<Database>,execute 完成后若有 task_id
// 则把产出落 task.output_json。工厂闭包 move 捕获 db 句柄(Arc clone 廉价)。
// mode 开关(工作流 AiAgentNode 直连 agentic 会话):config.mode == "agent" → 构造
// AiAgentNode(编排会话),缺省/"ask" 走旧 AiNode(单次补全,旧调用方零回归)。
// 工厂期 AppHandle 经进程级 OnceLock 读取(spawn_agent 同款,lib.rs setup 已注入,
// 工厂调用点在 run_workflow → build_dag,时序晚于 setup,句柄必已就绪)。
let ai_db = db.clone();
registry.register("ai", move |_config| {
Box::new(df_nodes::ai_node::AiNode::new(ai_db.clone()))
registry.register("ai", move |config| {
if config.get("mode").and_then(|v| v.as_str()) == Some("agent") {
Box::new(df_nodes::ai_agent_node::AiAgentNode::new(
ai_db.clone(),
crate::commands::ai::commands::chat::orchestration_session_spawner(),
))
} else {
Box::new(df_nodes::ai_node::AiNode::new(ai_db.clone()))
}
});
// AiAgentNode(df_nodes::ai_agent_node, impl Node trait):
// 独立注册类型:节点 execute 时以编程方式起独立 agentic 会话跑任务,收敛后取最终
// assistant 报告回写。spawn 通道经 orchestration_session_spawner 注入(内部经
// AppHandle OnceLock 取 AppState),db 句柄供 task_id 落 output_json(ai_db 同款)。
let agent_db = db.clone();
registry.register("ai_agent", move |_config| {
Box::new(df_nodes::ai_agent_node::AiAgentNode::new(
agent_db.clone(),
crate::commands::ai::commands::chat::orchestration_session_spawner(),
))
});
// AiSelfReviewNode(df_nodes::ai_self_review_node, impl Node trait):
// 决策 a 步骤③:四维度自审(prompt 固定+JSON 解析兜底),写回 output_json 加 review 子字段,