Skip to content

Commit 1f4f96f

Browse files
evanliu048kiran-garre
andauthored
feat: Adds checkpointing functionality using Git CLI commands (#2896)
* (in progress) Implement checkpointing using git CLI commands * feat: Add new checkpointing functionality using git CLI Updates: - Only works if the user has git installed - Supports auto initialization if the user is in a git repo, manual if not - UI ported over from dedicated file tools implementation * feat: Add user message for turn-level checkpoints, clean command Updates: - The clean subcommand will delete the shadow repo - The description for turn-level checkpoints is a truncated version of the user's last message * fix: Fix shadow repo deletion logic Updates: - Running the clean subcommand now properly deletes the entire shadow repo for both automatic and manual modes * chore: Run formatter and fix clippy warnings * feat: Add checkpoint diff Updates: - Users can now view diffs between checkpoints - Fixed tool-level checkpoint display handling * fix: Fix last messsage handling for checkpoints Updates: - Checkpoints now (hopefully) correctly display the correct turn-specific user message - Added slash command auto completion * fix: Fix commit message handling again * chore: Run formatter * Removed old comment * define a global capture dirctory * revise the capture path * fix cpature clean bug * add a clean all flag * add auto drop method for capture feature * support file details when expand * add the file summary when list and expand * revise structure and print no diff msg * delete all flag, add summry when fs read * refactor code * revise ui * add capture into experiement * clippy * rename to checkpoint * reverse false renaming * recover history * disable tangent mode in checkpoint * fix cr * nit: keep checkpoint name * allow both tangent & checkpoint enabled * ci --------- Co-authored-by: kiran-garre <[email protected]>
1 parent 0c23526 commit 1f4f96f

File tree

11 files changed

+1287
-4
lines changed

11 files changed

+1287
-4
lines changed

crates/chat-cli/src/cli/chat/checkpoint.rs

Lines changed: 422 additions & 0 deletions
Large diffs are not rendered by default.

crates/chat-cli/src/cli/chat/cli/checkpoint.rs

Lines changed: 573 additions & 0 deletions
Large diffs are not rendered by default.

crates/chat-cli/src/cli/chat/cli/experiment.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ static AVAILABLE_EXPERIMENTS: &[Experiment] = &[
5050
description: "Enables Q to create todo lists that can be viewed and managed using /todos",
5151
setting_key: Setting::EnabledTodoList,
5252
},
53+
Experiment {
54+
name: "Checkpoint",
55+
description: concat!(
56+
"Enables workspace checkpoints to snapshot, list, expand, diff, and restore files (/checkpoint)\n",
57+
" ",
58+
"Cannot be used in tangent mode (to avoid mixing up conversation history)"
59+
),
60+
setting_key: Setting::EnabledCheckpoint,
61+
},
5362
Experiment {
5463
name: "Context Usage Indicator",
5564
description: "Shows context usage percentage in the prompt (e.g., [rust-agent] 6% >)",

crates/chat-cli/src/cli/chat/cli/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod changelog;
2+
pub mod checkpoint;
23
pub mod clear;
34
pub mod compact;
45
pub mod context;
@@ -35,6 +36,7 @@ use tangent::TangentArgs;
3536
use todos::TodoSubcommand;
3637
use tools::ToolsArgs;
3738

39+
use crate::cli::chat::cli::checkpoint::CheckpointSubcommand;
3840
use crate::cli::chat::cli::subscribe::SubscribeArgs;
3941
use crate::cli::chat::cli::usage::UsageArgs;
4042
use crate::cli::chat::consts::AGENT_MIGRATION_DOC_URL;
@@ -102,6 +104,8 @@ pub enum SlashCommand {
102104
Persist(PersistSubcommand),
103105
// #[command(flatten)]
104106
// Root(RootSubcommand),
107+
#[command(subcommand)]
108+
Checkpoint(CheckpointSubcommand),
105109
/// View, manage, and resume to-do lists
106110
#[command(subcommand)]
107111
Todos(TodoSubcommand),
@@ -169,6 +173,7 @@ impl SlashCommand {
169173
// skip_printing_tools: true,
170174
// })
171175
// },
176+
Self::Checkpoint(subcommand) => subcommand.execute(os, session).await,
172177
Self::Todos(subcommand) => subcommand.execute(os, session).await,
173178
}
174179
}
@@ -198,6 +203,7 @@ impl SlashCommand {
198203
PersistSubcommand::Save { .. } => "save",
199204
PersistSubcommand::Load { .. } => "load",
200205
},
206+
Self::Checkpoint(_) => "checkpoint",
201207
Self::Todos(_) => "todos",
202208
}
203209
}

