From 9be673513e54db40af54f9a3459a215620f35777 Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 21 Aug 2026 09:24:20 +0000 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=9B=9E=E5=BD=92=EF=BC=9AsourceActio?= =?UTF-8?q?nId=20=E5=BF=85=E9=A1=BB=E5=86=99=E5=9C=A8=20summary=20?= =?UTF-8?q?=E4=B8=8A=EF=BC=8Cdetail=20=E5=88=B0=E4=B8=8D=E4=BA=86=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E6=89=8B=E9=87=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一笔把 sourceActionId 追加进 file.read 的 observation detail,同时砍掉了 agent.action_history。但 file.read 的 detail 在投影给模型和事件流之前会被换成 durable receipt 的 safe_detail(只有 path / contentSha256 / lines 三个字段), 追加的字段根本到不了模型手里。 于是 run 22 变成:模型照着新指令满世界找 sourceActionId,一次也没看到; agent.action_history 又已经被拿掉,它没有任何途径取得 actionId,只能猜;猜错被 acceptance_update 拒,然后判断"此前回执中的读取动作未被验收持久层接受",重读全文 再猜。29 轮里 28 次 file.read(全是同一页 1-132)、5 次 acceptance_update、3 次拒绝。 summary 是原样保留到模型和事件流的(日志里 `|` 左边那句就是 observe_agent_runtime_file 构造的原文),也没有任何解析方依赖它的形状。改成写在 summary 末尾。用例同步改成断言 summary 带 id、detail 保持原样。 Co-Authored-By: Claude Opus 5 --- .../agent/runtime_actions/action_execution.rs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) 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 就不再追加第二个"); } }