agent: Fix issue where created file is not deleted after rejecting (#51745)

Bennet Bo Fenner created

Release Notes:

- N/A

Change summary

crates/agent/src/tools/streaming_edit_file_tool.rs | 63 ++++++++++++++-
1 file changed, 56 insertions(+), 7 deletions(-)

Detailed changes

crates/agent/src/tools/streaming_edit_file_tool.rs 🔗

@@ -528,8 +528,10 @@ impl EditSession {
             }
         }) as Box<dyn FnOnce()>);
 
-        tool.action_log
-            .update(cx, |log, cx| log.buffer_read(buffer.clone(), cx));
+        tool.action_log.update(cx, |log, cx| match mode {
+            StreamingEditFileMode::Write => log.buffer_created(buffer.clone(), cx),
+            StreamingEditFileMode::Edit => log.buffer_read(buffer.clone(), cx),
+        });
 
         let old_snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot());
         let old_text = cx
@@ -568,10 +570,6 @@ impl EditSession {
 
                 let events = self.parser.finalize_content(&content);
                 self.process_events(&events, tool, event_stream, cx)?;
-
-                tool.action_log.update(cx, |log, cx| {
-                    log.buffer_created(self.buffer.clone(), cx);
-                });
             }
             StreamingEditFileMode::Edit => {
                 let edits = input.edits.ok_or_else(|| {
@@ -3711,7 +3709,7 @@ mod tests {
         assert!(
             !changed.is_empty(),
             "action_log.changed_buffers() should be non-empty after streaming edit,
-             but no changed buffers were found \u{2014} Accept All / Reject All will not appear"
+             but no changed buffers were found - Accept All / Reject All will not appear"
         );
     }
 
@@ -3856,6 +3854,57 @@ mod tests {
         assert_eq!(new_text, "new_content");
     }
 
+    #[gpui::test]
+    async fn test_streaming_reject_created_file_deletes_it(cx: &mut TestAppContext) {
+        let (tool, _project, action_log, fs, _thread) = setup_test(cx, json!({"dir": {}})).await;
+        cx.update(|cx| {
+            let mut settings = agent_settings::AgentSettings::get_global(cx).clone();
+            settings.tool_permissions.default = settings::ToolPermissionMode::Allow;
+            agent_settings::AgentSettings::override_global(settings, cx);
+        });
+
+        // Create a new file via the streaming edit file tool
+        let (event_stream, _rx) = ToolCallEventStream::test();
+        let task = cx.update(|cx| {
+            tool.clone().run(
+                ToolInput::resolved(StreamingEditFileToolInput {
+                    display_description: "Create new file".into(),
+                    path: "root/dir/new_file.txt".into(),
+                    mode: StreamingEditFileMode::Write,
+                    content: Some("Hello, World!".into()),
+                    edits: None,
+                }),
+                event_stream,
+                cx,
+            )
+        });
+        let result = task.await;
+        assert!(result.is_ok(), "create should succeed: {:?}", result.err());
+        cx.run_until_parked();
+
+        assert!(
+            fs.is_file(path!("/root/dir/new_file.txt").as_ref()).await,
+            "file should exist after creation"
+        );
+
+        // Reject all edits — this should delete the newly created file
+        let changed = action_log.read_with(cx, |log, cx| log.changed_buffers(cx));
+        assert!(
+            !changed.is_empty(),
+            "action_log should track the created file as changed"
+        );
+
+        action_log
+            .update(cx, |log, cx| log.reject_all_edits(None, cx))
+            .await;
+        cx.run_until_parked();
+
+        assert!(
+            !fs.is_file(path!("/root/dir/new_file.txt").as_ref()).await,
+            "file should be deleted after rejecting creation, but an empty file was left behind"
+        );
+    }
+
     async fn setup_test_with_fs(
         cx: &mut TestAppContext,
         fs: Arc<project::FakeFs>,