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": "请读取 中的说明"
+ }]
+ })));
+ }
}