feat(M12): complete milestone with plugins, checkpointing, and rewind

Implements the remaining M12 features from AGENTS.md:

**Plugin System (crates/platform/plugins)**
- Plugin manifest schema with plugin.json support
- Plugin loader for commands, agents, skills, hooks, and MCP servers
- Discovers plugins from ~/.config/owlen/plugins and .owlen/plugins
- Includes comprehensive tests (4 passing)

**Session Checkpointing (crates/core/agent)**
- Checkpoint struct capturing session state and file diffs
- CheckpointManager with snapshot, diff, save, load, and rewind capabilities
- File diff tracking with before/after content
- Checkpoint persistence to .owlen/checkpoints/
- Includes comprehensive tests (6 passing)

**REPL Commands (crates/app/cli)**
- /checkpoint - Save current session with file diffs
- /checkpoints - List all saved checkpoints
- /rewind <id> - Restore session and files from checkpoint
- Updated /help documentation

M12 milestone now fully complete:
 /permissions, /status, /cost (previously implemented)
 Checkpointing and /rewind
 Plugin loader with manifest schema

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-01 21:59:08 +01:00
parent 04a7085007
commit 5caf502009
8 changed files with 852 additions and 1 deletions

View File

@@ -466,6 +466,9 @@ async fn main() -> Result<()> {
let mut lines = stdin.lock().lines();
let mut stats = agent_core::SessionStats::new();
let mut history = agent_core::SessionHistory::new();
let mut checkpoint_mgr = agent_core::CheckpointManager::new(
std::path::PathBuf::from(".owlen/checkpoints")
);
loop {
print!("> ");
@@ -487,6 +490,9 @@ async fn main() -> Result<()> {
println!(" /permissions - Show permission settings");
println!(" /cost - Show token usage and timing");
println!(" /history - Show conversation history");
println!(" /checkpoint - Save current session state");
println!(" /checkpoints - List all saved checkpoints");
println!(" /rewind <id> - Restore session from checkpoint");
println!(" /clear - Clear conversation history");
println!(" /exit - Exit interactive mode");
}
@@ -553,6 +559,47 @@ async fn main() -> Result<()> {
println!("\n Tool Calls: {}", history.tool_calls.len());
}
}
"/checkpoint" => {
let checkpoint_id = format!("checkpoint-{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
);
match checkpoint_mgr.save_checkpoint(
checkpoint_id.clone(),
stats.clone(),
&history,
) {
Ok(checkpoint) => {
println!("\n💾 Checkpoint saved: {}", checkpoint_id);
if !checkpoint.file_diffs.is_empty() {
println!(" Files tracked: {}", checkpoint.file_diffs.len());
}
}
Err(e) => {
eprintln!("\n❌ Failed to save checkpoint: {}", e);
}
}
}
"/checkpoints" => {
match checkpoint_mgr.list_checkpoints() {
Ok(checkpoints) => {
if checkpoints.is_empty() {
println!("\n📋 No checkpoints saved yet");
} else {
println!("\n📋 Saved Checkpoints:");
for (i, cp_id) in checkpoints.iter().enumerate() {
println!(" [{}] {}", i + 1, cp_id);
}
println!("\n Use /rewind <id> to restore");
}
}
Err(e) => {
eprintln!("\n❌ Failed to list checkpoints: {}", e);
}
}
}
"/clear" => {
history.clear();
stats = agent_core::SessionStats::new();
@@ -562,6 +609,31 @@ async fn main() -> Result<()> {
println!("\n👋 Goodbye!");
break;
}
cmd if cmd.starts_with("/rewind ") => {
let checkpoint_id = cmd.strip_prefix("/rewind ").unwrap().trim();
match checkpoint_mgr.rewind_to(checkpoint_id) {
Ok(restored_files) => {
println!("\n⏪ Rewound to checkpoint: {}", checkpoint_id);
if !restored_files.is_empty() {
println!(" Restored files:");
for file in restored_files {
println!(" - {}", file.display());
}
}
// Load the checkpoint to restore history and stats
if let Ok(checkpoint) = checkpoint_mgr.load_checkpoint(checkpoint_id) {
stats = checkpoint.stats;
history.user_prompts = checkpoint.user_prompts;
history.assistant_responses = checkpoint.assistant_responses;
history.tool_calls = checkpoint.tool_calls;
println!(" Session state restored");
}
}
Err(e) => {
eprintln!("\n❌ Failed to rewind: {}", e);
}
}
}
_ => {
println!("\n❌ Unknown command: {}", input);
println!(" Type /help for available commands");