diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_execution.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_execution.rs index 5eead20f3..c85d9c1df 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_execution.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_execution.rs @@ -544,18 +544,16 @@ fn append_agent_runtime_file_read_source_action_id( let Some(action_id) = action_id.map(str::trim).filter(|value| !value.is_empty()) else { return; }; - let Some(detail) = observation.detail.as_deref() else { - return; - }; - let Some(first) = detail.lines().next() else { - return; - }; - if first.contains("sourceActionId=") { + if observation.summary.contains("sourceActionId=") { return; } - let rest = detail[first.len()..].to_string(); - let first = first.to_string(); - observation.detail = Some(format!("{first} · sourceActionId={action_id}{rest}")); + // 写进 **summary** 而不是 detail:`file.read` 的 detail 在投影给模型和事件流之前 + // 会被换成 durable receipt 的 safe_detail(只有 path / contentSha256 / lines 三个 + // 字段),追加在 detail 上的字段到不了模型手里。实测一次 run 里模型照着新指令 + // 满世界找 sourceActionId、一次也没看到,只能反复重读全文再猜 actionId,28 次 + // file.read、5 次 acceptance_update 才收敛。summary 是原样保留的,也没有任何 + // 解析方依赖它的形状。 + observation.summary = format!("{} · sourceActionId={action_id}", observation.summary); } pub(in crate::agent) fn observe_agent_runtime_project_snapshot_with_lock( @@ -935,12 +933,14 @@ mod file_read_source_action_id_tests { &mut observation, Some("action-0123456789abcdef01234567"), ); + assert!( + observation + .summary + .ends_with("· sourceActionId=action-0123456789abcdef01234567"), + "actionId 必须写在 summary 上——detail 会被换成 safe_detail,到不了模型手里" + ); let detail = observation.detail.clone().expect("detail"); - assert!(detail - .lines() - .next() - .expect("first line") - .ends_with("· sourceActionId=action-0123456789abcdef01234567")); + assert!(!detail.contains("sourceActionId="), "detail 保持原样"); assert!(detail.contains("第一行内容"), "首行之后的内容必须保留"); let root = std::env::temp_dir(); @@ -958,19 +958,19 @@ mod file_read_source_action_id_tests { fn appending_is_a_no_op_without_a_successful_read_or_an_action_id() { let mut failed = file_read_observation(); failed.status = "failed".to_string(); - let before = failed.detail.clone(); + let before = failed.summary.clone(); append_agent_runtime_file_read_source_action_id(&mut failed, Some("action-1")); - assert_eq!(failed.detail, before); + assert_eq!(failed.summary, before); let mut missing = file_read_observation(); - let before = missing.detail.clone(); + let before = missing.summary.clone(); append_agent_runtime_file_read_source_action_id(&mut missing, None); - assert_eq!(missing.detail, before); + assert_eq!(missing.summary, before); let mut twice = file_read_observation(); append_agent_runtime_file_read_source_action_id(&mut twice, Some("action-1")); - let once = twice.detail.clone(); + let once = twice.summary.clone(); append_agent_runtime_file_read_source_action_id(&mut twice, Some("action-2")); - assert_eq!(twice.detail, once, "已经带了 id 就不再追加第二个"); + assert_eq!(twice.summary, once, "已经带了 id 就不再追加第二个"); } }