The PRD's first acceptance gate now holds: grep -RinE '\bx\.ai\b|grok' crates/ --include='*.rs' → 0 matches (exempt: NOTICE and third-party license archives, README provenance, and the required 'Based on Grok Build Open Source' attribution, now sourced from version_attribution.txt). Wire-visible renames (both sides in this repo, changed in lockstep): - Auth method id 'grok.com' → 'kimi-code' (AuthMethodKind::KimiCode). - Every x.ai/* and _x.ai/* ACP ext method and meta key → kigi/* / _kigi/* (~200 names; grokShell → kigiShell). Session-file replay keeps a read-side alias for the legacy '_x.ai/session/update' method so existing updates.jsonl histories load; writes emit only the new name (both directions test-pinned). - Agent types grok-build* → kigi* with a documented legacy-prefix alias at resolution time so persisted sessions keep resolving. - ToolNamespace/BuiltinAgentName GrokBuild* → Kigi* (wire snake_case kigi/kigi_concise/kigi_hashline; schema regenerated); grok_build implementation dirs renamed to kigi*. - x-grok-* headers → x-kigi-*, __GROK_* sentinels → __KIGI_*, themes grokday/groknight → kigiday/kiginight (old persisted values fall back to the default theme), web_fetch allowlist xAI hosts → kimi.com + moonshot platforms, changelog CDN → this repo, grok-build changelog archives deleted. - BYOK default endpoint removed: [endpoints] api_base_url is now truly optional with NO default — consumers fail fast with the flag name when unset (no silent x.ai egress). Mock harnesses inject it explicitly. - System-prompt identity fixed: 'released by xAI' → 'an unofficial community CLI for Kimi' (template + regenerated encrypted form). Also repaired pre-existing grok-era test debt found by the sweep: the stale trace_classify default-model pin, the grok-pager UA label test, pty-harness stale-binary reuse and non-hermetic moonshot routing (a PTY test could previously reach the real api.moonshot.cn), and the outdated oauth fixture scope key. Gates: §9 grep 0; fmt clean; workspace check/clippy 0/0 (-D warnings); FULL cargo test --workspace: 234 suites, 21,961 passed, 0 failed; deny advisories ok.
94 lines
3.7 KiB
Rust
94 lines
3.7 KiB
Rust
//! Linux system sleep/wake via systemd-logind's `PrepareForSleep` D-Bus
|
|
//! signal, with a `delay` inhibitor lock so we get a short window to react
|
|
//! before the system actually sleeps.
|
|
//!
|
|
//! Uses the `zbus` blocking API on a dedicated thread so we don't require the
|
|
//! caller to run any particular async runtime. If the system bus or logind is
|
|
//! unavailable (non-systemd distro, container, permission error), `start`
|
|
//! returns `None` and the caller degrades gracefully.
|
|
|
|
use std::thread;
|
|
|
|
use super::{PowerCallback, PowerEvent};
|
|
|
|
const DEST: &str = "org.freedesktop.login1";
|
|
const PATH: &str = "/org/freedesktop/login1";
|
|
const IFACE: &str = "org.freedesktop.login1.Manager";
|
|
|
|
/// Linux listener handle.
|
|
///
|
|
/// There is intentionally no clean stop: the worker thread parks on a blocking
|
|
/// logind signal iterator, which cannot be interrupted without a signal
|
|
/// arriving, so dropping this neither joins nor cancels it. The thread (and its
|
|
/// D-Bus connection + sleep-delay inhibitor fd) live until process exit. That
|
|
/// is acceptable for the only intended use — a single process-lifetime listener
|
|
/// whose callback holds a `Weak` ref and no-ops once the owner is gone. (macOS
|
|
/// can `CFRunLoopStop` from `Drop` and so joins; Linux cannot — hence the
|
|
/// asymmetry, and why there is no `Drop` impl here.)
|
|
pub(crate) struct Listener;
|
|
|
|
impl Listener {
|
|
pub(crate) fn start(callback: PowerCallback) -> Option<Self> {
|
|
// Probe synchronously so registration failures return `None` to the
|
|
// caller rather than dying silently on the worker thread.
|
|
let conn = zbus::blocking::Connection::system().ok()?;
|
|
let proxy = zbus::blocking::Proxy::new(&conn, DEST, PATH, IFACE).ok()?;
|
|
let signals = proxy.receive_signal("PrepareForSleep").ok()?;
|
|
|
|
thread::Builder::new()
|
|
.name("xai-power-listener".into())
|
|
.spawn(move || run_thread(proxy, signals, callback))
|
|
.ok()?;
|
|
|
|
Some(Self)
|
|
}
|
|
}
|
|
|
|
/// Take a `delay` sleep inhibitor: logind holds off sleep until the returned fd
|
|
/// drops. `None` if unavailable — we then react without a pre-sleep window.
|
|
fn take_inhibitor(proxy: &zbus::blocking::Proxy<'_>) -> Option<zbus::zvariant::OwnedFd> {
|
|
proxy
|
|
.call(
|
|
"Inhibit",
|
|
&("sleep", "kigi", "Pause token refresh across sleep", "delay"),
|
|
)
|
|
.ok()
|
|
}
|
|
|
|
fn run_thread(
|
|
proxy: zbus::blocking::Proxy<'static>,
|
|
signals: zbus::blocking::proxy::SignalIterator<'static>,
|
|
callback: PowerCallback,
|
|
) {
|
|
// Hold the delay lock so the first PrepareForSleep(true) gives us a window.
|
|
let mut inhibitor = take_inhibitor(&proxy);
|
|
|
|
for msg in signals {
|
|
let Ok(about_to_sleep) = msg.body().deserialize::<bool>() else {
|
|
continue;
|
|
};
|
|
if about_to_sleep {
|
|
// The callback may block (bounded) waiting for an in-flight token
|
|
// refresh to finish; the `delay` inhibitor is still held across it,
|
|
// so that wait holds off the suspend (up to logind's
|
|
// `InhibitDelayMaxSec`, default 5 s). Release it only once the
|
|
// callback returns so the system can then proceed to sleep.
|
|
callback(PowerEvent::WillSleep);
|
|
inhibitor = None;
|
|
} else {
|
|
callback(PowerEvent::DidWake);
|
|
// Re-acquire the delay lock for the next sleep cycle.
|
|
inhibitor = take_inhibitor(&proxy);
|
|
}
|
|
}
|
|
|
|
drop(inhibitor);
|
|
}
|
|
|
|
pub(crate) fn current_power_state() -> crate::PowerState {
|
|
// Linux has no "dark wake" equivalent to query (the system is either
|
|
// suspended or fully awake); report Unknown so callers fall back to the
|
|
// logind `PrepareForSleep` path.
|
|
crate::PowerState::Unknown
|
|
}
|