From 0b0d48d9b4d715aa9723c3359c0a37abc4c489de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 7 Sep 2026 10:51:34 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=87=E6=BB=A4=20DirectProject=20=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E6=96=87=E7=94=A8=E6=88=B7=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 识别 Codex 环境与上下文标签形式的 user item 补充无 passthrough 元数据的回归测试 --- .../src/agent/direct_project_history.rs | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_project_history.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_project_history.rs index b552a53b9..3b67e47eb 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_project_history.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_project_history.rs @@ -15,6 +15,16 @@ const DIRECT_PROJECT_INTERNAL_CONTEXT_KINDS: &[&str] = &[ "environments.environment_context", ]; +const DIRECT_PROJECT_CONTEXTUAL_USER_TEXT_MARKERS: &[(&str, &str)] = &[ + ("", ""), + ("", ""), + ("", ""), + ("", ""), + ("", ""), + ("", ""), + ("", ""), +]; + pub(crate) fn is_direct_project_internal_context_item(item: &Value) -> bool { if matches!( item.get("role").and_then(Value::as_str), @@ -22,7 +32,8 @@ pub(crate) fn is_direct_project_internal_context_item(item: &Value) -> bool { ) { return true; } - item.pointer("/internal_chat_message_metadata_passthrough/content_item_kinds") + if item + .pointer("/internal_chat_message_metadata_passthrough/content_item_kinds") .and_then(Value::as_array) .is_some_and(|kinds| { kinds @@ -30,6 +41,22 @@ pub(crate) fn is_direct_project_internal_context_item(item: &Value) -> bool { .filter_map(Value::as_str) .any(|kind| DIRECT_PROJECT_INTERNAL_CONTEXT_KINDS.contains(&kind)) }) + { + return true; + } + item.get("content") + .and_then(Value::as_array) + .is_some_and(|parts| { + parts.iter().any(|part| { + let Some(text) = part.get("text").and_then(Value::as_str) else { + return false; + }; + let text = text.trim(); + DIRECT_PROJECT_CONTEXTUAL_USER_TEXT_MARKERS + .iter() + .any(|(start, end)| text.starts_with(start) && text.ends_with(end)) + }) + }) } fn history_path(root: &Path) -> PathBuf { @@ -96,7 +123,9 @@ fn find_direct_project_history_item_by_id_at( } if !pending.is_empty() { - if let Some(item) = direct_project_history_item_from_line(path, &pending)? { + // The append path repairs an unterminated final JSONL record before + // writing. A duplicate scan must not reject that repairable tail. + if let Ok(Some(item)) = direct_project_history_item_from_line(path, &pending) { if item.get("id").and_then(Value::as_str) == Some(item_id) { return Ok(Some(item)); } @@ -302,4 +331,24 @@ mod tests { } }))); } + + #[test] + fn filters_codex_contextual_user_item_without_passthrough_metadata() { + assert!(is_direct_project_internal_context_item(&json!({ + "type": "message", + "role": "user", + "content": [{ + "type": "input_text", + "text": "\n/tmp/project\n" + }] + }))); + assert!(!is_direct_project_internal_context_item(&json!({ + "type": "message", + "role": "user", + "content": [{ + "type": "input_text", + "text": "请读取 中的说明" + }] + }))); + } }