Synced from monorepo
Synced from monorepo Changes: - Refresh tool search when the managed MCP catalog is re-fetched - Prevent duplicate leader process spawn and startup hang from stale leaders - Document marketplaces, plugins, and organization controls - Stamp session ID on image generation direct-to-API requests - Fix auto mode blocked documentation - Auto mode considers recent user intent - Expose deploy archive, taken-down, limit, and in-progress reasons on the chat API - Fail-closed auth refresh contract for shell clients - Emit a chat-supplied per-session turn index in turn hooks - Show bash mode chrome in minimal mode - Add metrics for true-noop and stationarity stops - Include voice interim text on prompt submit - Silently end turn on true-noop thrash - Quiet copy toast when clipboard delivery is confirmed - Fix session fork truncating at the wrong prompt in rewound sessions - Make the idle "still running" watcher cue clickable to open the tasks pane - Default web search model to grok-4.5 - Let plugin subagents inherit parent MCP servers - Gate no-op end-turn reminder on system reminders - Add gateway bridge lifecycle telemetry - Allow editing finalized text while voice is open - Relocate token carrier to turn-commit events and plumb per-turn origin context - Raise workflow scratch quotas and make failed runs resumable - Workflows overlay: auto-progress phases, live agent status, and drop budget meter Source-Revision: 9b8d35b46d959c042ea9aa31cbbebbd1f0c5c527
This commit is contained in:
parent
69f0ba880a
commit
6e38642082
103 changed files with 4964 additions and 1261 deletions
|
|
@ -156,24 +156,42 @@ pub fn is_process_alive(pid: u32) -> bool {
|
|||
let _ = unsafe { CloseHandle(handle) };
|
||||
wait_result == WAIT_TIMEOUT
|
||||
}
|
||||
/// Terminate a process by PID. Idempotent: already-dead is `Ok`.
|
||||
///
|
||||
/// - Unix: `SIGTERM` via `nix::sys::signal::kill`; ESRCH maps to `Ok`.
|
||||
/// - Windows: `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess`;
|
||||
/// ERROR_INVALID_PARAMETER (Windows' "no such process") maps to `Ok`.
|
||||
/// Which termination signal to send. On Windows both map to `TerminateProcess`
|
||||
/// (already forceful), so the distinction only matters on Unix.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum KillSignal {
|
||||
/// Graceful `SIGTERM` (Unix) — the process may catch and drain.
|
||||
Term,
|
||||
/// Forceful `SIGKILL` (Unix) — unblockable escalation.
|
||||
Kill,
|
||||
}
|
||||
/// Terminate a process by PID with `SIGTERM`. Idempotent: already-dead is `Ok`.
|
||||
pub fn kill_process_by_pid(pid: u32) -> std::io::Result<()> {
|
||||
kill_process_with_signal(pid, KillSignal::Term)
|
||||
}
|
||||
/// Terminate a process by PID with a chosen signal. Idempotent: already-dead is `Ok`.
|
||||
///
|
||||
/// - Unix: `SIGTERM`/`SIGKILL` via `nix::sys::signal::kill`; ESRCH maps to `Ok`.
|
||||
/// - Windows: `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess` (already
|
||||
/// forceful, so `signal` is ignored); ERROR_INVALID_PARAMETER maps to `Ok`.
|
||||
pub fn kill_process_with_signal(pid: u32, signal: KillSignal) -> std::io::Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use nix::errno::Errno;
|
||||
use nix::sys::signal::{Signal, kill};
|
||||
use nix::unistd::Pid;
|
||||
match kill(Pid::from_raw(pid as i32), Signal::SIGTERM) {
|
||||
let sig = match signal {
|
||||
KillSignal::Term => Signal::SIGTERM,
|
||||
KillSignal::Kill => Signal::SIGKILL,
|
||||
};
|
||||
match kill(Pid::from_raw(pid as i32), sig) {
|
||||
Ok(()) | Err(Errno::ESRCH) => Ok(()),
|
||||
Err(e) => Err(std::io::Error::from_raw_os_error(e as i32)),
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = signal;
|
||||
use windows::Win32::Foundation::{CloseHandle, ERROR_INVALID_PARAMETER};
|
||||
use windows::Win32::System::Threading::{OpenProcess, PROCESS_TERMINATE, TerminateProcess};
|
||||
use windows::core::HRESULT;
|
||||
|
|
@ -242,6 +260,38 @@ pub fn is_grok_process(pid: u32) -> bool {
|
|||
cmd.status().is_ok_and(|s| s.success())
|
||||
}
|
||||
}
|
||||
/// Stricter [`is_grok_process`] for the auto-kill zombie path: on macOS/BSD it
|
||||
/// name-matches via `ps` (not liveness-only), so eviction never SIGKILLs a
|
||||
/// recycled PID now owned by an unrelated process. Linux/Windows already match
|
||||
/// exactly, so this delegates there. Use the permissive [`is_grok_process`] for
|
||||
/// operator-driven `grok leaders kill`.
|
||||
pub fn is_grok_process_strict(pid: u32) -> bool {
|
||||
#[cfg(all(not(target_os = "linux"), not(windows)))]
|
||||
{
|
||||
let mut cmd = std::process::Command::new("ps");
|
||||
cmd.args(["-p", &pid.to_string(), "-o", "comm="])
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null());
|
||||
xai_tty_utils::detach_std_command(&mut cmd);
|
||||
match cmd.output() {
|
||||
Ok(out) if out.status.success() => {
|
||||
let comm = String::from_utf8_lossy(&out.stdout);
|
||||
comm.lines()
|
||||
.next()
|
||||
.map(str::trim)
|
||||
.filter(|line| !line.is_empty())
|
||||
.and_then(|line| std::path::Path::new(line).file_name())
|
||||
.and_then(|name| name.to_str())
|
||||
.is_some_and(|name| name.to_ascii_lowercase().contains("grok"))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
{
|
||||
is_grok_process(pid)
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -345,4 +395,21 @@ mod tests {
|
|||
assert!(is_grok_process(std::process::id()));
|
||||
assert!(!is_grok_process(u32::MAX));
|
||||
}
|
||||
#[test]
|
||||
fn is_grok_process_strict_self_true_impossible_pid_false() {
|
||||
assert!(is_grok_process_strict(std::process::id()));
|
||||
assert!(!is_grok_process_strict(u32::MAX));
|
||||
}
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn kill_process_with_signal_sigkill_terminates_live_child() {
|
||||
let mut child = std::process::Command::new("sleep")
|
||||
.arg("60")
|
||||
.spawn()
|
||||
.expect("spawn sleep");
|
||||
let pid = child.id();
|
||||
kill_process_with_signal(pid, KillSignal::Kill).expect("sigkill should succeed");
|
||||
let status = child.wait().expect("wait child");
|
||||
assert!(!status.success(), "sleep was killed, not exited cleanly");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue