refactor: extract args.rs and session.rs, merge remote refactoring
This commit is contained in:
parent
59d6e9d58c
commit
3d011a50a7
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -7,15 +7,6 @@ use std::path::{Path, PathBuf};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::UNIX_EPOCH;
|
use std::time::UNIX_EPOCH;
|
||||||
|
|
||||||
pub(crate) struct LiveCli {
|
|
||||||
model: String,
|
|
||||||
allowed_tools: Option<AllowedToolSet>,
|
|
||||||
permission_mode: PermissionMode,
|
|
||||||
system_prompt: Vec<String>,
|
|
||||||
runtime: BuiltRuntime,
|
|
||||||
session: SessionHandle,
|
|
||||||
prompt_history: Vec<PromptHistoryEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LiveCli {
|
impl LiveCli {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,233 @@
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::time::UNIX_EPOCH;
|
||||||
|
|
||||||
|
use commands::{render_slash_command_help_filtered, SlashCommand};
|
||||||
|
use runtime::Session;
|
||||||
|
|
||||||
|
use crate::constants::*;
|
||||||
|
use crate::models::*;
|
||||||
|
|
||||||
|
pub(crate) fn current_session_store() -> Result<runtime::SessionStore, Box<dyn std::error::Error>> {
|
||||||
|
let cwd = env::current_dir()?;
|
||||||
|
runtime::SessionStore::from_cwd(&cwd).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sessions_dir() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
|
Ok(current_session_store()?.sessions_dir().to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_cli_session() -> Result<Session, Box<dyn std::error::Error>> {
|
||||||
|
Ok(Session::new().with_workspace_root(env::current_dir()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_managed_session_handle(
|
||||||
|
session_id: &str,
|
||||||
|
) -> Result<SessionHandle, Box<dyn std::error::Error>> {
|
||||||
|
let handle = current_session_store()?.create_handle(session_id);
|
||||||
|
Ok(SessionHandle {
|
||||||
|
id: handle.id,
|
||||||
|
path: handle.path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn resolve_session_reference(reference: &str) -> Result<SessionHandle, Box<dyn std::error::Error>> {
|
||||||
|
let handle = current_session_store()?
|
||||||
|
.resolve_reference(reference)
|
||||||
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
||||||
|
Ok(SessionHandle {
|
||||||
|
id: handle.id,
|
||||||
|
path: handle.path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn resolve_managed_session_path(session_id: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
|
current_session_store()?
|
||||||
|
.resolve_managed_path(session_id)
|
||||||
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn list_managed_sessions() -> Result<Vec<ManagedSessionSummary>, Box<dyn std::error::Error>> {
|
||||||
|
Ok(current_session_store()?
|
||||||
|
.list_sessions()
|
||||||
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|session| ManagedSessionSummary {
|
||||||
|
id: session.id,
|
||||||
|
path: session.path,
|
||||||
|
updated_at_ms: session.updated_at_ms,
|
||||||
|
modified_epoch_millis: session.modified_epoch_millis,
|
||||||
|
message_count: session.message_count,
|
||||||
|
parent_session_id: session.parent_session_id,
|
||||||
|
branch_name: session.branch_name,
|
||||||
|
lifecycle: SessionLifecycleSummary {
|
||||||
|
kind: SessionLifecycleKind::SavedOnly,
|
||||||
|
pane_id: None,
|
||||||
|
pane_command: None,
|
||||||
|
pane_path: None,
|
||||||
|
workspace_dirty: false,
|
||||||
|
abandoned: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn latest_managed_session() -> Result<ManagedSessionSummary, Box<dyn std::error::Error>> {
|
||||||
|
let session = current_session_store()?
|
||||||
|
.latest_session()
|
||||||
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
||||||
|
Ok(ManagedSessionSummary {
|
||||||
|
id: session.id,
|
||||||
|
path: session.path,
|
||||||
|
updated_at_ms: session.updated_at_ms,
|
||||||
|
modified_epoch_millis: session.modified_epoch_millis,
|
||||||
|
message_count: session.message_count,
|
||||||
|
parent_session_id: session.parent_session_id,
|
||||||
|
branch_name: session.branch_name,
|
||||||
|
lifecycle: SessionLifecycleSummary {
|
||||||
|
kind: SessionLifecycleKind::SavedOnly,
|
||||||
|
pane_id: None,
|
||||||
|
pane_command: None,
|
||||||
|
pane_path: None,
|
||||||
|
workspace_dirty: false,
|
||||||
|
abandoned: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_session_reference(
|
||||||
|
reference: &str,
|
||||||
|
) -> Result<(SessionHandle, Session), Box<dyn std::error::Error>> {
|
||||||
|
let loaded = current_session_store()?
|
||||||
|
.load_session(reference)
|
||||||
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
||||||
|
Ok((
|
||||||
|
SessionHandle {
|
||||||
|
id: loaded.handle.id,
|
||||||
|
path: loaded.handle.path,
|
||||||
|
},
|
||||||
|
loaded.session,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn delete_managed_session(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if !path.exists() {
|
||||||
|
return Err(format!("session file does not exist: {}", path.display()).into());
|
||||||
|
}
|
||||||
|
fs::remove_file(path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn confirm_session_deletion(session_id: &str) -> bool {
|
||||||
|
print!("Delete session '{session_id}'? This cannot be undone. [y/N]: ");
|
||||||
|
io::stdout().flush().unwrap_or(());
|
||||||
|
let mut answer = String::new();
|
||||||
|
if io::stdin().read_line(&mut answer).is_err() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
matches!(answer.trim(), "y" | "Y" | "yes" | "Yes" | "YES")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render_session_list(active_session_id: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let sessions = list_managed_sessions()?;
|
||||||
|
let mut lines = vec![
|
||||||
|
"Sessions".to_string(),
|
||||||
|
format!(" Directory {}", sessions_dir()?.display()),
|
||||||
|
];
|
||||||
|
if sessions.is_empty() {
|
||||||
|
lines.push(" No managed sessions saved yet.".to_string());
|
||||||
|
return Ok(lines.join("\n"));
|
||||||
|
}
|
||||||
|
for session in sessions {
|
||||||
|
let marker = if session.id == active_session_id {
|
||||||
|
"● current"
|
||||||
|
} else {
|
||||||
|
"○ saved"
|
||||||
|
};
|
||||||
|
let lineage = match (
|
||||||
|
session.branch_name.as_deref(),
|
||||||
|
session.parent_session_id.as_deref(),
|
||||||
|
) {
|
||||||
|
(Some(branch_name), Some(parent_session_id)) => {
|
||||||
|
format!(" branch={branch_name} from={parent_session_id}")
|
||||||
|
}
|
||||||
|
(None, Some(parent_session_id)) => format!(" from={parent_session_id}"),
|
||||||
|
(Some(branch_name), None) => format!(" branch={branch_name}"),
|
||||||
|
(None, None) => String::new(),
|
||||||
|
};
|
||||||
|
lines.push(format!(
|
||||||
|
" {id:<20} {marker:<10} msgs={msgs:<4} modified={modified}{lineage} path={path}",
|
||||||
|
id = session.id,
|
||||||
|
msgs = session.message_count,
|
||||||
|
modified = format_session_modified_age(session.modified_epoch_millis),
|
||||||
|
lineage = lineage,
|
||||||
|
path = session.path.display(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(lines.join("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn format_session_modified_age(modified_epoch_millis: u128) -> String {
|
||||||
|
let now = std::time::SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.ok()
|
||||||
|
.map_or(modified_epoch_millis, |duration| duration.as_millis());
|
||||||
|
let delta_seconds = now
|
||||||
|
.saturating_sub(modified_epoch_millis)
|
||||||
|
.checked_div(1_000)
|
||||||
|
.unwrap_or_default();
|
||||||
|
match delta_seconds {
|
||||||
|
0..=4 => "just-now".to_string(),
|
||||||
|
5..=59 => format!("{delta_seconds}s-ago"),
|
||||||
|
60..=3_599 => format!("{}m-ago", delta_seconds / 60),
|
||||||
|
3_600..=86_399 => format!("{}h-ago", delta_seconds / 3_600),
|
||||||
|
_ => format!("{}d-ago", delta_seconds / 86_400),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn write_session_clear_backup(
|
||||||
|
session: &Session,
|
||||||
|
session_path: &Path,
|
||||||
|
) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
|
let backup_path = session_clear_backup_path(session_path);
|
||||||
|
session.save_to_path(&backup_path)?;
|
||||||
|
Ok(backup_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn session_clear_backup_path(session_path: &Path) -> PathBuf {
|
||||||
|
let timestamp = std::time::SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.ok()
|
||||||
|
.map_or(0, |duration| duration.as_millis());
|
||||||
|
let file_name = session_path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|value| value.to_str())
|
||||||
|
.unwrap_or("session.jsonl");
|
||||||
|
session_path.with_file_name(format!("{file_name}.before-clear-{timestamp}.bak"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render_repl_help() -> String {
|
||||||
|
[
|
||||||
|
"REPL".to_string(),
|
||||||
|
" /exit Quit the REPL".to_string(),
|
||||||
|
" /quit Quit the REPL".to_string(),
|
||||||
|
" Up/Down Navigate prompt history".to_string(),
|
||||||
|
" Ctrl-R Reverse-search prompt history".to_string(),
|
||||||
|
" Tab Complete commands, modes, and recent sessions".to_string(),
|
||||||
|
" Ctrl-C Clear input (or exit on empty prompt)".to_string(),
|
||||||
|
" Shift+Enter/Ctrl+J Insert a newline".to_string(),
|
||||||
|
" Auto-save .claw/sessions/<session-id>.jsonl".to_string(),
|
||||||
|
" Resume latest /resume latest".to_string(),
|
||||||
|
" Browse sessions /session list".to_string(),
|
||||||
|
" Show prompt history /history [count]".to_string(),
|
||||||
|
String::new(),
|
||||||
|
render_slash_command_help_filtered(STUB_COMMANDS),
|
||||||
|
]
|
||||||
|
.join(
|
||||||
|
"
|
||||||
|
",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue