From 692cc9307548dcdba69581d463a19b13d58cb963 Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Sun, 16 Aug 2026 17:27:55 +0800 Subject: [PATCH] =?UTF-8?q?fix(ws):=20=E5=8E=86=E5=8F=B2=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E6=97=B6=E5=AF=B9=E8=B4=A6=20task=20running=20=E5=8D=A0?= =?UTF-8?q?=E4=BD=8D=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=88=B7=E6=96=B0/?= =?UTF-8?q?=E5=88=87=E8=AF=9D=E9=A2=98=E5=90=8E=E5=AD=90=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E6=B0=B8=E8=BF=9C=E6=98=BE=E7=A4=BA=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DB messages 表的 task tool_result 固化在 spawn 时刻的 running 状态,完成信号只更新 pending_subagents 表;send_topic_history 原样发送导致刷新后卡片退回黄色运行中。 新增 reconcile_running_in_messages:发送前按 pending_subagents 实际状态替换发送副本, 复用 session.rs 提取/格式化函数(改为 pub(crate)),不落库且幂等,无占位时零查询。 --- src/gateway/session.rs | 4 ++-- src/gateway/ws.rs | 54 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/gateway/session.rs b/src/gateway/session.rs index 880d238..16308ba 100644 --- a/src/gateway/session.rs +++ b/src/gateway/session.rs @@ -881,7 +881,7 @@ impl Session { /// 纯文本格式: `running, task_id=xxx. ...` /// /// 返回 (task_id, is_json)。仅当 status=="running" 时才提取(只对账 running 占位)。 -fn extract_task_id_from_content(content: &str) -> Option<(String, bool)> { +pub(crate) fn extract_task_id_from_content(content: &str) -> Option<(String, bool)> { // 尝试 JSON 格式 let json_start = content.find('{')?; if let Ok(parsed) = serde_json::from_str::(&content[json_start..]) { @@ -912,7 +912,7 @@ fn extract_task_id_from_content(content: &str) -> Option<(String, bool)> { /// /// JSON 格式:更新 JSON 中的 status 字段(保持前端 parseTaskResult 兼容)。 /// 纯文本格式:替换为描述性文本(向后兼容)。 -fn format_reconciled_content(task_id: &str, status: &str, is_json: bool) -> String { +pub(crate) fn format_reconciled_content(task_id: &str, status: &str, is_json: bool) -> String { if is_json { // 更新 JSON 中的 status 字段,保持前端 parseTaskResult 能正确解析 let placeholder = match status { diff --git a/src/gateway/ws.rs b/src/gateway/ws.rs index b428baa..88e28a9 100644 --- a/src/gateway/ws.rs +++ b/src/gateway/ws.rs @@ -806,7 +806,13 @@ async fn send_topic_history( task_repository: &Arc, ) -> Result<(), Box> { // 加载话题消息,按 session_id 过滤,避免混入子智能体消息 - let messages = store.load_messages_for_topic_full(topic_id, Some(session_id))?; + let mut messages = store.load_messages_for_topic_full(topic_id, Some(session_id))?; + + // 对账 running 占位:DB 中的 task tool_result 永远保持 spawn 时的 running 状态 + // (实时完成信号只更新前端内存与 pending_subagents 表),若不替换, + // 刷新/切话题后前端卡片会永远显示"运行中"。与 Session::reconcile_running_placeholders + // 同语义,仅改发送副本,不落库。 + reconcile_running_in_messages(&mut messages, store, topic_id); tracing::info!(topic_id = %topic_id, message_count = messages.len(), "Sending topic history"); @@ -877,6 +883,52 @@ async fn send_topic_history( Ok(()) } +/// 发送前对账消息列表中的 task "running" 占位。 +/// +/// DB messages 表中的 task tool_result 行固化在 spawn 时刻的 running 状态, +/// 完成信号只更新 pending_subagents 表;此处按该表的实际状态替换发送副本, +/// 避免前端刷新/切话题后卡片永远显示"运行中"。仍为 running 的保持原样。 +fn reconcile_running_in_messages( + messages: &mut [crate::bus::ChatMessage], + store: &Arc, + topic_id: &str, +) { + let has_running_placeholder = messages.iter().any(|m| { + m.role == "tool" && crate::gateway::session::extract_task_id_from_content(&m.content).is_some() + }); + if !has_running_placeholder { + return; // 无需查询 DB + } + + let pending = match store.list_pending_subagents(topic_id, None) { + Ok(records) => records, + Err(e) => { + tracing::warn!(error = %e, %topic_id, "Failed to query pending_subagents for history reconciliation"); + return; + } + }; + let status_map: std::collections::HashMap<&str, &str> = pending + .iter() + .map(|r| (r.task_id.as_str(), r.status.as_str())) + .collect(); + + for msg in messages.iter_mut() { + if msg.role != "tool" { + continue; + } + let Some((task_id, is_json)) = crate::gateway::session::extract_task_id_from_content(&msg.content) + else { + continue; + }; + match status_map.get(task_id.as_str()) { + Some(&status) if status != "running" => { + msg.content = crate::gateway::session::format_reconciled_content(&task_id, status, is_json); + } + _ => {} // 不存在(已清理)或仍在运行:保留原占位 + } + } +} + /// 加载并发送子智能体任务的历史消息 async fn send_task_messages( store: &Arc,