diff --git a/src/agent/log.rs b/src/agent/log.rs index 1855545..7353d14 100644 --- a/src/agent/log.rs +++ b/src/agent/log.rs @@ -125,4 +125,26 @@ impl ConversationLog { pub fn path(&self) -> &Path { &self.path } + + /// Get the timestamp of the oldest message in the log. + pub fn oldest_timestamp(&self) -> Option> { + let file = File::open(&self.path).ok()?; + let reader = BufReader::new(file); + for line in reader.lines().flatten() { + let line = line.trim().to_string(); + if line.is_empty() { continue; } + if let Ok(msg) = serde_json::from_str::(&line) { + if let Some(ts) = &msg.timestamp { + if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(ts) { + return Some(dt.to_utc()); + } + // Try other formats + if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(ts, "%Y-%m-%dT%H:%M:%S") { + return Some(dt.and_utc()); + } + } + } + } + None + } } diff --git a/src/agent/runner.rs b/src/agent/runner.rs index f0e926a..dee8bac 100644 --- a/src/agent/runner.rs +++ b/src/agent/runner.rs @@ -785,8 +785,55 @@ impl Agent { /// Uses the same budget logic as compaction but with empty conversation. /// Only parses the tail of the journal file (last 64KB) for speed. fn load_startup_journal(&mut self) { - let journal_path = journal::default_journal_path(); - let entries = journal::parse_journal_tail(&journal_path, 64 * 1024); + let store = match crate::store::Store::load() { + Ok(s) => s, + Err(_) => return, + }; + + // Find oldest message timestamp in conversation log + let oldest_msg_ts = self.conversation_log.as_ref() + .and_then(|log| log.oldest_timestamp()); + + // Get journal entries from the memory graph + let mut journal_nodes: Vec<_> = store.nodes.values() + .filter(|n| n.node_type == crate::store::NodeType::EpisodicSession) + .collect(); + journal_nodes.sort_by_key(|n| n.created_at); + + // Filter: entries older than oldest conversation message, + // plus the first one that overlaps (no gap) + let entries: Vec = if let Some(cutoff) = oldest_msg_ts { + let mut result = Vec::new(); + let mut included_overlap = false; + for node in &journal_nodes { + let ts = chrono::DateTime::from_timestamp(node.created_at, 0) + .unwrap_or_default(); + if ts < cutoff || !included_overlap { + result.push(journal::JournalEntry { + timestamp: ts, + content: node.content.clone(), + }); + if ts >= cutoff { + included_overlap = true; + } + } else { + break; + } + } + result + } else { + // No conversation yet — include recent entries for orientation + let n = 20; + let skip = journal_nodes.len().saturating_sub(n); + journal_nodes.iter().skip(skip).map(|node| { + journal::JournalEntry { + timestamp: chrono::DateTime::from_timestamp(node.created_at, 0) + .unwrap_or_default(), + content: node.content.clone(), + } + }).collect() + }; + if entries.is_empty() { return; }