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:
grokkybara[bot] 2026-07-24 16:59:42 +00:00
commit 6e38642082
103 changed files with 4964 additions and 1261 deletions

View file

@ -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");
}
}