feat: enhance subagent context with abort flag and implement tool call timeout
This commit is contained in:
+34
-11
@@ -17,35 +17,58 @@ pub struct EditLogEntry {
|
||||
pub session_id: String,
|
||||
}
|
||||
|
||||
/// Maximum number of edit entries held in memory at once.
|
||||
/// Beyond this limit, old entries are dropped from the in-memory cache
|
||||
/// to prevent unbounded memory growth in long sessions.
|
||||
const MAX_MEMORY_ENTRIES: usize = 10_000;
|
||||
|
||||
/// In-memory view of a session's edit log, backed by `edits.jsonl` on disk.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EditLog {
|
||||
pub entries: Vec<EditLogEntry>,
|
||||
pub path: std::path::PathBuf,
|
||||
/// Total entries on disk (may exceed `entries.len()` if truncated).
|
||||
pub total_on_disk: usize,
|
||||
}
|
||||
|
||||
impl EditLog {
|
||||
/// Open (or start tracking) the edit log for a session directory,
|
||||
/// replaying any existing `edits.jsonl` into memory.
|
||||
/// replaying any existing `edits.jsonl` into memory (capped at
|
||||
/// `MAX_MEMORY_ENTRIES` to prevent OOM).
|
||||
pub fn new(session_dir: &std::path::Path) -> Self {
|
||||
let path = session_dir.join("edits.jsonl");
|
||||
let entries = Self::load_from_disk(&path);
|
||||
EditLog { entries, path }
|
||||
let (entries, total_on_disk) = Self::load_from_disk(&path);
|
||||
EditLog { entries, path, total_on_disk }
|
||||
}
|
||||
|
||||
/// Reads every line of edits.jsonl back into memory so callers who create a
|
||||
/// *new* EditLog after a previous session can inspect the full history.
|
||||
fn load_from_disk(path: &std::path::Path) -> Vec<EditLogEntry> {
|
||||
/// Reads lines of edits.jsonl into memory, keeping only the most recent
|
||||
/// `MAX_MEMORY_ENTRIES` entries. The full history is preserved on disk
|
||||
/// regardless of the in-memory limit.
|
||||
fn load_from_disk(path: &std::path::Path) -> (Vec<EditLogEntry>, usize) {
|
||||
let file = match std::fs::File::open(path) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return Vec::new(),
|
||||
Err(_) => return (Vec::new(), 0),
|
||||
};
|
||||
use std::io::{BufRead, BufReader};
|
||||
let reader = BufReader::new(file);
|
||||
reader
|
||||
.lines()
|
||||
.filter_map(|line| line.ok().and_then(|l| serde_json::from_str(&l).ok()))
|
||||
.collect()
|
||||
let mut entries: Vec<EditLogEntry> = Vec::new();
|
||||
let mut total = 0usize;
|
||||
for line in reader.lines() {
|
||||
let line = match line {
|
||||
Ok(l) => l,
|
||||
Err(_) => continue,
|
||||
};
|
||||
total += 1;
|
||||
if let Ok(entry) = serde_json::from_str::<EditLogEntry>(&line) {
|
||||
// Keep only the most recent entries in memory
|
||||
if entries.len() >= MAX_MEMORY_ENTRIES {
|
||||
// Drop oldest (front) to make room
|
||||
entries.remove(0);
|
||||
}
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
(entries, total)
|
||||
}
|
||||
|
||||
/// Append one entry to `edits.jsonl` on disk and to the in-memory log,
|
||||
|
||||
@@ -78,9 +78,20 @@ impl Session {
|
||||
|
||||
/// Load a session's metadata by id from `<base_dir>/sessions/<id>/session.json`.
|
||||
///
|
||||
/// Security: the session id is validated to prevent directory traversal
|
||||
/// (e.g. `../../etc/passwd`). Only alphanumeric, hyphens, underscores,
|
||||
/// and dots are allowed — no path separators.
|
||||
///
|
||||
/// Return: the parsed `Session`, or an `io::Error` if the file is
|
||||
/// missing or malformed.
|
||||
pub fn load(id: &str, base_dir: &Path) -> std::io::Result<Self> {
|
||||
// Reject session ids that contain path separators or parent dir refs
|
||||
if id.contains('/') || id.contains('\\') || id.contains("..") {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!("invalid session id '{}': must not contain path separators", id),
|
||||
));
|
||||
}
|
||||
let path = base_dir.join("sessions").join(id).join("session.json");
|
||||
let data = std::fs::read_to_string(path)?;
|
||||
let session: Session = serde_json::from_str(&data)?;
|
||||
|
||||
Reference in New Issue
Block a user