diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs index 6e1a29391..691ddb02d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs @@ -83,18 +83,24 @@ fn approval_observation(receipt: &PlanGddApprovalV1) -> AgentRuntimeToolObservat fn append_plan_gdd_revision_message( root: &Path, receipt: &PlanGddApprovalV1, + receipt_gdd: &PlanGddV1, ) -> Result<(), String> { let label = if receipt.action == "reject" { "退回" } else { "修改" }; + let supervisor_session_id = resolve_game_creator_agent_runtime_session_id_for_run_at( + root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + &receipt_gdd.root_run_id, + )?; append_local_conversation_message_for_session_idempotent_at( root, Some(GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID), - // 落在 Supervisor 当前活动会话。delivery 上的 parentSessionId 是委派发出时 - // 的快照,不保证仍是可写的活动会话。 - None, + // receipt 的 rootRun 所属 Supervisor task 才是这条用户意见的历史归属; + // 不能在 recovery 重放时按当前 active session 重新路由到别的会话。 + Some(&supervisor_session_id), LocalConversationMessage { role: "user".to_string(), content: format!( @@ -1271,7 +1277,7 @@ fn project_receipt_locked( note_plan_gdd_projection_gap(root, receipt, "delivery-revision-mark", &error); recovery_pending = true; } - if let Err(error) = append_plan_gdd_revision_message(root, receipt) { + if let Err(error) = append_plan_gdd_revision_message(root, receipt, receipt_gdd) { note_plan_gdd_projection_gap(root, receipt, "delivery-revision-message", &error); recovery_pending = true; } diff --git a/apps/ai-game-creator-shell/src-tauri/src/project/conversation.rs b/apps/ai-game-creator-shell/src-tauri/src/project/conversation.rs index 3e40c51b4..12eb9f5d0 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project/conversation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project/conversation.rs @@ -1152,12 +1152,6 @@ fn append_local_conversation_message_for_session_internal_at( ) } }; - if archived { - return Err(format!( - "Agent Session 已归档,只能读取:{}", - normalized_session_id.as_deref().unwrap_or_default() - )); - } let role = role.trim(); if !matches!(role, "user" | "assistant" | "tool") { return Err("对话角色必须是 user、assistant 或 tool".to_string()); @@ -1226,6 +1220,12 @@ fn append_local_conversation_message_for_session_internal_at( } false } else { + if archived { + return Err(format!( + "Agent Session 已归档,只能读取:{}", + normalized_session_id.as_deref().unwrap_or_default() + )); + } append_jsonl_line_unlocked(&path, &line, "对话记录")?; true }; diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 95d15253c..277195298 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -7792,6 +7792,7 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 现象:GDD“修改”已成功生成下一版本且当前 pending 身份正确,但 hydrate 持续返回 `recoveryPending=true`,审批卡显示“审批状态正在恢复”。 - 原因:恢复扫描会重放全部历史 approval receipt;旧版本 receipt 仍拿当前单例 approval pending 做 identity 比对。修改后当前 pending 已属于新版本,旧 receipt 的 identity 不同是正常状态,却被误记为投影缺口。 - 决策:receipt 的 index、Markdown、audit、submit observation、session 等投影继续允许全量恢复;approval pending 只由 lineage 最新 GDD 的 receipt 读取、更新和清理。历史 receipt 不得检查或改写当前 pending,也不得因此提升 `recoveryPending`。 +- 审批意见消息按 receipt 的 `rootRunId` 解析到原 Supervisor task 所属会话恢复;不会按当前 active session 重新路由。已存在于归档会话的幂等消息允许重放且不新增消息,缺失消息仍保持恢复失败,不静默写入其他会话。 - 验证:沿用现有审批恢复与 planning submit 定向测试;未新增独立测试,避免为非代表性 fixture 引入额外状态构造。 ## 2026-08-27 审批修订以最新用户意见更新 GDD 决定快照