crates/chat-cli/src/cli/chat/cli/tangent.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,22 @@ impl TangentArgs {
6565

6666
match self.subcommand {
6767
Some(TangentSubcommand::Tail) => {
68+
// Check if checkpoint is enabled
69+
if os
70+
.database
71+
.settings
72+
.get_bool(Setting::EnabledCheckpoint)
73+
.unwrap_or(false)
74+
{
75+
execute!(
76+
session.stderr,
77+
style::SetForegroundColor(Color::Yellow),
78+
style::Print(
79+
"⚠️ Checkpoint is disabled while in tangent mode. Please exit tangent mode if you want to use checkpoint.\n"
80+
),
81+
style::SetForegroundColor(Color::Reset),
82+
)?;
83+
}
6884
if session.conversation.is_in_tangent_mode() {
6985
let duration_seconds = session.conversation.get_tangent_duration_seconds().unwrap_or(0);
7086
session.conversation.exit_tangent_mode_with_tail();
@@ -106,6 +122,23 @@ impl TangentArgs {
106122
style::SetForegroundColor(Color::Reset)
107123
)?;
108124
} else {
125+
// Check if checkpoint is enabled
126+
if os
127+
.database
128+
.settings
129+
.get_bool(Setting::EnabledCheckpoint)
130+
.unwrap_or(false)
131+
{
132+
execute!(
133+
session.stderr,
134+
style::SetForegroundColor(Color::Yellow),
135+
style::Print(
136+
"⚠️ Checkpoint is disabled while in tangent mode. Please exit tangent mode if you want to use checkpoint.\n"
137+
),
138+
style::SetForegroundColor(Color::Reset),
139+
)?;
140+
}
141+
109142
session.conversation.enter_tangent_mode();
110143

111144
// Get the configured tangent mode key for display

crates/chat-cli/src/cli/chat/conversation.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ use crate::cli::agent::hook::{
7474
HookTrigger,
7575
};
7676
use crate::cli::chat::ChatError;
77+
use crate::cli::chat::checkpoint::{
78+
Checkpoint,
79+
CheckpointManager,
80+
};
7781
use crate::cli::chat::cli::model::{
7882
ModelInfo,
7983
get_model_info,
@@ -138,6 +142,8 @@ pub struct ConversationState {
138142
/// Maps from a file path to [FileLineTracker]
139143
#[serde(default)]
140144
pub file_line_tracker: HashMap<String, FileLineTracker>,
145+
146+
pub checkpoint_manager: Option<CheckpointManager>,
141147
#[serde(default = "default_true")]
142148
pub mcp_enabled: bool,
143149
/// Tangent mode checkpoint - stores main conversation when in tangent mode
@@ -203,6 +209,7 @@ impl ConversationState {
203209
model: None,
204210
model_info: model,
205211
file_line_tracker: HashMap::new(),
212+
checkpoint_manager: None,
206213
mcp_enabled,
207214
tangent_state: None,
208215
}
@@ -891,6 +898,20 @@ Return only the JSON configuration, no additional text.",
891898
self.transcript.push_back(message);
892899
}
893900

901+
/// Restore conversation from a checkpoint's history snapshot
902+
pub fn restore_to_checkpoint(&mut self, checkpoint: &Checkpoint) -> Result<(), eyre::Report> {
903+
// 1. Restore history from snapshot
904+
self.history = checkpoint.history_snapshot.clone();
905+
906+
// 2. Clear any pending next message (uncommitted state)
907+
self.next_message = None;
908+
909+
// 3. Update valid history range
910+
self.valid_history_range = (0, self.history.len());
911+
912+
Ok(())
913+
}
914+
894915
/// Swapping agent involves the following:
895916
/// - Reinstantiate the context manager
896917
/// - Swap agent on tool manager

0 commit comments

Comments
 (0)