Files
Kigi-CLI/crates/codegen/kigi-system-power/src/linux.rs
T
ZacharyZhang-NY 6f31415ed6 §9 acceptance: grep-zero sweep — every internal x.ai/grok identifier renamed
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.
2026-07-18 02:48:46 -04:00

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
}