Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions crates/chat-cli/src/cli/chat/auto_save.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::database::settings::Setting;
use crate::os::Os;
use crate::cli::chat::conversation::ConversationState;
use chrono::Local;
use tracing::warn;

pub struct AutoSaveManager {
session_filename: Option<String>,
}

impl AutoSaveManager {
pub fn new() -> Self {
Self {
session_filename: None,
}
}

pub async fn auto_save_if_enabled(
&mut self,
os: &Os,
conversation: &ConversationState,
) -> Result<(), Box<dyn std::error::Error>> {
// Check if auto-save is enabled
let auto_save_enabled = os.database.settings.get_bool(Setting::ChatEnableAutoSave).unwrap_or(false);
tracing::info!("Auto-save check: enabled={}", auto_save_enabled);

if !auto_save_enabled {
return Ok(());
}

// Generate filename on first save
if self.session_filename.is_none() {
let pattern = os.database.settings
.get_string(Setting::ChatAutoSavePath)
.unwrap_or_else(|| "auto-save-{timestamp}.json".to_string());

let timestamp = Local::now().format("%Y%m%d-%H%M%S");
let filename = pattern.replace("{timestamp}", &timestamp.to_string());
tracing::info!("Auto-save: generating filename: {}", filename);
self.session_filename = Some(filename);
}

// Execute auto-save
if let Some(filename) = &self.session_filename {
tracing::info!("Auto-save: attempting to save to {}", filename);
match serde_json::to_string_pretty(conversation) {
Ok(contents) => {
match os.fs.write(filename, contents).await {
Ok(_) => tracing::info!("Auto-save: successfully saved to {}", filename),
Err(e) => warn!("Auto-save failed: {}", e),
}
}
Err(e) => {
warn!("Auto-save serialization failed: {}", e);
}
}
}

Ok(())
}
}
13 changes: 13 additions & 0 deletions crates/chat-cli/src/cli/chat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod cli;
mod consts;
pub mod context;
mod conversation;
mod auto_save;
mod input_source;
mod message;
mod parse;
Expand Down Expand Up @@ -677,6 +678,7 @@ pub struct ChatSession {
prompt_ack_rx: std::sync::mpsc::Receiver<()>,
/// Additional context to be added to the next user message (e.g., delegate task summaries)
pending_additional_context: Option<String>,
auto_save_manager: auto_save::AutoSaveManager,
}

impl ChatSession {
Expand Down Expand Up @@ -814,6 +816,7 @@ impl ChatSession {
wrap,
prompt_ack_rx,
pending_additional_context: None,
auto_save_manager: auto_save::AutoSaveManager::new(),
})
}

Expand Down Expand Up @@ -1172,6 +1175,11 @@ impl ChatSession {
self.tool_turn_start_time = None;
self.reset_user_turn();

// Auto-save conversation if enabled
if let Err(e) = self.auto_save_manager.auto_save_if_enabled(os, &self.conversation).await {
warn!("Auto-save error: {}", e);
}

self.inner = Some(ChatState::PromptUser {
skip_printing_tools: false,
});
Expand Down Expand Up @@ -3207,6 +3215,11 @@ impl ChatSession {
.await;
}

// Auto-save conversation if enabled
if let Err(e) = self.auto_save_manager.auto_save_if_enabled(os, &self.conversation).await {
warn!("Auto-save error: {}", e);
}

Ok(ChatState::PromptUser {
skip_printing_tools: false,
})
Expand Down
9 changes: 9 additions & 0 deletions crates/chat-cli/src/database/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ pub enum Setting {
EnabledCheckpoint,
#[strum(message = "Enable the delegate tool for subagent management (boolean)")]
EnabledDelegate,
#[strum(message = "Enable automatic conversation saving (boolean)")]
ChatEnableAutoSave,
#[strum(message = "Auto-save file path pattern (string)")]
ChatAutoSavePath,
#[strum(message = "Specify UI variant to use (string)")]
UiMode,
}
Expand Down Expand Up @@ -132,6 +136,8 @@ impl AsRef<str> for Setting {
Self::EnabledCheckpoint => "chat.enableCheckpoint",
Self::EnabledContextUsageIndicator => "chat.enableContextUsageIndicator",
Self::EnabledDelegate => "chat.enableDelegate",
Self::ChatEnableAutoSave => "chat.enableAutoSave",
Self::ChatAutoSavePath => "chat.autoSavePath",
Self::UiMode => "chat.uiMode",
}
}
Expand Down Expand Up @@ -182,6 +188,9 @@ impl TryFrom<&str> for Setting {
"chat.enableTodoList" => Ok(Self::EnabledTodoList),
"chat.enableCheckpoint" => Ok(Self::EnabledCheckpoint),
"chat.enableContextUsageIndicator" => Ok(Self::EnabledContextUsageIndicator),
"chat.enableDelegate" => Ok(Self::EnabledDelegate),
"chat.enableAutoSave" => Ok(Self::ChatEnableAutoSave),
"chat.autoSavePath" => Ok(Self::ChatAutoSavePath),
"chat.uiMode" => Ok(Self::UiMode),
_ => Err(DatabaseError::InvalidSetting(value.to_string())),
}
Expand Down