Add session continuation symlink fix and /resume command
Fix session detection: - Add save_session_continuation() calls at all session exit points - Sessions now properly create .g3/session symlink for resume detection - Fixes issue where g3 wasn't offering to resume previous sessions Add /resume command: - New list_sessions_for_directory() to scan available sessions - New switch_to_session() method to safely switch between sessions - Shows numbered list with timestamps, context %, and TODO status - Saves current session before switching (can be resumed later) - Restores full context if <80% used, otherwise uses summary - Machine mode supports /resume and /resume <number> Documentation: - Add /clear and /resume to CONTROL_COMMANDS.md - Update /help output with new commands
This commit is contained in:
@@ -23,7 +23,7 @@ pub mod webdriver_session;
|
||||
pub use task_result::TaskResult;
|
||||
pub use retry::{RetryConfig, RetryResult, execute_with_retry, retry_operation};
|
||||
pub use feedback_extraction::{ExtractedFeedback, FeedbackSource, FeedbackExtractionConfig, extract_coach_feedback};
|
||||
pub use session_continuation::{SessionContinuation, load_continuation, save_continuation, clear_continuation, has_valid_continuation, get_session_dir, load_context_from_session_log, find_incomplete_agent_session};
|
||||
pub use session_continuation::{SessionContinuation, load_continuation, save_continuation, clear_continuation, has_valid_continuation, get_session_dir, load_context_from_session_log, find_incomplete_agent_session, list_sessions_for_directory, format_session_time};
|
||||
|
||||
// Re-export context window types
|
||||
pub use context_window::{ContextWindow, ThinScope};
|
||||
@@ -1528,6 +1528,42 @@ impl<W: UiWriter> Agent<W> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Switch to a different session, saving the current one first.
|
||||
/// This discards the current in-memory state and loads the new session.
|
||||
pub fn switch_to_session(
|
||||
&mut self,
|
||||
continuation: &crate::session_continuation::SessionContinuation,
|
||||
) -> Result<bool> {
|
||||
// Save current session first (so it can be resumed later)
|
||||
self.save_session_continuation(None);
|
||||
|
||||
// Reset session-specific metrics
|
||||
self.thinning_events.clear();
|
||||
self.compaction_events.clear();
|
||||
self.first_token_times.clear();
|
||||
self.tool_call_metrics.clear();
|
||||
self.tool_call_count = 0;
|
||||
self.pending_90_compaction = false;
|
||||
|
||||
// Update session ID to the new session
|
||||
self.session_id = Some(continuation.session_id.clone());
|
||||
|
||||
// Update agent mode info from continuation
|
||||
self.is_agent_mode = continuation.is_agent_mode;
|
||||
self.agent_name = continuation.agent_name.clone();
|
||||
|
||||
// Load TODO content from the new session if available
|
||||
if let Some(ref todo) = continuation.todo_snapshot {
|
||||
// Use blocking write since we're in a sync context
|
||||
if let Ok(mut guard) = self.todo_content.try_write() {
|
||||
*guard = todo.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Restore context from the continuation
|
||||
self.restore_from_continuation(continuation)
|
||||
}
|
||||
|
||||
async fn stream_completion(
|
||||
&mut self,
|
||||
request: CompletionRequest,
|
||||
|
||||
@@ -375,6 +375,71 @@ pub fn find_incomplete_agent_session(agent_name: &str) -> Result<Option<SessionC
|
||||
Ok(candidates.into_iter().next())
|
||||
}
|
||||
|
||||
/// List all available sessions in the current working directory.
|
||||
/// Returns sessions sorted by creation time (most recent first).
|
||||
pub fn list_sessions_for_directory() -> Result<Vec<SessionContinuation>> {
|
||||
let sessions_dir = get_sessions_dir();
|
||||
|
||||
if !sessions_dir.exists() {
|
||||
debug!("Sessions directory does not exist: {:?}", sessions_dir);
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let current_dir = std::env::current_dir()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut sessions: Vec<SessionContinuation> = Vec::new();
|
||||
|
||||
// Scan all session directories
|
||||
for entry in std::fs::read_dir(&sessions_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if !path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for latest.json in this session directory
|
||||
let latest_path = path.join(CONTINUATION_FILENAME);
|
||||
if !latest_path.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to load the continuation
|
||||
let json = match std::fs::read_to_string(&latest_path) {
|
||||
Ok(j) => j,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let continuation: SessionContinuation = match serde_json::from_str(&json) {
|
||||
Ok(c) => c,
|
||||
Err(_) => continue, // Skip sessions with old format
|
||||
};
|
||||
|
||||
// Only include sessions from the current working directory
|
||||
if continuation.working_directory == current_dir {
|
||||
sessions.push(continuation);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by created_at descending (most recent first)
|
||||
sessions.sort_by(|a, b| b.created_at.cmp(&a.created_at));
|
||||
|
||||
Ok(sessions)
|
||||
}
|
||||
|
||||
/// Format a session's created_at timestamp for display
|
||||
pub fn format_session_time(created_at: &str) -> String {
|
||||
match chrono::DateTime::parse_from_rfc3339(created_at) {
|
||||
Ok(dt) => {
|
||||
let local: chrono::DateTime<chrono::Local> = dt.into();
|
||||
local.format("%Y-%m-%d %H:%M").to_string()
|
||||
}
|
||||
Err(_) => created_at.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user