Fix #258: preserve unsaved headless MCP work with opt-in checkpoints

This commit is contained in:
jonassneu
2026-10-08 14:38:24 +02:00
parent 52ab38fcff
commit 9d9f8ecbb4
12 changed files with 682 additions and 18 deletions
+3 -2
View File
@@ -239,11 +239,12 @@ fn auto_save(s: &mut Session, _: &Value) -> Result<Value> {
fn recovery_info(s: &mut Session, _: &Value) -> Result<Value> {
let root = s.default_autosave_root();
let latest = crate::autosave::latest_in(s.file_ops(), &s.prefs, s.path.as_deref(), root.as_deref()).map(|p| p.to_string_lossy().to_string());
let prefs = s.autosave_prefs();
let latest = crate::autosave::latest_in(s.file_ops(), &prefs, s.path.as_deref(), root.as_deref()).map(|p| p.to_string_lossy().to_string());
Ok(json!({
"lastAutoSave": s.autosave.last_path,
"latestOnDisk": latest,
"folder": crate::autosave::folder(&s.prefs, s.path.as_deref(), root.as_deref()).map(|p| p.to_string_lossy().to_string()),
"folder": crate::autosave::folder(&prefs, s.path.as_deref(), root.as_deref()).map(|p| p.to_string_lossy().to_string()),
"recentProjects": s.prefs.recent_projects,
}))
}
+4
View File
@@ -396,6 +396,9 @@ pub struct Session {
pub shortcut_table: std::sync::OnceLock<shortcuts::ShortcutTable>,
/// Auto-save bookkeeping.
pub autosave: autosave::AutoSaveState,
/// Host-owned isolation for all auto-saves (e.g. one MCP session). Independent of settings
/// and project bookkeeping, so opening/saving a project or changing preferences keeps it.
pub autosave_folder_override: Option<std::path::PathBuf>,
/// The viewer snapshot (Take Snapshot / Show Snapshot).
pub snapshot: Option<viewer::Snapshot>,
/// The JavaScript scripting engine (set by the host that links `effectcraft-script`):
@@ -504,6 +507,7 @@ impl Default for Session {
ui_commands: vec![],
shortcut_table: std::sync::OnceLock::new(),
autosave: autosave::AutoSaveState::default(),
autosave_folder_override: None,
snapshot: None,
script: None,
plugin_loader: None,
+15 -2
View File
@@ -200,6 +200,17 @@ impl Session {
}
}
/// Auto-save preferences with the host's isolation folder applied, without changing or
/// persisting the user's settings.
pub fn autosave_prefs(&self) -> crate::prefs::Prefs {
let mut prefs = self.prefs.clone();
if let Some(folder) = &self.autosave_folder_override {
prefs.auto_save.location = "custom".into();
prefs.auto_save.folder = folder.to_string_lossy().into_owned();
}
prefs
}
/// Write an auto-save now (whether or not the project is dirty). Returns its path.
///
/// With [`autosave::AutoSaveState::background`] the project is serialised and written on a
@@ -209,8 +220,9 @@ impl Session {
// One write at a time: the previous one must land before its slot rotates.
self.autosave_wait()?;
let root = self.default_autosave_root();
let prefs = self.autosave_prefs();
let fail = |e: std::io::Error| EngineError::Other(format!("auto-save failed: {e}"));
let plan = autosave::plan_in(self.file_ops(), &self.prefs, self.path.as_deref(), root.as_deref(), self.autosave.last_slot).map_err(fail)?;
let plan = autosave::plan_in(self.file_ops(), &prefs, self.path.as_deref(), root.as_deref(), self.autosave.last_slot).map_err(fail)?;
if self.autosave.background && !cfg!(target_arch = "wasm32") {
let project = self.project.clone();
let config = self.config.clone();
@@ -267,7 +279,8 @@ impl Session {
if !self.is_dirty() || self.autosave.saved_revision == Some(self.revision) || self.render_job.is_some() {
return None;
}
if self.path.is_none() && self.prefs.auto_save.location != "custom" && self.default_autosave_root().is_none() {
if self.path.is_none() && self.prefs.auto_save.location != "custom" && self.default_autosave_root().is_none() && self.autosave_folder_override.is_none()
{
return None;
}
let r = self.autosave_now();