feat: enhance subagent context with abort flag and implement tool call timeout

This commit is contained in:
asepharyana
2026-07-13 04:59:16 +07:00
parent 3b711bbf3b
commit 2856dd78b8
9 changed files with 272 additions and 131 deletions
+34 -11
View File
@@ -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,
+11
View File
@@ -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)?;