feat: implemented process management builtins and cross-platform snapshotting
- Implemented new shell builtins including `top`, `pgrep`, `pkill`, `pidwait`, and `kill`. - Added a cross-platform process snapshot module supporting Linux, macOS, and Windows. - Extended job and process handling utilities with process iteration and handle termination methods. - Replaced test mutex locks with thread-local counters to prevent test races in the minimizer engine.
This commit is contained in:
Generated
+66
-66
File diff suppressed because one or more lines are too long
@@ -477,8 +477,18 @@ fn resolve_pipeline<'a>(
|
||||
// Atomic counter for commands that reached `apply` without a matching filter.
|
||||
static UNKNOWN_COMMAND_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
// Per-thread mirror of the counter: integration tests drive `apply` from
|
||||
// parallel tokio workers, so equality assertions on the process-wide atomic
|
||||
// race. Tests assert on the calling thread's own recordings instead.
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
static THREAD_UNKNOWN_COMMAND_COUNT: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
fn record_unknown_command(_command: &str) {
|
||||
UNKNOWN_COMMAND_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
#[cfg(test)]
|
||||
THREAD_UNKNOWN_COMMAND_COUNT.with(|count| count.set(count.get() + 1));
|
||||
}
|
||||
|
||||
/// Total number of commands that fell through `apply` without any matching
|
||||
@@ -487,10 +497,11 @@ pub fn unknown_command_count() -> u64 {
|
||||
UNKNOWN_COMMAND_COUNT.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Reset the unknown-command counter (intended for tests).
|
||||
#[doc(hidden)]
|
||||
pub fn reset_unknown_command_count() {
|
||||
UNKNOWN_COMMAND_COUNT.store(0, Ordering::Relaxed);
|
||||
/// Unknown-command recordings made by the current thread. Race-free
|
||||
/// alternative to [`unknown_command_count`] for test assertions.
|
||||
#[cfg(test)]
|
||||
fn thread_unknown_command_count() -> u64 {
|
||||
THREAD_UNKNOWN_COMMAND_COUNT.with(std::cell::Cell::get)
|
||||
}
|
||||
|
||||
const BUILTIN_FILTERS_TOML: &str = include_str!(concat!(env!("OUT_DIR"), "/builtin_filters.toml"));
|
||||
@@ -523,7 +534,6 @@ mod tests {
|
||||
};
|
||||
|
||||
static CONFIG_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
pub static TEST_LOCK: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
|
||||
|
||||
use super::*;
|
||||
use crate::minimizer::MinimizerOptions;
|
||||
@@ -759,15 +769,15 @@ strip_lines_matching = [".*"]
|
||||
|
||||
#[test]
|
||||
fn segmented_chain_supported_command_does_not_record_unknown() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
// Phase 7 (Mode α resolution): supported chains route through
|
||||
// filters::dispatch via the chain decomposer instead of falling
|
||||
// back to passthrough. The unknown-command counter must remain
|
||||
// stable — the chain entry point is structurally known.
|
||||
reset_unknown_command_count();
|
||||
// stable — the chain entry point is structurally known. Asserted on
|
||||
// this thread's recordings: parallel integration tests legitimately
|
||||
// bump the process-wide counter.
|
||||
let cfg = MinimizerConfig { enabled: true, ..Default::default() };
|
||||
let input = "diff --git a/file.rs b/file.rs\n@@\n-old\n+new\n";
|
||||
let before = unknown_command_count();
|
||||
let before = thread_unknown_command_count();
|
||||
|
||||
assert_eq!(mode_for("git diff ; printf done", &cfg), MinimizerMode::SegmentedChain);
|
||||
let out = apply("git diff ; printf done", input, 0, &cfg);
|
||||
@@ -778,7 +788,7 @@ strip_lines_matching = [".*"]
|
||||
// recorded (per-segment minimization is the segmented runner's job).
|
||||
assert!(!out.changed, "mixed chain must stay passthrough in whole-buffer minimization");
|
||||
assert_eq!(out.filter, "compound");
|
||||
assert_eq!(unknown_command_count(), before);
|
||||
assert_eq!(thread_unknown_command_count(), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1230,15 +1240,19 @@ mod pipeline_integration_tests {
|
||||
|
||||
#[test]
|
||||
fn unknown_command_counter_increments() {
|
||||
let _guard = super::tests::TEST_LOCK.lock();
|
||||
reset_unknown_command_count();
|
||||
let cfg = MinimizerConfig::from_options(&MinimizerOptions {
|
||||
enabled: Some(true),
|
||||
..Default::default()
|
||||
});
|
||||
let before = unknown_command_count();
|
||||
let global_before = unknown_command_count();
|
||||
let thread_before = thread_unknown_command_count();
|
||||
let _ = apply("zzzobscurecmd foo", "hi\n", 0, &cfg);
|
||||
let after = unknown_command_count();
|
||||
assert!(after > before, "counter should advance for unknown commands");
|
||||
// Exact accounting on this thread; monotonic advance on the public
|
||||
// process-wide counter (other threads may add, never subtract).
|
||||
assert_eq!(thread_unknown_command_count(), thread_before + 1);
|
||||
assert!(
|
||||
unknown_command_count() > global_before,
|
||||
"counter should advance for unknown commands"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+3143
-4
File diff suppressed because it is too large
Load Diff
Vendored
+13
@@ -617,6 +617,19 @@ impl Job {
|
||||
.representative_pid()
|
||||
.map_or_else(|| self.id.to_string(), |pid| pid.to_string())
|
||||
}
|
||||
/// Returns the number of external processes retained by this job.
|
||||
pub fn external_process_count(&self) -> usize {
|
||||
self.tasks.iter().filter(|task| task.is_external()).count()
|
||||
}
|
||||
|
||||
/// Iterates over the external process IDs that make up this job.
|
||||
pub fn process_ids(&self) -> impl Iterator<Item = sys::process::ProcessId> + '_ {
|
||||
self.tasks.iter().filter_map(|task| match task {
|
||||
JobTask::External(process) => process.pid(),
|
||||
JobTask::Internal(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/// Tries to retrieve a "representative" pid for the job.
|
||||
pub fn representative_pid(&self) -> Option<sys::process::ProcessId> {
|
||||
|
||||
+20
@@ -243,6 +243,26 @@ fn terminate_raw_handle(handle: RawHandle) -> bool {
|
||||
unsafe { TerminateProcess(handle, 1) != 0 }
|
||||
}
|
||||
|
||||
/// Checks whether a duplicated Windows process handle still refers to a running process.
|
||||
#[cfg(windows)]
|
||||
#[must_use]
|
||||
pub fn process_handle_is_running(handle: &OwnedHandle) -> bool {
|
||||
use windows_sys::Win32::{
|
||||
Foundation::WAIT_TIMEOUT,
|
||||
System::Threading::WaitForSingleObject,
|
||||
};
|
||||
|
||||
// SAFETY: `handle` is a live duplicated process handle with synchronization access.
|
||||
unsafe { WaitForSingleObject(handle.as_raw_handle(), 0) == WAIT_TIMEOUT }
|
||||
}
|
||||
|
||||
/// Terminates the process referenced by a duplicated Windows process handle.
|
||||
#[cfg(windows)]
|
||||
#[must_use]
|
||||
pub fn terminate_process_handle(handle: &OwnedHandle) -> bool {
|
||||
terminate_raw_handle(handle.as_raw_handle())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn terminate_process_id(pid: sys::process::ProcessId) -> bool {
|
||||
use windows_sys::Win32::Foundation::CloseHandle;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
- Fixed Anthropic web search sending unsupported temperature parameters to sampling-restricted Claude models ([#7195](https://github.com/can1357/oh-my-pi/pull/7195) by [@will-bogusz](https://github.com/will-bogusz)).
|
||||
- Fixed mid-turn steering/peer-interrupt tool skips rendering as errors (red ✘, red border/text) in the TUI; pending and in-flight interrupt placeholders now render as neutral info cards while preserving whether `tool.execute` started ([#7199](https://github.com/can1357/oh-my-pi/issues/7199)).
|
||||
- Added `Shift+Up` as a second default for the message dequeue, so the shortcut is reachable in macOS Terminal.app where Option is consumed for character composition.
|
||||
- Added in-process `pgrep`, `pkill`, `pidwait`, and `top` shell builtins with cross-platform process discovery, BSD/procps-style filters, pidfile handling, signal selection, waiting, and snapshots.
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -36,12 +37,10 @@
|
||||
- Preserved explicit `-e`/`--extension` and `--hook` packages under
|
||||
`--no-extensions` while excluding ambient extension factories and sibling
|
||||
capabilities from settings or installed OMP packages.
|
||||
### Fixed
|
||||
|
||||
- Fixed explicit `thinking` metadata in `models.yml` custom definitions and `modelOverrides` being replaced by canonical catalog policy during model rebuilding. ([#7307](https://github.com/can1357/oh-my-pi/issues/7307))
|
||||
### Fixed
|
||||
|
||||
- Fixed the auto-titler installing a model's whole answer as the session title when the tiny title model ignored the titling task and answered the first user message instead. `normalizeGeneratedTitle` now rejects overlong output (>80 chars or >12 words) so the caller defers titling to the next user turn rather than accepting a full sentence ([#7303](https://github.com/can1357/oh-my-pi/issues/7303)).
|
||||
- Fixed the in-process `kill` builtin to validate signals, preserve negative PID operands, signal every process in pipeline jobs, continue after bad targets, and refuse non-probe signals aimed at the host process or process group.
|
||||
|
||||
|
||||
## [17.2.3] - 2026-08-01
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Use ONLY for one binary or a short pipeline that computes a fact (`wc -l`, `sort
|
||||
- `pty: true` only for terminal interaction (`sudo`, `ssh`).
|
||||
- Order-dependent commands use `&&` in one call; independent calls may run concurrently.
|
||||
- Internal URIs (`skill://`, `agent://`, …) auto-resolve to paths.
|
||||
{{#if hasShellBuiltins}}- aux utils available: mkdir, wc, sort, comm, diff, uniq, base64, cmp, md5sum, sha{1,224,256,384,512}sum, b2sum, basename, dirname, readlink, realpath, touch, stat, date, mktemp, seq, yes, printenv, truncate, tac, nproc, uname, whoami, hostname, which, cut, tee, tr, paste, sed, xargs, jq, rm, mv, ln, ts, sponge, ifne, isutf8, combine{{#unless isWindows}}, errno{{/unless}}{{/if}}
|
||||
{{#if hasShellBuiltins}}- aux utils available: mkdir, wc, sort, comm, diff, uniq, base64, cmp, md5sum, sha{1,224,256,384,512}sum, b2sum, basename, dirname, readlink, realpath, touch, stat, date, mktemp, seq, yes, printenv, truncate, tac, nproc, uname, whoami, hostname, which, pgrep, pkill, pidwait, top, cut, tee, tr, paste, sed, xargs, jq, rm, mv, ln, ts, sponge, ifne, isutf8, combine{{#unless isWindows}}, errno{{/unless}}{{/if}}
|
||||
{{#if asyncEnabled}}- `async: true` defers a finite command's result; it does not extend `timeout`.{{/if}}
|
||||
</instruction>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user