diff --git a/server/src/session/codex/translate.rs b/server/src/session/codex/translate.rs index 3095e2f..e61aedc 100644 --- a/server/src/session/codex/translate.rs +++ b/server/src/session/codex/translate.rs @@ -154,10 +154,10 @@ fn start_item(item: &Value) -> Vec { item.get("type").and_then(Value::as_str), Some("file_change" | "fileChange") ) { - let Some(id) = item.get("id").and_then(Value::as_str) else { - return Vec::new(); - }; - return vec![patch_start(id.to_string(), file_change_diff(item))]; + // Unlike command execution, a file change's start notification has no payload. Its + // completed copy carries the diff, so that is where both common events are made; recording + // this empty shell produced a Patch card containing only `diff:`. + return Vec::new(); } let Some((id, tool, input)) = tool(item) else { return Vec::new(); @@ -202,10 +202,13 @@ fn complete_item(item: &Value, include_agent_message: bool) -> Vec { .get("id") .and_then(Value::as_str) .map(|id| { - vec![Event::ToolEnd { - id: id.to_string(), - output: tool_output(item), - }] + vec![ + patch_start(id.to_string(), file_change_diff(item)), + Event::ToolEnd { + id: id.to_string(), + output: tool_output(item), + }, + ] }) .unwrap_or_default(), _ => { @@ -529,23 +532,23 @@ mod tests { fn a_file_change_becomes_the_common_patch_shape() { let mut translator = Translator::default(); let started = translator.translate(&line( - r#"{"method":"item/started","params":{"item":{"id":"patch-1","type":"fileChange","changes":{"src/main.rs":{"type":"update","unified_diff":"@@ -1 +1 @@\n-old\n+new\n","move_path":null}},"status":"inProgress"}}}"#, + r#"{"method":"item/started","params":{"item":{"id":"patch-1","type":"fileChange","changes":{},"status":"inProgress"}}}"#, )); - assert_eq!( - started, - vec![patch_start( - "patch-1".to_string(), - "--- src/main.rs\n+++ src/main.rs\n@@ -1 +1 @@\n-old\n+new\n".to_string() - )] - ); + assert!(started.is_empty()); assert_eq!( translator.translate(&line( - r#"{"method":"item/completed","params":{"item":{"id":"patch-1","type":"fileChange","changes":{},"status":"completed","stdout":"Success"}}}"# + r#"{"method":"item/completed","params":{"item":{"id":"patch-1","type":"fileChange","changes":{"src/main.rs":{"type":"update","unified_diff":"@@ -1 +1 @@\n-old\n+new\n","move_path":null}},"status":"completed","stdout":"Success"}}}"# )), - vec![Event::ToolEnd { - id: "patch-1".to_string(), - output: String::new() - }] + vec![ + patch_start( + "patch-1".to_string(), + "--- src/main.rs\n+++ src/main.rs\n@@ -1 +1 @@\n-old\n+new\n".to_string() + ), + Event::ToolEnd { + id: "patch-1".to_string(), + output: String::new() + } + ] ); }