feat: added a .fork command to fork a new session from a running conversation
This commit is contained in:
@@ -633,6 +633,43 @@ impl RequestContext {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fork_session(&mut self, fork_name: Option<&str>) -> Result<()> {
|
||||
let current_name = match &self.session {
|
||||
Some(s) => s.name().to_string(),
|
||||
None => bail!("No active session to fork"),
|
||||
};
|
||||
|
||||
let fork_name: String = match fork_name {
|
||||
Some(name) => name.to_string(),
|
||||
None => {
|
||||
let base = fork_base_name(¤t_name);
|
||||
let sessions_dir = self.sessions_dir();
|
||||
(1_u32..)
|
||||
.map(|n| format!("{base}-fork-{n}"))
|
||||
.find(|name| !sessions_dir.join(format!("{name}.yaml")).exists())
|
||||
.unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
let fork_path = self.session_file(&fork_name);
|
||||
if fork_path.exists() {
|
||||
bail!("Session '{}' already exists", fork_name);
|
||||
}
|
||||
|
||||
self.save_session(None)?;
|
||||
|
||||
let session = self.session.as_ref().unwrap();
|
||||
let mut fork = session.clone();
|
||||
fork.set_name(fork_name.clone());
|
||||
fork.clear_autoname();
|
||||
fork.save(&fork_name, &fork_path, self.working_mode.is_repl())?;
|
||||
|
||||
self.session = Some(fork);
|
||||
println!("Forked '{current_name}' → '{fork_name}'");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn empty_session(&mut self) -> Result<()> {
|
||||
if let Some(session) = self.session.as_mut() {
|
||||
if let Some(agent) = self.agent.as_ref() {
|
||||
@@ -4053,6 +4090,17 @@ impl RequestContext {
|
||||
}
|
||||
}
|
||||
|
||||
fn fork_base_name(name: &str) -> &str {
|
||||
if let Some(pos) = name.rfind("-fork-") {
|
||||
let suffix = &name[pos + 6..];
|
||||
if !suffix.is_empty() && suffix.chars().all(|c| c.is_ascii_digit()) {
|
||||
return &name[..pos];
|
||||
}
|
||||
}
|
||||
|
||||
name
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::mcp_factory::McpFactory;
|
||||
@@ -5456,6 +5504,32 @@ mod tests {
|
||||
assert!(!ctx.working_mode.is_cmd());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fork_base_name_strips_fork_suffix() {
|
||||
assert_eq!(fork_base_name("my-session-fork-1"), "my-session");
|
||||
assert_eq!(fork_base_name("my-session-fork-42"), "my-session");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fork_base_name_leaves_plain_names_unchanged() {
|
||||
assert_eq!(fork_base_name("my-session"), "my-session");
|
||||
assert_eq!(fork_base_name("research"), "research");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fork_base_name_ignores_non_numeric_suffix() {
|
||||
assert_eq!(fork_base_name("my-session-fork-abc"), "my-session-fork-abc");
|
||||
assert_eq!(fork_base_name("my-session-fork-"), "my-session-fork-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fork_base_name_flattens_nested_forks() {
|
||||
assert_eq!(
|
||||
fork_base_name("my-session-fork-1-fork-2"),
|
||||
"my-session-fork-1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_file_returns_yaml_path() {
|
||||
let ctx = create_test_ctx();
|
||||
|
||||
@@ -177,6 +177,14 @@ impl Session {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
|
||||
pub fn clear_autoname(&mut self) {
|
||||
self.autoname = None;
|
||||
}
|
||||
|
||||
pub fn role_name(&self) -> Option<&str> {
|
||||
self.role_name.as_deref()
|
||||
}
|
||||
@@ -1046,4 +1054,13 @@ mod tests {
|
||||
session.set_autonaming(true);
|
||||
assert!(!session.need_autoname());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_set_name_updates_name() {
|
||||
let mut session = Session::default();
|
||||
|
||||
session.set_name("my-fork".to_string());
|
||||
|
||||
assert_eq!(session.name(), "my-fork");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user