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
12 changes: 11 additions & 1 deletion apps/native/src-tauri/src/evolve/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,15 @@ pub async fn backup_evolve_and_record_changeset(
// Short-circuit: conversational responses made no environment changes.
if evolution.state == EvolutionState::Conversational {
info!("[evolution] Conversational response — skipping git/db workflow");
let evolve_state = evolve_state::get(app).unwrap_or_default();
let evolve_state = evolve_state::set(
app,
EvolveState {
last_evolution_state: Some(EvolutionState::Conversational),
..evolve_state::get(app).unwrap_or_default()
},
&initial_status.changes,
)
.unwrap_or_else(|_| evolve_state::get(app).unwrap_or_default());
return Ok(EvolutionResult {
change_map: SemanticChangeMap::default(),
git_status: initial_status,
Expand Down Expand Up @@ -271,6 +279,7 @@ pub async fn backup_evolve_and_record_changeset(
evolution_id: db_evolution_id,
current_changeset_id: new_changeset_id,
backup_branch,
last_evolution_state: Some(evolution.state.clone()),
..current_state
},
&final_status.changes,
Expand Down Expand Up @@ -321,6 +330,7 @@ fn restore_after_failure(app: &AppHandle, config_dir: &str, backup_branch: &Opti
app,
EvolveState {
backup_branch: None,
last_evolution_state: Some(EvolutionState::Failed),
..evolve_state::get(app).unwrap_or_default()
},
&[],
Expand Down
1 change: 1 addition & 0 deletions apps/native/src-tauri/src/managed_edits/managed_edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub fn prepare_managed_edit(app: &AppHandle) -> Result<ManagedEditContext> {
rollback_store_path,
rollback_changeset_id,
step: shared_types::EvolveStep::Evolve,
last_evolution_state: None,
},
&pre_edit_status.changes,
)
Expand Down
7 changes: 7 additions & 0 deletions apps/native/src-tauri/src/shared_types/evolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ pub struct EvolveState {
pub rollback_changeset_id: Option<i64>,
/// UI step derived from the routing state.
pub step: EvolveStep,
/// Last terminal state observed for this routing session.
///
/// This supports transition-sensitive behavior when returning to Begin
/// and maybe some other useful things in the future.
#[serde(default)]
pub last_evolution_state: Option<EvolutionState>,
}

impl Default for EvolveState {
Expand All @@ -115,6 +121,7 @@ impl Default for EvolveState {
rollback_store_path: None,
rollback_changeset_id: None,
step: EvolveStep::Begin,
last_evolution_state: None,
}
}
}
Expand Down
50 changes: 42 additions & 8 deletions apps/native/src-tauri/src/state/evolve_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use tauri::{AppHandle, Runtime};
use tauri_plugin_store::StoreExt;

use crate::evolve::session_chat_memory_store;
use crate::shared_types::{EvolveState, EvolveStep};
use crate::shared_types::{EvolutionState, EvolveState, EvolveStep};
use crate::sqlite_types::Change;

impl EvolveState {
Expand All @@ -24,8 +24,12 @@ impl EvolveState {
const EVOLVE_STATE_PATH: &str = "evolve-state.json";
const EVOLVE_STATE_KEY: &str = "evolveState";

fn clear_chat_memory_if_begin(step: &EvolveStep, clear_fn: impl FnOnce()) {
if *step == EvolveStep::Begin {
fn clear_chat_memory_if_begin(
step: &EvolveStep,
preserve_for_conversational_begin: bool,
clear_fn: impl FnOnce(),
) {
if *step == EvolveStep::Begin && !preserve_for_conversational_begin {
clear_fn();
}
}
Expand All @@ -52,6 +56,16 @@ pub fn set<R: Runtime>(
let is_built = crate::state::build_state::current_state_built(app, current_changes);
let has_changes = !current_changes.is_empty();
state.recompute_step(is_built, has_changes);

// Preserve chat memory whenever we are at Begin and the last evolution
// outcome was conversational (no edits). This intentionally persists
// across repeated conversational cycles.
let preserve_for_conversational_begin = state.step == EvolveStep::Begin
&& matches!(
state.last_evolution_state.as_ref(),
Some(EvolutionState::Conversational)
);

let store = app.store(EVOLVE_STATE_PATH)?;
store.set(EVOLVE_STATE_KEY, serde_json::to_value(&state)?);
store.save()?;
Expand All @@ -62,7 +76,9 @@ pub fn set<R: Runtime>(
// Note that `clear` is NOT a suitable place to do this since it is not called
// on all possible transitions back to Begin (e.g. Evolve -> Begin when evolution_id
// is cleared but committable is still true).
clear_chat_memory_if_begin(&state.step, || session_chat_memory_store().clear());
clear_chat_memory_if_begin(&state.step, preserve_for_conversational_begin, || {
session_chat_memory_store().clear()
});

Ok(state)
}
Expand All @@ -79,7 +95,7 @@ mod tests {
#[test]
fn clear_chat_memory_if_begin_calls_clear() {
let mut cleared = false;
clear_chat_memory_if_begin(&EvolveStep::Begin, || {
clear_chat_memory_if_begin(&EvolveStep::Begin, false, || {
cleared = true;
});
assert!(cleared);
Expand All @@ -88,17 +104,35 @@ mod tests {
#[test]
fn clear_chat_memory_if_begin_skips_non_begin_steps() {
let mut cleared = false;
clear_chat_memory_if_begin(&EvolveStep::Evolve, || {
clear_chat_memory_if_begin(&EvolveStep::Evolve, false, || {
cleared = true;
});
assert!(!cleared);

clear_chat_memory_if_begin(&EvolveStep::Commit, false, || {
cleared = true;
});
assert!(!cleared);
}

clear_chat_memory_if_begin(&EvolveStep::Commit, || {
#[test]
fn clear_chat_memory_if_begin_skips_when_last_state_is_conversational() {
let mut cleared = false;
clear_chat_memory_if_begin(&EvolveStep::Begin, true, || {
cleared = true;
});
assert!(!cleared);
}

#[test]
fn clear_chat_memory_if_begin_clears_for_non_conversational_last_state() {
let mut cleared = false;
clear_chat_memory_if_begin(&EvolveStep::Begin, false, || {
cleared = true;
});
assert!(cleared);
}

#[test]
fn recomputed_begin_triggers_clear_logic() {
let mut state = EvolveState {
Expand All @@ -109,7 +143,7 @@ mod tests {
state.recompute_step(true, false);

let mut cleared = false;
clear_chat_memory_if_begin(&state.step, || {
clear_chat_memory_if_begin(&state.step, false, || {
cleared = true;
});

Expand Down
Loading