diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs index 0fbb5a01f..1a45580fe 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs @@ -3149,6 +3149,23 @@ pub(crate) async fn direct_game_creator_codex_chat_at_with_optional_observer( .map_err(|error| error.to_string()) } +pub(crate) fn build_direct_codex_history_prompt( + root: &std::path::Path, + client_turn_id: &str, + current_prompt: &str, +) -> Result { + let conversation = read_local_conversation_for_session_at(root, None, None)?; + let current_message_id = format!("direct-codex:{client_turn_id}:user"); + let mut lines = conversation + .messages + .iter() + .filter(|message| message.message_id.as_deref() != Some(current_message_id.as_str())) + .map(|message| format!("{}: {}", message.role, message.content)) + .collect::>(); + lines.push(format!("user: {}", current_prompt.trim())); + Ok(lines.join("\n")) +} + /// Direct home-page chat never binds Codex to a user project. It gets a /// fresh isolated read-only workspace and a stable in-process thread so a /// normal conversation can continue without creating a project, assets, a @@ -3234,6 +3251,41 @@ pub(in crate::agent) fn shutdown_game_creator_codex_app_servers_impl() -> Result mod tests { use super::*; + #[test] + fn direct_history_prompt_replays_all_project_messages_in_order() { + let root = tempfile::tempdir().expect("temp dir"); + init_local_game_project_at(root.path(), "history-project", "history").expect("init"); + for (role, content, message_id) in [ + ("user", "hello", "direct-codex:old:user"), + ("assistant", "partial\nunexpected interrupt happened here", "partial-id"), + ("tool", "file-read result", "tool-id"), + ("user", "stored raw request", "direct-codex:new-turn:user"), + ] { + append_local_conversation_message_for_session_idempotent_at( + root.path(), + None, + None, + LocalConversationMessage { + role: role.to_string(), + content: content.to_string(), + agent_id: None, + }, + message_id, + ) + .expect("append history"); + } + let prompt = build_direct_codex_history_prompt( + root.path(), + "new-turn", + "new request", + ) + .expect("build prompt"); + assert_eq!( + prompt, + "user: hello\nassistant: partial\nunexpected interrupt happened here\ntool: file-read result\nuser: new request" + ); + } + #[test] fn direct_item_activities_are_closed_safe_categories() { let allowed = [ diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs index 6189558e7..69d959195 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs @@ -4220,9 +4220,20 @@ pub(crate) async fn chat_with_game_creator_direct_codex( return Err(error); } }; + let replay_prompt = match crate::agent::codex_app_server::build_direct_codex_history_prompt( + root, + &turn_id, + &user_prompt, + ) { + Ok(prompt) => prompt, + Err(error) => { + audit.finish(false); + return Err(error); + } + }; let reply = match run_direct_game_creator_turn_at_with_creation_type_and_emitter( root, - &user_prompt, + &replay_prompt, creation_type.as_deref(), Some(&turn_emitter), Some(&mut audit),