Synced from monorepo
Synced from monorepo Changes: - Temporarily disable session share link creation in the TUI - Do not approve plan on empty Enter from the revise prompt - Expose chat product Skills via ACP available_commands_update - Return immediately from a blocking wait on an already-completed ACP task - Split headless pager module for clearer structure - Stop git worktree prune from removing user registrations on resume - Use compaction sampler tokenizer for item token counts - Opt-in extra root CAs via GROK_EXTRA_CA_BUNDLE - Cancel all session subagents when the user stops - Let the session persistence actor exit when its session ends - Make fullscreen terminal resize much cheaper on long sessions - Report honestly from kill_task when an ACP task does not exist - Hide /usage for external-auth deployments - Forward the history-load trailer’s computer_reason to the client - Remove ineffective no-op tool reminder - Declare slash-command screen-mode support in one place - Keep settings enum picker on the committed value until Enter - Reap a PTY’s full process tree - Stream tool calls from headless mode over ACP - Bridge gateway task lifecycle to ACP for chat session background tasks - Don’t warn about truncated history on a suppressed replay - Fit full-replace summarizer input and recover on context-length errors - Stop dropping agents over an unrecognized frontmatter color - Add /undo as a slash alias for /rewind - Harden sleep/wake token-refresh paths against forced re-login - Add session/list ACP method - Give each sampling backend its own conversion module - Treat an unenrolled child process as a lint error - Suppress the cancelled marker on send-now wake turns - Stop tearing down Roslyn on every edit, and read C# diagnostics Source-Revision: 2a28b4a86cfc4a4c133c35b7fc2a6a9964387c39
This commit is contained in:
parent
500129c714
commit
dd04f397b1
367 changed files with 29489 additions and 10051 deletions
|
|
@ -96,6 +96,23 @@ pub fn current_power_state() -> PowerState {
|
|||
imp::current_power_state()
|
||||
}
|
||||
|
||||
/// Ask the OS not to sleep until the returned guard is dropped. Motivating
|
||||
/// case: a macOS dark wake re-sleeps within seconds **without any sleep
|
||||
/// notification**, so a `WillSleep` handler cannot protect work started
|
||||
/// there. `None` where unsupported or refused — callers carry on.
|
||||
/// [`SleepAssertion`] releases from `Drop` (a leaked assertion pins the
|
||||
/// machine awake); visible in `pmset -g assertions`.
|
||||
#[must_use = "the assertion is released as soon as the guard is dropped"]
|
||||
pub fn hold_awake(reason: &str) -> Option<SleepAssertion> {
|
||||
imp::hold_awake(reason).map(|inner| SleepAssertion { _inner: inner })
|
||||
}
|
||||
|
||||
/// RAII guard from [`hold_awake`]; releases the OS assertion on drop.
|
||||
#[derive(Debug)]
|
||||
pub struct SleepAssertion {
|
||||
_inner: imp::Assertion,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[path = "macos.rs"]
|
||||
mod imp;
|
||||
|
|
@ -123,6 +140,16 @@ mod imp {
|
|||
pub(crate) fn current_power_state() -> super::PowerState {
|
||||
super::PowerState::Unknown
|
||||
}
|
||||
|
||||
/// Never constructed (`hold_awake` always returns `None`); present so the
|
||||
/// cross-platform `SleepAssertion` has a field type on every target.
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct Assertion;
|
||||
|
||||
pub(crate) fn hold_awake(_reason: &str) -> Option<Assertion> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A running system-power listener. On macOS/Windows, dropping it stops the
|
||||
|
|
@ -178,3 +205,40 @@ mod tests {
|
|||
let _listener = SystemPowerListener::start(|_event| {});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, target_os = "macos"))]
|
||||
mod assertion_tests {
|
||||
/// Proves the FFI really registers with the OS, not merely that it links:
|
||||
/// take an assertion, look for it in `pmset -g assertions`, drop it, and
|
||||
/// confirm it went away. A wrong symbol or ABI would compile and silently
|
||||
/// protect nothing.
|
||||
#[test]
|
||||
fn hold_awake_registers_and_releases_a_real_assertion() {
|
||||
let name = format!("xai-system-power selftest {}", std::process::id());
|
||||
let listed = || -> String {
|
||||
std::process::Command::new("/usr/bin/pmset")
|
||||
.args(["-g", "assertions"])
|
||||
.output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
if listed().is_empty() {
|
||||
return; // no pmset (sandboxed CI) — nothing to assert against
|
||||
}
|
||||
|
||||
// The OS can refuse (sandboxed runners deny the powerd service);
|
||||
// refusal is a supported outcome, not a test failure.
|
||||
let Some(held) = super::hold_awake(&name) else {
|
||||
return;
|
||||
};
|
||||
assert!(
|
||||
listed().contains(&name),
|
||||
"assertion should be visible to the OS while held"
|
||||
);
|
||||
drop(held);
|
||||
assert!(
|
||||
!listed().contains(&name),
|
||||
"assertion must be released on drop, or the machine cannot sleep"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,3 +91,16 @@ pub(crate) fn current_power_state() -> crate::PowerState {
|
|||
// logind `PrepareForSleep` path.
|
||||
crate::PowerState::Unknown
|
||||
}
|
||||
|
||||
/// No power-assertion support on this platform: callers carry on unprotected,
|
||||
/// which is the same behaviour as before assertions existed.
|
||||
///
|
||||
/// Never constructed here (`hold_awake` always returns `None`); it exists so
|
||||
/// the cross-platform `SleepAssertion` has a field type on every target.
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct Assertion;
|
||||
|
||||
pub(crate) fn hold_awake(_reason: &str) -> Option<Assertion> {
|
||||
None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,6 +285,100 @@ extern "C" fn power_callback(
|
|||
}
|
||||
}
|
||||
|
||||
// ── Power assertions ────────────────────────────────────────────────
|
||||
|
||||
// `IOPMAssertionID` is a `uint32_t`; `kIOPMAssertionLevelOn` is 255 and
|
||||
// `kIOReturnSuccess` is 0 (IOPMLib.h / IOReturn.h).
|
||||
type IoPmAssertionId = u32;
|
||||
const K_IOPM_ASSERTION_LEVEL_ON: u32 = 255;
|
||||
const K_IO_RETURN_SUCCESS: i32 = 0;
|
||||
const K_CF_STRING_ENCODING_UTF8: u32 = 0x0800_0100;
|
||||
|
||||
/// Spelled out because `kIOPMAssertionTypePreventSystemSleep` is a
|
||||
/// `CFSTR(...)` macro, not an exported symbol — an `extern static` links
|
||||
/// and then aborts at load ("symbol not found in flat namespace"), which
|
||||
/// Linux CI can never catch. `PreventSystemSleep` is also the only type
|
||||
/// that keeps a machine resident in a dark wake
|
||||
/// (`PreventUserIdleSystemSleep` suppresses only *idle* sleep).
|
||||
const ASSERTION_TYPE_PREVENT_SYSTEM_SLEEP: &str = "PreventSystemSleep";
|
||||
|
||||
#[link(name = "IOKit", kind = "framework")]
|
||||
unsafe extern "C" {
|
||||
fn IOPMAssertionCreateWithName(
|
||||
assertion_type: *const c_void,
|
||||
assertion_level: u32,
|
||||
assertion_name: *const c_void,
|
||||
assertion_id: *mut IoPmAssertionId,
|
||||
) -> i32;
|
||||
fn IOPMAssertionRelease(assertion_id: IoPmAssertionId) -> i32;
|
||||
}
|
||||
|
||||
#[link(name = "CoreFoundation", kind = "framework")]
|
||||
unsafe extern "C" {
|
||||
fn CFStringCreateWithBytes(
|
||||
alloc: *const c_void,
|
||||
bytes: *const u8,
|
||||
num_bytes: isize,
|
||||
encoding: u32,
|
||||
is_external_representation: u8,
|
||||
) -> *const c_void;
|
||||
fn CFRelease(cf: *const c_void);
|
||||
}
|
||||
|
||||
/// Live `PreventSystemSleep` assertion, released on drop.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Assertion(IoPmAssertionId);
|
||||
|
||||
impl Drop for Assertion {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: the id came from a successful `IOPMAssertionCreateWithName`,
|
||||
// this type is not `Clone`, and `drop` runs once — so the assertion is
|
||||
// released exactly once. Releasing is what keeps a leaked assertion
|
||||
// from pinning the machine awake.
|
||||
unsafe { IOPMAssertionRelease(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `CFStringRef` from a Rust `&str`. Caller owns it and must `CFRelease`.
|
||||
fn cf_string(value: &str) -> *const c_void {
|
||||
// SAFETY: pointer/length describe a valid UTF-8 slice, read only for the
|
||||
// duration of the call — CF copies the bytes into the new string.
|
||||
unsafe {
|
||||
CFStringCreateWithBytes(
|
||||
std::ptr::null(),
|
||||
value.as_ptr(),
|
||||
value.len() as isize,
|
||||
K_CF_STRING_ENCODING_UTF8,
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn hold_awake(reason: &str) -> Option<Assertion> {
|
||||
let kind = cf_string(ASSERTION_TYPE_PREVENT_SYSTEM_SLEEP);
|
||||
if kind.is_null() {
|
||||
return None;
|
||||
}
|
||||
let name = cf_string(reason);
|
||||
if name.is_null() {
|
||||
// SAFETY: `kind` is a live CFStringRef we own.
|
||||
unsafe { CFRelease(kind) };
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut id: IoPmAssertionId = 0;
|
||||
// SAFETY: both strings are live CFStringRefs and `id` is a valid
|
||||
// out-pointer for the duration of the call.
|
||||
let rc = unsafe { IOPMAssertionCreateWithName(kind, K_IOPM_ASSERTION_LEVEL_ON, name, &mut id) };
|
||||
// The assertion retains what it needs; drop our references either way.
|
||||
// SAFETY: we created both and have not released them yet.
|
||||
unsafe {
|
||||
CFRelease(name);
|
||||
CFRelease(kind);
|
||||
}
|
||||
(rc == K_IO_RETURN_SUCCESS).then_some(Assertion(id))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -101,3 +101,16 @@ pub(crate) fn current_power_state() -> crate::PowerState {
|
|||
// callers fall back to the suspend/resume notification path.
|
||||
crate::PowerState::Unknown
|
||||
}
|
||||
|
||||
/// No power-assertion support on this platform: callers carry on unprotected,
|
||||
/// which is the same behaviour as before assertions existed.
|
||||
///
|
||||
/// Never constructed here (`hold_awake` always returns `None`); it exists so
|
||||
/// the cross-platform `SleepAssertion` has a field type on every target.
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct Assertion;
|
||||
|
||||
pub(crate) fn hold_awake(_reason: &str) -> Option<Assertion> {
|
||||
None
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue