feat(agent): implement agent execution engine and turn handling with background processing
This commit is contained in:
@@ -104,36 +104,55 @@ impl SessionLock {
|
||||
let _ = fs::remove_file(&self.path);
|
||||
}
|
||||
|
||||
/// Check whether a process with the given PID is currently alive.
|
||||
/// Check whether a process with the given PID is currently alive and
|
||||
/// belongs to the same binary (mitigating PID-reuse races).
|
||||
///
|
||||
/// Strategy (Unix):
|
||||
/// 1. Resolve `/proc/<pid>/exe` — if it doesn't match our own binary,
|
||||
/// the PID either belongs to another process or is reused — return false.
|
||||
/// 2. Send `kill(pid, 0)` to verify the process is still alive.
|
||||
/// 3. Re-check `/proc/<pid>/exe` to close the TOCTOU window between
|
||||
/// step 1 and step 2 (PID reuse after exe check, before kill).
|
||||
///
|
||||
/// Uses `kill(pid, 0)` on Unix via the `nix` or `libc` crate in production;
|
||||
/// here we provide a best-effort check using the process table.
|
||||
/// On non-Unix platforms this always returns `true` (conservative).
|
||||
fn is_alive(pid: u32) -> bool {
|
||||
// On Unix, signal 0 checks process existence without sending a signal.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// SAFETY: `libc::kill(pid, 0)` does not send a signal; it only checks
|
||||
// whether the process exists and the caller has permission to signal it.
|
||||
// The integer argument is a PID validated by `try_lock`.
|
||||
// Resolve our own executable path once.
|
||||
let self_exe = match std::fs::read_link("/proc/self/exe") {
|
||||
Ok(exe) => exe,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let pid_signed: i32 = match pid.try_into() {
|
||||
Ok(p) => p,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let proc_exe = std::path::PathBuf::from(format!("/proc/{pid}/exe"));
|
||||
|
||||
// Phase 1: read /proc/<pid>/exe and compare with self_exe.
|
||||
let target = match std::fs::read_link(&proc_exe) {
|
||||
Ok(t) => t,
|
||||
Err(_) => return false,
|
||||
};
|
||||
if target != self_exe {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Phase 2: verify the process is still alive.
|
||||
// SAFETY: `libc::kill(pid, 0)` does not send a signal; it only checks
|
||||
// whether the process exists and the caller has permission to signal it.
|
||||
if unsafe { libc::kill(pid_signed, 0) != 0 } {
|
||||
return false;
|
||||
}
|
||||
// Extra check: verify the PID belongs to a zesdex process via
|
||||
// /proc/<pid>/exe to mitigate the PID-reuse race.
|
||||
let proc_exe = std::path::PathBuf::from(format!("/proc/{pid}/exe"));
|
||||
if let Ok(target) = std::fs::read_link(&proc_exe) {
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if target != exe {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: re-check /proc/<pid>/exe to detect PID reuse between
|
||||
// Phase 1 and Phase 2.
|
||||
match std::fs::read_link(&proc_exe) {
|
||||
Ok(recheck) if recheck == self_exe => true,
|
||||
_ => false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
|
||||
@@ -333,8 +333,9 @@ impl SseParser {
|
||||
if let Some(tool_calls) =
|
||||
d.get("tool_calls").and_then(|tc| tc.as_array())
|
||||
{
|
||||
const MAX_TOOL_CALLS: usize = 64;
|
||||
for tc in tool_calls {
|
||||
let index =
|
||||
let raw_index =
|
||||
tc.get("index").and_then(Value::as_u64).unwrap_or_else(
|
||||
|| {
|
||||
tracing::warn!(
|
||||
@@ -344,7 +345,10 @@ impl SseParser {
|
||||
0
|
||||
},
|
||||
);
|
||||
let index = usize::try_from(index).unwrap_or(0);
|
||||
// Clamp index to prevent out-of-bounds / memory exhaustion
|
||||
let index = usize::try_from(raw_index)
|
||||
.unwrap_or(0)
|
||||
.min(MAX_TOOL_CALLS.saturating_sub(1));
|
||||
let id = tc
|
||||
.get("id")
|
||||
.and_then(|i| i.as_str())
|
||||
|
||||
Reference in New Issue
Block a user