diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs index 8e73bb857..34ea55a4f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs @@ -717,6 +717,25 @@ fn direct_codex_safe_activity_for_item_value(item: &serde_json::Value) -> &'stat direct_codex_safe_activity_for_item(item_type) } +/// Project an app-server item into the small public payload carried by the +/// DirectProject event queue. Full item contents are persisted in JSONL and +/// must not be forwarded through the runtime event stream. +fn direct_thread_item_started_payload(item: &serde_json::Value) -> serde_json::Value { + serde_json::json!({ + "itemType": item + .get("type") + .and_then(serde_json::Value::as_str) + .unwrap_or("unknown"), + }) +} + +fn direct_thread_item_id(item: &serde_json::Value) -> Option { + item.get("id") + .and_then(serde_json::Value::as_str) + .filter(|value| !value.is_empty()) + .map(str::to_string) +} + fn direct_codex_command_is_game_verification(command: &str) -> bool { let command = command.to_ascii_lowercase(); command.contains("game.static_smoke") @@ -2933,18 +2952,14 @@ impl CodexAppServerConnection { })? .map_err(platform_llm::LlmError::InvalidRequest)?; direct_project_history.complete_item(&item); - let item_id = item - .get("id") - .and_then(serde_json::Value::as_str) - .filter(|value| !value.is_empty()) - .map(str::to_string); + let item_id = direct_thread_item_id(&item); append_direct_thread_event( &direct_thread_id, DirectThreadRawEventDraft { event_type: "item.completed".to_string(), turn_id: turn_id.clone(), item_id, - payload: serde_json::json!({ "item": item }), + payload: serde_json::json!({}), }, ); } @@ -3013,18 +3028,14 @@ impl CodexAppServerConnection { && self.inner.workspace_mode == CodexAppServerWorkspaceMode::DirectProject { - let item_id = item - .get("id") - .and_then(serde_json::Value::as_str) - .filter(|value| !value.is_empty()) - .map(str::to_string); + let item_id = direct_thread_item_id(item); append_direct_thread_event( &direct_thread_id, DirectThreadRawEventDraft { event_type: "item.started".to_string(), turn_id: turn_id.clone(), item_id, - payload: serde_json::json!({ "item": item }), + payload: direct_thread_item_started_payload(item), }, ); } @@ -3057,7 +3068,7 @@ impl CodexAppServerConnection { event_type: "turn.completed".to_string(), turn_id: turn_id.clone(), item_id: None, - payload: params.clone(), + payload: serde_json::json!({ "status": status }), }, ); } @@ -4035,6 +4046,22 @@ pub(crate) fn build_direct_codex_history_prompt( mod tests { use super::*; + #[test] + fn direct_thread_item_projection_drops_full_app_server_payload() { + let item = serde_json::json!({ + "id": "item-1", + "type": "mcpToolCall", + "tool": "agc_write_file", + "arguments": { "path": "game/index.html", "token": "secret" }, + "result": { "content": "large output" } + }); + assert_eq!(direct_thread_item_id(&item).as_deref(), Some("item-1")); + assert_eq!( + direct_thread_item_started_payload(&item), + serde_json::json!({ "itemType": "mcpToolCall" }) + ); + } + #[test] fn direct_item_activities_are_closed_safe_categories() { let allowed = [