Writes the current context window to logs/current_context_window (uses a symlink to a session ID). This PR was unfortunately generated by a different LLM and did a ton of superficial reformating, it's actually a fairly small and benign change, but I don't want to roll back everything. Hope that's ok.
271 lines
8.0 KiB
Rust
271 lines
8.0 KiB
Rust
//! Status tracking for flock mode
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
|
|
/// Status of an individual segment worker
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SegmentStatus {
|
|
/// Segment number
|
|
pub segment_id: usize,
|
|
|
|
/// Segment workspace directory
|
|
pub workspace: PathBuf,
|
|
|
|
/// Current state of the segment
|
|
pub state: SegmentState,
|
|
|
|
/// Start time
|
|
pub started_at: DateTime<Utc>,
|
|
|
|
/// Completion time (if finished)
|
|
pub completed_at: Option<DateTime<Utc>>,
|
|
|
|
/// Total tokens used
|
|
pub tokens_used: u64,
|
|
|
|
/// Number of tool calls made
|
|
pub tool_calls: u64,
|
|
|
|
/// Number of errors encountered
|
|
pub errors: u64,
|
|
|
|
/// Current turn number (for autonomous mode)
|
|
pub current_turn: usize,
|
|
|
|
/// Maximum turns allowed
|
|
pub max_turns: usize,
|
|
|
|
/// Last status message
|
|
pub last_message: Option<String>,
|
|
|
|
/// Error message (if failed)
|
|
pub error_message: Option<String>,
|
|
}
|
|
|
|
/// State of a segment worker
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub enum SegmentState {
|
|
/// Waiting to start
|
|
Pending,
|
|
|
|
/// Currently running
|
|
Running,
|
|
|
|
/// Completed successfully
|
|
Completed,
|
|
|
|
/// Failed with error
|
|
Failed,
|
|
|
|
/// Cancelled by user
|
|
Cancelled,
|
|
}
|
|
|
|
impl std::fmt::Display for SegmentState {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
SegmentState::Pending => write!(f, "⏳ Pending"),
|
|
SegmentState::Running => write!(f, "🔄 Running"),
|
|
SegmentState::Completed => write!(f, "✅ Completed"),
|
|
SegmentState::Failed => write!(f, "❌ Failed"),
|
|
SegmentState::Cancelled => write!(f, "⚠️ Cancelled"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Overall flock status
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FlockStatus {
|
|
/// Flock session ID
|
|
pub session_id: String,
|
|
|
|
/// Project directory
|
|
pub project_dir: PathBuf,
|
|
|
|
/// Flock workspace directory
|
|
pub flock_workspace: PathBuf,
|
|
|
|
/// Number of segments
|
|
pub num_segments: usize,
|
|
|
|
/// Start time
|
|
pub started_at: DateTime<Utc>,
|
|
|
|
/// Completion time (if finished)
|
|
pub completed_at: Option<DateTime<Utc>>,
|
|
|
|
/// Status of each segment
|
|
pub segments: HashMap<usize, SegmentStatus>,
|
|
|
|
/// Total tokens used across all segments
|
|
pub total_tokens: u64,
|
|
|
|
/// Total tool calls across all segments
|
|
pub total_tool_calls: u64,
|
|
|
|
/// Total errors across all segments
|
|
pub total_errors: u64,
|
|
}
|
|
|
|
impl FlockStatus {
|
|
/// Create a new flock status
|
|
pub fn new(
|
|
session_id: String,
|
|
project_dir: PathBuf,
|
|
flock_workspace: PathBuf,
|
|
num_segments: usize,
|
|
) -> Self {
|
|
Self {
|
|
session_id,
|
|
project_dir,
|
|
flock_workspace,
|
|
num_segments,
|
|
started_at: Utc::now(),
|
|
completed_at: None,
|
|
segments: HashMap::new(),
|
|
total_tokens: 0,
|
|
total_tool_calls: 0,
|
|
total_errors: 0,
|
|
}
|
|
}
|
|
|
|
/// Update segment status
|
|
pub fn update_segment(&mut self, segment_id: usize, status: SegmentStatus) {
|
|
self.segments.insert(segment_id, status);
|
|
self.recalculate_totals();
|
|
}
|
|
|
|
/// Recalculate total metrics
|
|
fn recalculate_totals(&mut self) {
|
|
self.total_tokens = self.segments.values().map(|s| s.tokens_used).sum();
|
|
self.total_tool_calls = self.segments.values().map(|s| s.tool_calls).sum();
|
|
self.total_errors = self.segments.values().map(|s| s.errors).sum();
|
|
}
|
|
|
|
/// Check if all segments are complete
|
|
pub fn is_complete(&self) -> bool {
|
|
self.segments.len() == self.num_segments
|
|
&& self.segments.values().all(|s| {
|
|
matches!(
|
|
s.state,
|
|
SegmentState::Completed | SegmentState::Failed | SegmentState::Cancelled
|
|
)
|
|
})
|
|
}
|
|
|
|
/// Get count of segments by state
|
|
pub fn count_by_state(&self, state: SegmentState) -> usize {
|
|
self.segments.values().filter(|s| s.state == state).count()
|
|
}
|
|
|
|
/// Save status to file
|
|
pub fn save_to_file(&self, path: &PathBuf) -> anyhow::Result<()> {
|
|
let json = serde_json::to_string_pretty(self)?;
|
|
std::fs::write(path, json)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Load status from file
|
|
pub fn load_from_file(path: &PathBuf) -> anyhow::Result<Self> {
|
|
let json = std::fs::read_to_string(path)?;
|
|
let status = serde_json::from_str(&json)?;
|
|
Ok(status)
|
|
}
|
|
|
|
/// Generate a summary report
|
|
pub fn generate_report(&self) -> String {
|
|
let mut report = String::new();
|
|
|
|
report.push_str(&format!("\n{}", "=".repeat(80)));
|
|
report.push_str(&format!("\n📊 FLOCK MODE SESSION REPORT"));
|
|
report.push_str(&format!("\n{}", "=".repeat(80)));
|
|
|
|
report.push_str(&format!("\n\n🆔 Session ID: {}", self.session_id));
|
|
report.push_str(&format!("\n📁 Project: {}", self.project_dir.display()));
|
|
report.push_str(&format!(
|
|
"\n🗂️ Workspace: {}",
|
|
self.flock_workspace.display()
|
|
));
|
|
report.push_str(&format!("\n🔢 Segments: {}", self.num_segments));
|
|
|
|
let duration = if let Some(completed) = self.completed_at {
|
|
completed.signed_duration_since(self.started_at)
|
|
} else {
|
|
Utc::now().signed_duration_since(self.started_at)
|
|
};
|
|
|
|
report.push_str(&format!(
|
|
"\n⏱️ Duration: {:.2}s",
|
|
duration.num_milliseconds() as f64 / 1000.0
|
|
));
|
|
|
|
// Segment status summary
|
|
report.push_str(&format!("\n\n📈 Segment Status:"));
|
|
report.push_str(&format!(
|
|
"\n • Completed: {}",
|
|
self.count_by_state(SegmentState::Completed)
|
|
));
|
|
report.push_str(&format!(
|
|
"\n • Running: {}",
|
|
self.count_by_state(SegmentState::Running)
|
|
));
|
|
report.push_str(&format!(
|
|
"\n • Failed: {}",
|
|
self.count_by_state(SegmentState::Failed)
|
|
));
|
|
report.push_str(&format!(
|
|
"\n • Pending: {}",
|
|
self.count_by_state(SegmentState::Pending)
|
|
));
|
|
report.push_str(&format!(
|
|
"\n • Cancelled: {}",
|
|
self.count_by_state(SegmentState::Cancelled)
|
|
));
|
|
|
|
// Metrics
|
|
report.push_str(&format!("\n\n📊 Aggregate Metrics:"));
|
|
report.push_str(&format!("\n • Total Tokens: {}", self.total_tokens));
|
|
report.push_str(&format!(
|
|
"\n • Total Tool Calls: {}",
|
|
self.total_tool_calls
|
|
));
|
|
report.push_str(&format!("\n • Total Errors: {}", self.total_errors));
|
|
|
|
// Per-segment details
|
|
report.push_str(&format!("\n\n🔍 Segment Details:"));
|
|
let mut segments: Vec<_> = self.segments.iter().collect();
|
|
segments.sort_by_key(|(id, _)| *id);
|
|
|
|
for (id, segment) in segments {
|
|
report.push_str(&format!("\n\n Segment {}:", id));
|
|
report.push_str(&format!("\n Status: {}", segment.state));
|
|
report.push_str(&format!(
|
|
"\n Workspace: {}",
|
|
segment.workspace.display()
|
|
));
|
|
report.push_str(&format!("\n Tokens: {}", segment.tokens_used));
|
|
report.push_str(&format!("\n Tool Calls: {}", segment.tool_calls));
|
|
report.push_str(&format!("\n Errors: {}", segment.errors));
|
|
report.push_str(&format!(
|
|
"\n Turn: {}/{}",
|
|
segment.current_turn, segment.max_turns
|
|
));
|
|
|
|
if let Some(ref msg) = segment.last_message {
|
|
report.push_str(&format!("\n Last Message: {}", msg));
|
|
}
|
|
|
|
if let Some(ref err) = segment.error_message {
|
|
report.push_str(&format!("\n Error: {}", err));
|
|
}
|
|
}
|
|
|
|
report.push_str(&format!("\n\n{}", "=".repeat(80)));
|
|
|
|
report
|
|
}
|
|
}
|