diff --git a/crates/reliary-agent/pi/gate.js b/crates/reliary-agent/pi/gate.js index 7904f6c..d7c634b 100644 --- a/crates/reliary-agent/pi/gate.js +++ b/crates/reliary-agent/pi/gate.js @@ -2,7 +2,7 @@ const { execFileSync, spawnSync } = require("child_process"); const { existsSync, readFileSync, readdirSync, statSync, unlinkSync } = require("fs"); const { createHash } = require("crypto"); -const GATE_VERSION = "2.6.0"; +const GATE_VERSION = "0.6.5"; // ── Log levels (matching RELIARY_LOG convention) ── const LOG_LEVELS = { error: 1, warn: 2, info: 3, debug: 4, trace: 5 }; diff --git a/crates/reliary-agent/src/init.rs b/crates/reliary-agent/src/init.rs index 6575ed4..98d149d 100644 --- a/crates/reliary-agent/src/init.rs +++ b/crates/reliary-agent/src/init.rs @@ -827,7 +827,7 @@ pub fn restore_opencode_proxy_routes() -> bool { where F: FnOnce(PathBuf), { - let _lock = INIT_TEST_LOCK.lock().unwrap(); + let _lock = INIT_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()); use std::sync::atomic::{AtomicU32, Ordering}; static COUNTER: AtomicU32 = AtomicU32::new(0); let dir = std::env::temp_dir().join(format!("reliary_init_test_{}_{}", std::process::id(), COUNTER.fetch_add(1, Ordering::SeqCst))); diff --git a/crates/reliary-agent/src/main.rs b/crates/reliary-agent/src/main.rs index 8dc1e2b..9f62e9e 100644 --- a/crates/reliary-agent/src/main.rs +++ b/crates/reliary-agent/src/main.rs @@ -965,6 +965,10 @@ fn main() { } } Commands::Serve { port } => { + rayon::ThreadPoolBuilder::new() + .num_threads(4) + .build_global() + .unwrap_or(()); // ok if already initialized ux::write_pid_file(); eprintln!("{} Reliary Agent v{}", color::bold(""), VERSION); eprintln!( @@ -978,7 +982,11 @@ fn main() { let state = Arc::new(SessionState::new( std::env::current_dir().unwrap_or_default().to_string_lossy().as_ref() )); - let rt = tokio::runtime::Runtime::new().unwrap(); + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(4) + .enable_all() + .build() + .unwrap(); rt.block_on(async { crate::proxy::start(*port, Some(state)).await }) .unwrap_or_else(|e| error!("Server error: {}", e)); } @@ -1220,7 +1228,11 @@ fn main() { let state = Arc::new(SessionState::new( std::env::current_dir().unwrap_or_default().to_string_lossy().as_ref() )); - let rt = tokio::runtime::Runtime::new().unwrap(); + let rt = tokio::runtime::Builder::new_multi_thread() + .worker_threads(4) + .enable_all() + .build() + .unwrap(); rt.block_on(async { crate::proxy::start(9799, Some(state)).await }) .unwrap_or_else(|e| error!("Server error: {}", e)); } diff --git a/crates/reliary-agent/src/novel_compress.rs b/crates/reliary-agent/src/novel_compress.rs index 59252fe..c35b872 100644 --- a/crates/reliary-agent/src/novel_compress.rs +++ b/crates/reliary-agent/src/novel_compress.rs @@ -3,9 +3,8 @@ //! 1. Cache-hit feedback loop: adapt compression aggressiveness from API cache metrics. //! 2. Stream-aware prefetch: parse SSE chunks for file paths, pre-load reads. //! 3. Maxwell's Demon: information-theoretic token erasure (cost = -log2(freq)). -//! 4. Asymmetric tool-call compression: preserve results, compress requests. -//! 5. Invariant hoisting: JSON arrays → header + delta rows. -//! 6. Dialogue State Ledger: extract [STATE] from old reasoning. +//! 4. Invariant hoisting: JSON arrays → header + delta rows. +//! 5. Dialogue State Ledger: extract [STATE] from old reasoning. use rustc_hash::FxHashMap; use serde_json::Value; diff --git a/crates/reliary-agent/src/proxy.rs b/crates/reliary-agent/src/proxy.rs index 6900cbb..3d2926c 100644 --- a/crates/reliary-agent/src/proxy.rs +++ b/crates/reliary-agent/src/proxy.rs @@ -579,6 +579,17 @@ async fn proxy_post( let result = crate::guard::check_diff(&index_path, rp, &new_text); let status = result.get("status").and_then(|s| s.as_str()).unwrap_or("error").to_string(); if let Ok(mut c) = GUARD_CACHE.lock() { + // Evict stale entries (elapsed > 60s) to bound memory. + c.retain(|_, e| e.inserted_at.elapsed() < std::time::Duration::from_secs(60)); + // Hard cap to prevent unbounded growth. + if c.len() >= 500 { + if let Some(&oldest_key) = c.iter() + .min_by_key(|(_, e)| e.inserted_at) + .map(|(k, _)| k) + { + c.remove(&oldest_key); + } + } c.insert(gk, GuardCacheEntry { status: status.clone(), inserted_at: std::time::Instant::now() }); } status @@ -758,9 +769,13 @@ async fn proxy_post( Ok(Some(chunk)) => { // Track for usage parsing and response cache let chunk_str = String::from_utf8_lossy(&chunk); - // Stream-aware prefetch: extract file paths from live chunks + // Stream-aware prefetch: extract file paths from live chunks. + // Run in spawn_blocking to avoid sync fs I/O stalling the async runtime. if !std::env::var("RELIARY_PROXY_PREFETCH").is_ok_and(|v| v == "0") { - crate::novel_compress::try_prefetch(&chunk_str); + let pf_chunk = chunk_str.to_string(); + tokio::task::spawn_blocking(move || { + crate::novel_compress::try_prefetch(&pf_chunk); + }); } if chunk_str.contains("\"usage\"") || chunk_str.contains("\"prompt_tokens\"") { last_chunk_with_usage = chunk_str.to_string(); diff --git a/crates/reliary-agent/src/reindex.rs b/crates/reliary-agent/src/reindex.rs index 9583b6c..8fd3fe7 100644 --- a/crates/reliary-agent/src/reindex.rs +++ b/crates/reliary-agent/src/reindex.rs @@ -70,7 +70,9 @@ fn file_modified(p: &Path) -> u64 { fn walkdir(dir: &str) -> Result, String> { let mut files = Vec::new(); let mut stack = vec![std::path::PathBuf::from(dir)]; - let skip_dirs = [".git", ".reliary", "node_modules", "target", "__pycache__", ".venv"]; + let skip_dirs = [".git", ".reliary", "node_modules", "target", "__pycache__", + ".venv", ".cargo", ".rustup", ".npm", ".cache", ".local", "venv", + ".next", "dist", "build", "vendor", "bundle", ".bundle"]; while let Some(path) = stack.pop() { if let Ok(entries) = std::fs::read_dir(&path) { diff --git a/crates/reliary-agent/src/scavenger.rs b/crates/reliary-agent/src/scavenger.rs index fe7b117..73f2e38 100644 --- a/crates/reliary-agent/src/scavenger.rs +++ b/crates/reliary-agent/src/scavenger.rs @@ -1,24 +1,40 @@ use std::sync::Arc; -use std::time::Duration; +use std::time::{Duration, Instant}; use rayon::prelude::*; use crate::session_state::SessionState; use crate::chronicle; pub fn scavenger_loop(state: Arc) { + let mut sleep_secs: u64 = 120; loop { - std::thread::sleep(Duration::from_secs(120)); + std::thread::sleep(Duration::from_secs(sleep_secs)); if !state.is_scavenger_allowed() { continue; } let workdir = state.workdir.to_string_lossy().to_string(); let db_path = state.chronicle_path.to_string_lossy().to_string(); + let cycle_start = Instant::now(); // 1. Parallel incremental re-index for changed files crate::reindex::incremental_reindex(&workdir); // 2. Collect file paths then scan for dead code in parallel let file_tasks: Vec<_> = walkdir::WalkDir::new(&workdir) + .follow_links(false) + .max_depth(20) .into_iter() + .filter_entry(|e| { + if e.file_type().is_dir() { + let name = e.file_name().to_string_lossy(); + !matches!(name.as_ref(), + ".git" | ".reliary" | "node_modules" | "target" | "__pycache__" + | ".venv" | ".cargo" | ".rustup" | ".npm" | ".cache" + | ".local" | "venv" | ".next" | "dist" | "build" + | "vendor" | "bundle" | ".bundle") + } else { + true + } + }) .filter_map(|e| e.ok()) .map(|e| e.path().to_path_buf()) .filter(|fp| { @@ -53,6 +69,9 @@ pub fn scavenger_loop(state: Arc) { rusqlite::params![cutoff], ); + // WSL2 drvfs detection: skip heal subprocess (cargo test) on /mnt/ paths + let on_drvfs = workdir.starts_with("/mnt/"); + // Process candidates sequentially (heal calls need serial DB) for c in candidates.iter() { if c.confidence != reliary_dead::Confidence::High { continue; } @@ -62,7 +81,12 @@ pub fn scavenger_loop(state: Arc) { let fixes = vec![(c.name.clone(), String::new())]; let (modified, count) = reliary_fix::apply_fixes(&content, &fixes); if count > 0 && modified != content { - match crate::heal::heal_edit(&c.file, &modified, &workdir) { + let result = if on_drvfs { + Err("WSL2 drvfs: heal skipped".into()) + } else { + crate::heal::heal_edit(&c.file, &modified, &workdir) + }; + match result { Ok(()) => { let _ = std::fs::write(&c.file, &modified); chronicle::append(&chronicle_db, "scavenge", &c.file, &c.name, "removed"); @@ -75,5 +99,13 @@ pub fn scavenger_loop(state: Arc) { } } } + + // Adaptive backoff: if cycle took longer than 60s, double sleep interval + let elapsed = cycle_start.elapsed().as_secs(); + if elapsed > 60 { + sleep_secs = (sleep_secs * 2).min(1800); // max 30 min + } else { + sleep_secs = 120; + } } } diff --git a/crates/reliary-agent/src/session_state.rs b/crates/reliary-agent/src/session_state.rs index b8a4c43..f5e257e 100644 --- a/crates/reliary-agent/src/session_state.rs +++ b/crates/reliary-agent/src/session_state.rs @@ -35,7 +35,7 @@ impl SessionState { warn!("session_dir create_dir_all: {}", e); } Self { - scavenger_muzzled: AtomicBool::new(false), + scavenger_muzzled: AtomicBool::new(true), muzzle_time: Mutex::new(Instant::now()), chronicle_path, workdir: PathBuf::from(workdir), diff --git a/crates/reliary-agent/tests/common/mod.rs b/crates/reliary-agent/tests/common/mod.rs index 497c73d..1d4baff 100644 --- a/crates/reliary-agent/tests/common/mod.rs +++ b/crates/reliary-agent/tests/common/mod.rs @@ -47,6 +47,7 @@ fn start_daemon_inner() -> DaemonGuard { let bin = binary_path(); let mut child = Command::new(&bin) .arg("serve") + .env_remove("RELIARY_UPSTREAM_URL") // Test isolation: don't accept unknown keys .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn() diff --git a/crates/reliary-agent/tests/e2e_proxy.rs b/crates/reliary-agent/tests/e2e_proxy.rs index cd68263..d827d04 100644 --- a/crates/reliary-agent/tests/e2e_proxy.rs +++ b/crates/reliary-agent/tests/e2e_proxy.rs @@ -81,6 +81,8 @@ fn e2e_auth_routing() { let client = common::http_client(); // Unknown key -> 403 + // NOTE: The daemon is spawned with RELIARY_UPSTREAM_URL removed (see common/mod.rs) + // so unknown keys are rejected even if the developer has the env var set. let resp = client .post("http://127.0.0.1:9090/v1/chat/completions") .header("Authorization", "Bearer sk-fake-key-xxxxx") diff --git a/pi/gate.js b/pi/gate.js index 7904f6c..d7c634b 100644 --- a/pi/gate.js +++ b/pi/gate.js @@ -2,7 +2,7 @@ const { execFileSync, spawnSync } = require("child_process"); const { existsSync, readFileSync, readdirSync, statSync, unlinkSync } = require("fs"); const { createHash } = require("crypto"); -const GATE_VERSION = "2.6.0"; +const GATE_VERSION = "0.6.5"; // ── Log levels (matching RELIARY_LOG convention) ── const LOG_LEVELS = { error: 1, warn: 2, info: 3, debug: 4, trace: 5 };