Skip to content

Commit d56631d

Browse files
ConradIrwintidely
authored andcommitted
Only reject agent actions, don't restore checkpoint on revert (zed-industries#37801)
Updates zed-industries#37623 Release Notes: - Changed the behaviour when editing an old message in a native agent thread. Prior to this, it would automatically restore the checkpoint (which could lead to a surprising amount of work being discarded). Now it will just reject any unaccepted agent edits, and you can use the "restore checkpoint" button for the original behavior.
1 parent a1745c1 commit d56631d

File tree

2 files changed

+49
-39
lines changed

2 files changed

+49
-39
lines changed

crates/acp_thread/src/acp_thread.rs

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,37 +1640,56 @@ impl AcpThread {
16401640
cx.foreground_executor().spawn(send_task)
16411641
}
16421642

1643-
/// Rewinds this thread to before the entry at `index`, removing it and all
1644-
/// subsequent entries while reverting any changes made from that point.
1645-
pub fn rewind(&mut self, id: UserMessageId, cx: &mut Context<Self>) -> Task<Result<()>> {
1646-
let Some(truncate) = self.connection.truncate(&self.session_id, cx) else {
1647-
return Task::ready(Err(anyhow!("not supported")));
1648-
};
1649-
let Some(message) = self.user_message(&id) else {
1643+
/// Restores the git working tree to the state at the given checkpoint (if one exists)
1644+
pub fn restore_checkpoint(
1645+
&mut self,
1646+
id: UserMessageId,
1647+
cx: &mut Context<Self>,
1648+
) -> Task<Result<()>> {
1649+
let Some((_, message)) = self.user_message_mut(&id) else {
16501650
return Task::ready(Err(anyhow!("message not found")));
16511651
};
16521652

16531653
let checkpoint = message
16541654
.checkpoint
16551655
.as_ref()
16561656
.map(|c| c.git_checkpoint.clone());
1657-
1657+
let rewind = self.rewind(id.clone(), cx);
16581658
let git_store = self.project.read(cx).git_store().clone();
1659-
cx.spawn(async move |this, cx| {
1659+
1660+
cx.spawn(async move |_, cx| {
1661+
rewind.await?;
16601662
if let Some(checkpoint) = checkpoint {
16611663
git_store
16621664
.update(cx, |git, cx| git.restore_checkpoint(checkpoint, cx))?
16631665
.await?;
16641666
}
16651667

1668+
Ok(())
1669+
})
1670+
}
1671+
1672+
/// Rewinds this thread to before the entry at `index`, removing it and all
1673+
/// subsequent entries while rejecting any action_log changes made from that point.
1674+
/// Unlike `restore_checkpoint`, this method does not restore from git.
1675+
pub fn rewind(&mut self, id: UserMessageId, cx: &mut Context<Self>) -> Task<Result<()>> {
1676+
let Some(truncate) = self.connection.truncate(&self.session_id, cx) else {
1677+
return Task::ready(Err(anyhow!("not supported")));
1678+
};
1679+
1680+
cx.spawn(async move |this, cx| {
16661681
cx.update(|cx| truncate.run(id.clone(), cx))?.await?;
16671682
this.update(cx, |this, cx| {
16681683
if let Some((ix, _)) = this.user_message_mut(&id) {
16691684
let range = ix..this.entries.len();
16701685
this.entries.truncate(ix);
16711686
cx.emit(AcpThreadEvent::EntriesRemoved(range));
16721687
}
1673-
})
1688+
this.action_log()
1689+
.update(cx, |action_log, cx| action_log.reject_all_edits(cx))
1690+
})?
1691+
.await;
1692+
Ok(())
16741693
})
16751694
}
16761695

@@ -1727,20 +1746,6 @@ impl AcpThread {
17271746
})
17281747
}
17291748

