M0: compilable skeleton — Kigi 0.1.0 fork surgery

Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.

Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
  kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
  ptyctl, ptyctl-cli, third_party/ unchanged; proto package
  xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
  KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
  (templates re-encrypted)

Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
  trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
  module & dc_log, heap-profile uploader, auth-diagnostics uploader,
  session-analytics halves of feedback; local zero-egress observability
  preserved in new kigi-log crate (unified log, --debug firehose,
  subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
  direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
  relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
  ~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
  kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
  session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
  shell util

Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
  https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
  https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
  Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted

Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
  workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
  all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
  exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
  insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean

Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
  (new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
  fast-worktree); RSS measurement tests serialized via serial_test

Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
  notices sustained; kigi-tools ported-code notices extended; README,
  CONTRIBUTING, SECURITY, AGENTS.md rewritten

Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
2026-07-17 05:31:01 -04:00
commit d6c20fc13f
2612 changed files with 1353757 additions and 0 deletions
@@ -0,0 +1,304 @@
//! Cross-platform IPC transport for leader<->client communication.
//!
//! - **Unix:** [`LeaderStream`] / [`LeaderListener`] are type aliases for
//! `tokio::net::UnixStream` / `UnixListener`. Zero wrapper, no unsafe.
//! - **Windows:** wraps `tokio::net::windows::named_pipe::*` (tokio doesn't
//! expose AF_UNIX on Windows). The leader's filesystem path is hashed
//! into `\\.\pipe\grok-leader-<hash>` so callers keep their path-based API.
//!
#[cfg(unix)]
pub use tokio::net::UnixListener as LeaderListener;
#[cfg(unix)]
pub use tokio::net::UnixStream as LeaderStream;
/// Has a leader bound a listener at `path`?
///
/// - Unix: stats the socket file.
/// - Windows: probes the named pipe (Named Pipes don't appear in the
/// filesystem, so `path.exists()` doesn't work).
pub fn listener_is_ready(path: &std::path::Path) -> bool {
#[cfg(unix)]
{
path.exists()
}
#[cfg(windows)]
{
windows_impl::listener_is_ready(path)
}
}
#[cfg(windows)]
pub use windows_impl::{LeaderListener, LeaderStream};
#[cfg(windows)]
mod windows_impl {
use std::io;
use std::path::Path;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tracing::debug;
/// Bidirectional IPC stream wrapping a connected named pipe (server-
/// or client-side, depending on how it was created).
pub struct LeaderStream {
inner: StreamInner,
}
enum StreamInner {
Server(tokio::net::windows::named_pipe::NamedPipeServer),
Client(tokio::net::windows::named_pipe::NamedPipeClient),
}
impl LeaderStream {
/// Connect to a listener at `path`. The path is translated to a
/// named-pipe name and `ClientOptions::open` is used.
pub async fn connect<P: AsRef<Path>>(path: P) -> io::Result<Self> {
use tokio::net::windows::named_pipe::ClientOptions;
// ClientOptions::open returns ERROR_PIPE_BUSY if all pipe
// instances are in use; the caller's CONNECT_TIMEOUT loop
// already retries, so we surface the error and let it handle.
let pipe_name = path_to_pipe_name(path.as_ref());
let inner = ClientOptions::new().open(pipe_name)?;
Ok(Self {
inner: StreamInner::Client(inner),
})
}
}
// tokio's NamedPipeServer / NamedPipeClient are auto-Unpin (they wrap
// PollEvented<mio::windows::NamedPipe>, which is Unpin), so our
// wrapping enum and struct are auto-Unpin as well. That means
// Pin<&mut Self>::get_mut() is safe — no unsafe needed for the
// structural projection into `inner`.
impl AsyncRead for LeaderStream {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
match &mut self.get_mut().inner {
StreamInner::Server(s) => Pin::new(s).poll_read(cx, buf),
StreamInner::Client(c) => Pin::new(c).poll_read(cx, buf),
}
}
}
impl AsyncWrite for LeaderStream {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
match &mut self.get_mut().inner {
StreamInner::Server(s) => Pin::new(s).poll_write(cx, buf),
StreamInner::Client(c) => Pin::new(c).poll_write(cx, buf),
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut self.get_mut().inner {
StreamInner::Server(s) => Pin::new(s).poll_flush(cx),
StreamInner::Client(c) => Pin::new(c).poll_flush(cx),
}
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut self.get_mut().inner {
StreamInner::Server(s) => Pin::new(s).poll_shutdown(cx),
StreamInner::Client(c) => Pin::new(c).poll_shutdown(cx),
}
}
}
/// Listener for incoming leader IPC connections. Holds the pipe name
/// plus the next pre-created server instance (Windows named pipes
/// require pre-creating an instance per pending connection).
pub struct LeaderListener {
pipe_name: std::ffi::OsString,
/// Next pre-created server instance, ready for `connect().await`.
/// We rotate: take this one, await its connect, immediately create
/// the next one for the following accept(). The first instance is
/// created in `bind()` with `first_pipe_instance(true)` to lock
/// out other processes from squatting the pipe name.
///
/// tokio::sync::Mutex (not parking_lot) because accept() holds the
/// lock across `server.connect().await`.
next_server: tokio::sync::Mutex<Option<tokio::net::windows::named_pipe::NamedPipeServer>>,
}
impl LeaderListener {
/// Reserve a named-pipe name (no on-disk file is created).
pub fn bind<P: AsRef<Path>>(path: P) -> io::Result<Self> {
use tokio::net::windows::named_pipe::ServerOptions;
let pipe_name = path_to_pipe_name(path.as_ref());
let first = ServerOptions::new()
.first_pipe_instance(true)
.create(&pipe_name)?;
Ok(Self {
pipe_name,
next_server: tokio::sync::Mutex::new(Some(first)),
})
}
/// Wait for the next incoming connection. Mirrors
/// `UnixListener::accept`, returning a connected stream and a unit
/// placeholder where Unix would return the peer address (named
/// pipes don't carry one).
pub async fn accept(&self) -> io::Result<(LeaderStream, ())> {
use tokio::net::windows::named_pipe::ServerOptions;
// Take the pending instance (or create one), await a client, then
// pre-create the next. On connect() error, drop the instance and
// retry with a fresh one — returning early would leave the slot
// empty and brick the listener. Bounded with a backoff so a
// persistently failing connect() can't busy-spin.
const MAX_ACCEPT_ATTEMPTS: usize = 10;
const RETRY_BACKOFF: Duration = Duration::from_millis(20);
let mut slot = self.next_server.lock().await;
let mut last_err: Option<io::Error> = None;
for attempt in 0..MAX_ACCEPT_ATTEMPTS {
let server = match slot.take() {
Some(server) => server,
None => ServerOptions::new().create(&self.pipe_name)?,
};
match server.connect().await {
Ok(()) => {
*slot = Some(ServerOptions::new().create(&self.pipe_name)?);
return Ok((
LeaderStream {
inner: StreamInner::Server(server),
},
(),
));
}
Err(e) => {
// Failed `server` drops here, freeing the instance.
debug!(attempt, error = %e, "named-pipe accept connect failed; retrying");
last_err = Some(e);
tokio::time::sleep(RETRY_BACKOFF).await;
}
}
}
// Best-effort re-arm; take-or-create above still recovers if this fails.
if let Ok(fresh) = ServerOptions::new().create(&self.pipe_name) {
*slot = Some(fresh);
}
Err(last_err
.unwrap_or_else(|| io::Error::other("LeaderListener: accept exhausted retries")))
}
}
/// Whether a leader has a pipe bound at `path`.
///
/// Probes with `WaitNamedPipeW` (non-connecting), not `ClientOptions::open`,
/// which would open a real client the leader's `accept()` consumes as a
/// phantom session. `ERROR_FILE_NOT_FOUND` means absent; `TRUE` or any other
/// error (e.g. `ERROR_SEM_TIMEOUT`: exists but busy) means ready.
pub fn listener_is_ready(path: &Path) -> bool {
use std::os::windows::ffi::OsStrExt;
use windows::Win32::Foundation::{ERROR_FILE_NOT_FOUND, GetLastError};
use windows::Win32::System::Pipes::WaitNamedPipeW;
use windows::core::PCWSTR;
// 1 ms (a real timeout, not 0 = "server default").
const PROBE_TIMEOUT_MS: u32 = 1;
let pipe_name = path_to_pipe_name(path);
let wide: Vec<u16> = pipe_name
.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect();
if unsafe { WaitNamedPipeW(PCWSTR(wide.as_ptr()), PROBE_TIMEOUT_MS) }.as_bool() {
return true;
}
// FALSE: only a missing pipe means not-ready.
let err = unsafe { GetLastError() };
err != ERROR_FILE_NOT_FOUND
}
/// Full named-pipe path: `\\.\pipe\<leaf>`.
fn path_to_pipe_name(path: &Path) -> std::ffi::OsString {
let mut name = std::ffi::OsString::from(r"\\.\pipe\");
name.push(pipe_leaf_name(path));
name
}
/// Deterministic leaf name (`grok-leader-<hash>`) for a filesystem path.
///
/// Uses SipHash-1-3 with fixed keys so the hash is stable across Rust
/// versions (unlike `DefaultHasher`, whose algorithm is unspecified).
fn pipe_leaf_name(path: &Path) -> std::ffi::OsString {
use siphasher::sip::SipHasher13;
use std::hash::{Hash, Hasher};
// Fixed keys — must never change once shipped.
let mut hasher = SipHasher13::new_with_keys(0x67726f6b_6c656164, 0x65725f70_69706521);
path.hash(&mut hasher);
let hash = hasher.finish();
std::ffi::OsString::from(format!("grok-leader-{hash:016x}"))
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn pipe_name_is_deterministic() {
let a = path_to_pipe_name(Path::new("/tmp/grok.sock"));
let b = path_to_pipe_name(Path::new("/tmp/grok.sock"));
assert_eq!(a, b);
}
#[test]
fn different_paths_produce_different_names() {
let a = path_to_pipe_name(Path::new("/tmp/a.sock"));
let b = path_to_pipe_name(Path::new("/tmp/b.sock"));
assert_ne!(a, b);
}
#[test]
fn pipe_name_has_correct_prefix() {
let name = path_to_pipe_name(Path::new("/tmp/test.sock"));
let s = name.to_string_lossy();
assert!(s.starts_with(r"\\.\pipe\grok-leader-"), "got: {s}");
}
#[test]
fn pipe_name_is_bounded() {
let long_path = "/".to_owned() + &"a".repeat(500);
let name = path_to_pipe_name(Path::new(&long_path));
// \\.\pipe\grok-leader- (20 chars) + 16 hex chars = 36 total
assert!(name.len() <= 256, "pipe name too long: {}", name.len());
}
#[tokio::test]
async fn listener_is_ready_tracks_pipe_lifecycle() {
// Unique path per process so parallel test binaries don't collide on
// the derived pipe name.
let path =
std::env::temp_dir().join(format!("grok-ready-probe-{}.sock", std::process::id()));
// Nothing bound yet -> ERROR_FILE_NOT_FOUND -> not ready.
assert!(!listener_is_ready(&path));
let listener = LeaderListener::bind(&path).unwrap();
// Ready as soon as the pipe is bound, before any accept().
assert!(listener_is_ready(&path));
// After the last instance is dropped the pipe name disappears.
drop(listener);
assert!(!listener_is_ready(&path));
}
}
}