From d8d069895de93f348859b0f796c9f936f09c19af Mon Sep 17 00:00:00 2001 From: Linghong Date: Tue, 15 Sep 2026 15:10:38 +0000 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=A1=E6=89=B9=E5=89=8D?= =?UTF-8?q?=E6=80=9D=E8=80=83=E8=BF=87=E7=A8=8B=E7=9A=84=E9=98=B6=E6=AE=B5?= =?UTF-8?q?=E5=BD=92=E5=B1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在用户回合边界立即绑定上一轮 reasoning 到对应 assistant 消息 新增审批边界回归测试,防止思考过程错误堆积到下一阶段底部 --- .../src-tauri/src/agent/design_runtime.rs | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs index 38e7a2f8a..ebe6152b8 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs @@ -150,14 +150,26 @@ fn persisted_design_reasoning_entries(session: &DesignSession) -> Vec = Vec::new(); + let mut pending_reasoning: Vec = Vec::new(); let mut saw_response_output = false; for item in &session.history { if item.get("role").and_then(Value::as_str) == Some("user") { if !pending_reasoning.is_empty() || !current_reasoning.is_empty() { pending_reasoning.append(&mut current_reasoning); + // A user item closes the previous turn. Resolve its reasoning + // against that turn's last assistant message before moving to + // the next group; otherwise it is incorrectly attached to the + // next turn and rendered at the bottom as an orphan. + let assistant_id = assistant_groups + .get(group_index) + .and_then(|ids| ids.last()) + .cloned(); + for mut entry in pending_reasoning.drain(..) { + entry.message_id = assistant_id.clone(); + entries.push(entry); + } } group_index += 1; assistant_index = 0; @@ -1638,6 +1650,53 @@ mod tests { ); } + #[test] + fn persisted_reasoning_stays_with_the_turn_before_an_approval_boundary() { + let mut session = new_design_session("project", "quality"); + session.messages = vec![ + DesignMessage { + id: "turn-1:user".into(), + role: "user".into(), + text: "第一轮需求".into(), + }, + DesignMessage { + id: "turn-1:assistant".into(), + role: "assistant".into(), + text: "第一轮已提交审批".into(), + }, + DesignMessage { + id: "turn-2:user".into(), + role: "user".into(), + text: "用户已批准,进入下一阶段".into(), + }, + DesignMessage { + id: "turn-2:assistant".into(), + role: "assistant".into(), + text: "查询工作阶段".into(), + }, + ]; + session.history = vec![ + json!({"role":"user", "content":"第一轮需求"}), + json!({"type":"reasoning", "id":"before-approval", "content":[{"type":"reasoning_text", "text":"审批前的思考"}]}), + json!({"type":"message", "role":"assistant", "content":[{"type":"output_text", "text":"第一轮已提交审批"}]}), + json!({"role":"user", "content":"用户已批准,进入下一阶段"}), + json!({"type":"reasoning", "id":"after-approval", "content":[{"type":"reasoning_text", "text":"审批后的思考"}]}), + json!({"type":"message", "role":"assistant", "content":[{"type":"output_text", "text":"查询工作阶段"}]}), + ]; + + let entries = persisted_design_reasoning_entries(&session); + assert_eq!( + entries + .iter() + .map(|entry| (entry.id.as_str(), entry.message_id.as_deref())) + .collect::>(), + vec![ + ("before-approval", Some("turn-1:assistant")), + ("after-approval", Some("turn-2:assistant")), + ] + ); + } + #[tokio::test(flavor = "current_thread")] async fn scripted_design_provider_emits_reasoning_without_persisting_it() { let (_temp, root, _resources) = init_design_project();