1730-
fn user_message(&self, id: &UserMessageId) -> Option<&UserMessage> {
1731-
self.entries.iter().find_map(|entry| {
1732-
if let AgentThreadEntry::UserMessage(message) = entry {
1733-
if message.id.as_ref() == Some(id) {
1734-
Some(message)
1735-
} else {
1736-
None
1737-
}
1738-
} else {
1739-
None
1740-
}
1741-
})
1742-
}
1743-
17441749
fn user_message_mut(&mut self, id: &UserMessageId) -> Option<(usize, &mut UserMessage)> {
17451750
self.entries.iter_mut().enumerate().find_map(|(ix, entry)| {
17461751
if let AgentThreadEntry::UserMessage(message) = entry {
@@ -2684,7 +2689,7 @@ mod tests {
26842689
let AgentThreadEntry::UserMessage(message) = &thread.entries[2] else {
26852690
panic!("unexpected entries {:?}", thread.entries)
26862691
};
2687-
thread.rewind(message.id.clone().unwrap(), cx)
2692+
thread.restore_checkpoint(message.id.clone().unwrap(), cx)
26882693
})
26892694
.await
26902695
.unwrap();

crates/agent_ui/src/acp/thread_view.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ impl AcpThreadView {
927927
}
928928
}
929929
ViewEvent::MessageEditorEvent(editor, MessageEditorEvent::Send) => {
930-
self.regenerate(event.entry_index, editor, window, cx);
930+
self.regenerate(event.entry_index, editor.clone(), window, cx);
931931
}
932932
ViewEvent::MessageEditorEvent(_editor, MessageEditorEvent::Cancel) => {
933933
self.cancel_editing(&Default::default(), window, cx);
@@ -1151,7 +1151,7 @@ impl AcpThreadView {
11511151
fn regenerate(
11521152
&mut self,
11531153
entry_ix: usize,
1154-
message_editor: &Entity<MessageEditor>,
1154+
message_editor: Entity<MessageEditor>,
11551155
window: &mut Window,
11561156
cx: &mut Context<Self>,
11571157
) {
@@ -1168,16 +1168,18 @@ impl AcpThreadView {
11681168
return;
11691169
};
11701170

1171-
let contents = message_editor.update(cx, |message_editor, cx| message_editor.contents(cx));
1172-
1173-
let task = cx.spawn(async move |_, cx| {
1174-
let contents = contents.await?;
1171+
cx.spawn_in(window, async move |this, cx| {
11751172
thread
11761173
.update(cx, |thread, cx| thread.rewind(user_message_id, cx))?
11771174
.await?;
1178-
Ok(contents)
1179-
});
1180-
self.send_impl(task, window, cx);
1175+
let contents =
1176+
message_editor.update(cx, |message_editor, cx| message_editor.contents(cx))?;
1177+
this.update_in(cx, |this, window, cx| {
1178+
this.send_impl(contents, window, cx);
1179+
})?;
1180+
anyhow::Ok(())
1181+
})
1182+
.detach();
11811183
}
11821184

11831185
fn open_agent_diff(&mut self, _: &OpenAgentDiff, window: &mut Window, cx: &mut Context<Self>) {
@@ -1635,14 +1637,16 @@ impl AcpThreadView {
16351637
cx.notify();
16361638
}
16371639

1638-
fn rewind(&mut self, message_id: &UserMessageId, cx: &mut Context<Self>) {
1640+
fn restore_checkpoint(&mut self, message_id: &UserMessageId, cx: &mut Context<Self>) {
16391641
let Some(thread) = self.thread() else {
16401642
return;
16411643
};
1644+
16421645
thread
1643-
.update(cx, |thread, cx| thread.rewind(message_id.clone(), cx))
1646+
.update(cx, |thread, cx| {
1647+
thread.restore_checkpoint(message_id.clone(), cx)
1648+
})
16441649
.detach_and_log_err(cx);
1645-
cx.notify();
16461650
}
16471651

16481652
fn render_entry(
@@ -1712,8 +1716,9 @@ impl AcpThreadView {
17121716
.label_size(LabelSize::XSmall)
17131717
.icon_color(Color::Muted)
17141718
.color(Color::Muted)
1719+
.tooltip(Tooltip::text("Restores all files in the project to the content they had at this point in the conversation."))
17151720
.on_click(cx.listener(move |this, _, _window, cx| {
1716-
this.rewind(&message_id, cx);
1721+
this.restore_checkpoint(&message_id, cx);
17171722
}))
17181723
)
17191724
.child(Divider::horizontal())
@@ -1784,7 +1789,7 @@ impl AcpThreadView {
17841789
let editor = editor.clone();
17851790
move |this, _, window, cx| {
17861791
this.regenerate(
1787-
entry_ix, &editor, window, cx,
1792+
entry_ix, editor.clone(), window, cx,
17881793
);
17891794
}
17901795
})).into_any_element()

0 commit comments

Comments
 (0)