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,466 @@
use toml::Value as TomlValue;
/// Resolve `mcp.liveness_watchers` for a session.
///
/// Thin wrapper around the canonical
/// [`crate::agent::config::resolve_mcp_liveness_watchers`], which
/// unifies the two previous implementations so they can't drift.
///
/// Pulls each layer from its appropriate TOML / runtime source:
///
/// | Layer | Source |
/// |--------------|-----------------------------------------------------------------|
/// | requirement | `[features] mcp_liveness_watchers` in `requirements.toml` |
/// | cli | (none — no CLI flag) |
/// | env | `KIGI_MCP_LIVENESS_WATCHERS` (handled by `BoolFlag::env`) |
/// | config | `[features] mcp_liveness_watchers` in `~/.kigi/config.toml` |
/// | managed | `[features] mcp_liveness_watchers` in `managed_config.toml` |
/// | feature_flag | (none yet — remote settings plumbing TBD) |
/// | default | `true` |
///
/// Returns the resolved boolean (the `Resolved::source` is discarded
/// for this call site — session-actor only needs the value).
pub fn resolve_mcp_liveness_watchers(
requirements: Option<&TomlValue>,
user: Option<&TomlValue>,
managed: Option<&TomlValue>,
) -> bool {
fn from_toml(v: Option<&TomlValue>) -> Option<bool> {
v?.get("features")?.get("mcp_liveness_watchers")?.as_bool()
}
crate::agent::config::resolve_mcp_liveness_watchers(
from_toml(requirements),
/* cli */ None,
from_toml(user),
from_toml(managed),
/* feature_flag */ None,
)
.value
}
/// Resolve `mcp.auto_restart` for a session.
///
/// Thin wrapper around the canonical
/// [`crate::agent::config::resolve_mcp_auto_restart`]. Mirrors
/// [`resolve_mcp_liveness_watchers`].
///
/// Pulls each layer from its appropriate TOML / runtime source:
///
/// | Layer | Source |
/// |--------------|-----------------------------------------------------------------|
/// | requirement | `[features] mcp_auto_restart` in `requirements.toml` |
/// | cli | (none — no CLI flag) |
/// | env | `KIGI_MCP_AUTO_RESTART` (handled by `BoolFlag::env`) |
/// | config | `[features] mcp_auto_restart` in `~/.kigi/config.toml` |
/// | managed | `[features] mcp_auto_restart` in `managed_config.toml` |
/// | feature_flag | (none yet — remote settings plumbing TBD) |
/// | default | `true` |
///
/// Returns the resolved boolean (the `Resolved::source` is discarded
/// for this call site — session-actor only needs the value).
pub fn resolve_mcp_auto_restart(
requirements: Option<&TomlValue>,
user: Option<&TomlValue>,
managed: Option<&TomlValue>,
) -> bool {
fn from_toml(v: Option<&TomlValue>) -> Option<bool> {
v?.get("features")?.get("mcp_auto_restart")?.as_bool()
}
crate::agent::config::resolve_mcp_auto_restart(
from_toml(requirements),
/* cli */ None,
from_toml(user),
from_toml(managed),
/* feature_flag */ None,
)
.value
}
/// Resolve `mcp.push_server_status` for a session.
///
/// Thin wrapper around the canonical
/// [`crate::agent::config::resolve_mcp_push_server_status`] that
/// mirrors [`resolve_mcp_liveness_watchers`].
///
/// Pulls each layer from its TOML / runtime source:
///
/// | Layer | Source |
/// |--------------|-----------------------------------------------------------------|
/// | requirement | `[features] mcp_push_server_status` in `requirements.toml` |
/// | cli | (none — no CLI flag) |
/// | env | `KIGI_MCP_PUSH_SERVER_STATUS` (handled by `BoolFlag::env`) |
/// | config | `[features] mcp_push_server_status` in `~/.kigi/config.toml` |
/// | managed | `[features] mcp_push_server_status` in `managed_config.toml` |
/// | feature_flag | (none yet — remote settings plumbing TBD) |
/// | default | `true` |
///
/// Returns the resolved boolean.
pub fn resolve_mcp_push_server_status(
requirements: Option<&TomlValue>,
user: Option<&TomlValue>,
managed: Option<&TomlValue>,
) -> bool {
fn from_toml(v: Option<&TomlValue>) -> Option<bool> {
v?.get("features")?.get("mcp_push_server_status")?.as_bool()
}
crate::agent::config::resolve_mcp_push_server_status(
from_toml(requirements),
/* cli */ None,
from_toml(user),
from_toml(managed),
/* feature_flag */ None,
)
.value
}
/// Resolve `mcp.recursive_config_watch` for the leader's
/// `ConfigFileWatcher` spawn path.
///
/// Thin wrapper around the canonical
/// [`crate::agent::config::resolve_mcp_recursive_config_watch`] —
/// mirrors the same wrapper pattern as the other MCP resolvers so the
/// two implementations can't drift.
///
/// Pulls each layer from its TOML / runtime source:
///
/// | Layer | Source |
/// |--------------|---------------------------------------------------------------------|
/// | requirement | `[features] mcp_recursive_config_watch` in `requirements.toml` |
/// | cli | (none — no CLI flag) |
/// | env | `KIGI_MCP_RECURSIVE_CONFIG_WATCH` (handled by `BoolFlag::env`) |
/// | config | `[features] mcp_recursive_config_watch` in `~/.kigi/config.toml` |
/// | managed | `[features] mcp_recursive_config_watch` in `managed_config.toml` |
/// | feature_flag | (none yet — remote settings plumbing TBD) |
/// | default | `true` |
///
/// Returns the resolved boolean (the `Resolved::source` is discarded
/// for this call site — the leader's watcher-spawn only needs the
/// value).
pub fn resolve_mcp_recursive_config_watch(
requirements: Option<&TomlValue>,
user: Option<&TomlValue>,
managed: Option<&TomlValue>,
) -> bool {
fn from_toml(v: Option<&TomlValue>) -> Option<bool> {
v?.get("features")?
.get("mcp_recursive_config_watch")?
.as_bool()
}
crate::agent::config::resolve_mcp_recursive_config_watch(
from_toml(requirements),
/* cli */ None,
from_toml(user),
from_toml(managed),
/* feature_flag */ None,
)
.value
}
/// Default MCP startup-handshake timeout (seconds) when nothing overrides it.
/// Kept in sync with `kigi_mcp::servers`'s standalone fallback.
pub const DEFAULT_MCP_STARTUP_TIMEOUT_SECS: u64 = 30;
/// Env override for the MCP startup timeout, in milliseconds (shared with
/// common third-party tooling, so an existing setting carries over).
const ENV_MCP_TIMEOUT_MS: &str = "MCP_TIMEOUT";
/// Env override for the MCP startup timeout, in seconds (grok-native).
const ENV_MCP_STARTUP_TIMEOUT_SECS: &str = "KIGI_MCP_STARTUP_TIMEOUT_SECS";
/// Cached remote settings `mcp_startup_timeout_secs` (`0` = unset). MCP servers start
/// from free functions with no handle to the live `RemoteSettings`, so the
/// remote tier is cached here when settings are applied.
static REMOTE_MCP_STARTUP_TIMEOUT_SECS: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
/// Record the remote settings `mcp_startup_timeout_secs` for the free-function
/// resolver. Call wherever `RemoteSettings` is applied. `0` is treated as unset.
pub fn cache_remote_mcp_startup_timeout_secs(value: Option<u64>) {
REMOTE_MCP_STARTUP_TIMEOUT_SECS.store(value.unwrap_or(0), std::sync::atomic::Ordering::Relaxed);
}
fn cached_remote_mcp_startup_timeout_secs() -> Option<u64> {
match REMOTE_MCP_STARTUP_TIMEOUT_SECS.load(std::sync::atomic::Ordering::Relaxed) {
0 => None,
secs => Some(secs),
}
}
/// Global default MCP startup-handshake timeout (seconds), applying the cached
/// remote tier. Global fallback only — a per-server
/// `startup_timeout_sec` / `_meta.startupTimeoutMs` still wins (see
/// `session::mcp_servers`).
pub fn resolved_mcp_startup_timeout_secs() -> u64 {
resolve_mcp_startup_timeout_secs(cached_remote_mcp_startup_timeout_secs())
}
/// Resolve the global MCP startup-handshake timeout (seconds). Precedence:
/// requirements.toml `[mcp].startup_timeout_sec` > env (`MCP_TIMEOUT` ms /
/// `KIGI_MCP_STARTUP_TIMEOUT_SECS` secs) > effective `config.toml [mcp]` >
/// remote settings `remote` > [`DEFAULT_MCP_STARTUP_TIMEOUT_SECS`].
pub fn resolve_mcp_startup_timeout_secs(remote: Option<u64>) -> u64 {
fn extract(v: &toml::Value) -> Option<u64> {
v.get("mcp")?
.get("startup_timeout_sec")?
.as_integer()
.and_then(|n| u64::try_from(n).ok())
.filter(|n| *n > 0)
}
let requirements = crate::config::load_merged_requirements()
.as_ref()
.and_then(extract);
let config = crate::config::load_effective_config()
.ok()
.as_ref()
.and_then(extract);
resolve_mcp_startup_timeout_precedence(
requirements,
mcp_startup_timeout_from_env(),
config,
remote,
)
}
/// `MCP_TIMEOUT` (ms, rounded up so a sub-second value never becomes 0s) >
/// `KIGI_MCP_STARTUP_TIMEOUT_SECS` (secs). Unparseable/zero values are ignored.
fn mcp_startup_timeout_from_env() -> Option<u64> {
if let Some(ms) = std::env::var(ENV_MCP_TIMEOUT_MS)
.ok()
.and_then(|s| s.trim().parse::<u64>().ok())
.filter(|n| *n > 0)
{
return Some(ms.div_ceil(1000));
}
std::env::var(ENV_MCP_STARTUP_TIMEOUT_SECS)
.ok()
.and_then(|s| s.trim().parse::<u64>().ok())
.filter(|n| *n > 0)
}
/// Pure precedence for [`resolve_mcp_startup_timeout_secs`] (tiers injected so it
/// is unit-testable without touching env/disk).
fn resolve_mcp_startup_timeout_precedence(
requirements: Option<u64>,
env: Option<u64>,
config: Option<u64>,
remote: Option<u64>,
) -> u64 {
requirements
.or(env)
.or(config)
.or(remote)
.unwrap_or(DEFAULT_MCP_STARTUP_TIMEOUT_SECS)
}
#[cfg(test)]
mod mcp_startup_timeout_tests {
use super::{DEFAULT_MCP_STARTUP_TIMEOUT_SECS, resolve_mcp_startup_timeout_precedence as r};
#[test]
fn precedence_requirements_env_config_remote_default() {
assert_eq!(r(None, None, None, None), DEFAULT_MCP_STARTUP_TIMEOUT_SECS);
assert_eq!(r(Some(5), Some(6), Some(7), Some(8)), 5); // requirements highest
assert_eq!(r(None, Some(6), Some(7), Some(8)), 6); // env
assert_eq!(r(None, None, Some(7), Some(8)), 7); // config
assert_eq!(r(None, None, None, Some(8)), 8); // remote
}
}
// ── MCP max output bytes (inline tool-result cap) ───────────────────────────
//
// Full multi-tier resolve lives only here (shell can read config/requirements).
// Tools holds a single effective atomic: we resolve once on apply and push the
// result via `set_mcp_max_output_bytes` so free-function truncation sees it.
/// Default MCP tool-result inline cap (bytes).
pub const DEFAULT_MAX_MCP_OUTPUT_BYTES: usize = kigi_tools::MCP_MAX_OUTPUT_BYTES;
/// Resolve the full stack for `remote` and seed the tools-crate effective limit.
///
/// Call wherever `RemoteSettings` is applied (same sites as
/// [`cache_remote_mcp_startup_timeout_secs`]). Unlike that helper — which only
/// caches the remote tier for a free-function resolver still living in shell —
/// this pushes the *fully resolved* value into tools (tools cannot re-read
/// config/requirements on every use).
pub fn cache_remote_max_mcp_output_bytes(remote: Option<u64>) {
kigi_tools::set_mcp_max_output_bytes(resolve_max_mcp_output_bytes(remote));
}
/// Extract `[mcp] max_output_bytes` from one TOML root. Positive integers only.
fn max_mcp_output_bytes_from_toml(v: &toml::Value) -> Option<usize> {
let raw = v.get("mcp")?.get("max_output_bytes")?.as_integer()?;
u64::try_from(raw)
.ok()
.and_then(|n| usize::try_from(n).ok())
.filter(|n| *n > 0)
}
/// Resolve the MCP tool-result inline cap (bytes) — **global / atomic path**
/// (no cwd, so no project tier; see [`resolve_max_mcp_output_bytes_for_cwd`]).
///
/// Precedence (highest first):
/// 1. requirements.toml `[mcp] max_output_bytes`
/// 2. env `KIGI_MAX_MCP_OUTPUT_BYTES` / `MAX_MCP_OUTPUT_BYTES`
/// (Grok-native wins when both set)
/// 3. effective `config.toml [mcp] max_output_bytes`
/// 4. remote settings `RemoteSettings.max_mcp_output_bytes`
/// 5. [`DEFAULT_MAX_MCP_OUTPUT_BYTES`] (20_000)
pub fn resolve_max_mcp_output_bytes(remote: Option<u64>) -> usize {
let remote_usize = remote
.and_then(|n| usize::try_from(n).ok())
.filter(|n| *n > 0);
let requirements = crate::config::load_merged_requirements()
.as_ref()
.and_then(max_mcp_output_bytes_from_toml);
let config = crate::config::load_effective_config()
.ok()
.as_ref()
.and_then(max_mcp_output_bytes_from_toml);
resolve_max_mcp_output_bytes_precedence(
requirements,
kigi_tools::mcp_max_output_bytes_from_env(),
None, // project tier needs a cwd — see resolve_max_mcp_output_bytes_for_cwd
config,
remote_usize,
)
}
/// Project tier of the MCP output cap: `[mcp] max_output_bytes` from the
/// `.kigi/config.toml` chain (`cwd` → git root), deepest file wins.
///
/// Folder-trust-gated: an untrusted checkout must not raise (context-stuffing
/// / cost vector) or lower the cap, matching how project plugin paths and
/// repo env contributions are gated.
fn project_max_mcp_output_bytes(cwd: &std::path::Path) -> Option<usize> {
if !crate::agent::folder_trust::project_scope_allowed(cwd) {
return None;
}
let mut value = None;
// Repo-root-first → cwd-last: later (deeper) files overwrite.
for config_path in crate::config::find_project_configs(cwd) {
if let Ok(toml_val) = kigi_config::load_config_file(&config_path)
&& let Some(v) = max_mcp_output_bytes_from_toml(&toml_val)
{
value = Some(v);
}
}
value
}
/// Session-scoped MCP output cap: `Some(bytes)` **only when the project tier
/// wins** the full precedence stack for `cwd`; `None` otherwise.
///
/// The caller seeds `Some` values into the session's `TruncationCfg` resource
/// (consulted by MCP truncation *before* the process-global atomic). Returning
/// `None` when any higher- or lower-priority tier would win keeps the atomic
/// authoritative for those — including live remote settings refresh — so sessions
/// without a repo-level value behave exactly as before.
///
/// The project tier only wins when requirements and env are absent (it sits
/// above user config / remote settings / default), so `Some` here is simply
/// "requirements and env unset, project value present".
pub fn resolve_max_mcp_output_bytes_for_cwd(cwd: &std::path::Path) -> Option<usize> {
let requirements = crate::config::load_merged_requirements()
.as_ref()
.and_then(max_mcp_output_bytes_from_toml);
if requirements.is_some() || kigi_tools::mcp_max_output_bytes_from_env().is_some() {
return None;
}
project_max_mcp_output_bytes(cwd)
}
/// Pure precedence for [`resolve_max_mcp_output_bytes`] (tiers injected so it is
/// unit-testable without env/disk).
///
/// `requirements` > `env` > `project` > `config` > `remote` > default.
pub(crate) fn resolve_max_mcp_output_bytes_precedence(
requirements: Option<usize>,
env: Option<usize>,
project: Option<usize>,
config: Option<usize>,
remote: Option<usize>,
) -> usize {
requirements
.or(env)
.or(project)
.or(config)
.or(remote)
.unwrap_or(DEFAULT_MAX_MCP_OUTPUT_BYTES)
}
#[cfg(test)]
mod max_mcp_output_bytes_tests {
use super::{
DEFAULT_MAX_MCP_OUTPUT_BYTES, max_mcp_output_bytes_from_toml,
resolve_max_mcp_output_bytes_precedence as r,
};
#[test]
fn precedence_requirements_env_project_config_remote_default() {
assert_eq!(
r(None, None, None, None, None),
DEFAULT_MAX_MCP_OUTPUT_BYTES
);
assert_eq!(r(Some(1), Some(2), Some(9), Some(3), Some(4)), 1); // requirements highest
assert_eq!(r(None, Some(2), Some(9), Some(3), Some(4)), 2); // env beats project
assert_eq!(r(None, None, Some(9), Some(3), Some(4)), 9); // project beats user config
assert_eq!(r(None, None, None, Some(3), Some(4)), 3); // user config beats remote
assert_eq!(r(None, None, None, None, Some(4)), 4); // remote beats default
}
#[test]
fn toml_extractor_rejects_non_positive_and_wrong_types() {
let ok: toml::Value = toml::from_str("[mcp]\nmax_output_bytes = 40000").unwrap();
assert_eq!(max_mcp_output_bytes_from_toml(&ok), Some(40_000));
for bad in [
"[mcp]\nmax_output_bytes = 0",
"[mcp]\nmax_output_bytes = -5",
"[mcp]\nmax_output_bytes = \"big\"",
"[other]\nmax_output_bytes = 5",
] {
let v: toml::Value = toml::from_str(bad).unwrap();
assert_eq!(max_mcp_output_bytes_from_toml(&v), None, "input: {bad}");
}
}
/// The project-tier walk: repo-root-first, deepest file wins; files
/// without the key leave the running value untouched.
///
/// Uses the pure chain logic via tempdirs + `find_project_configs`
/// ordering (repo root → cwd), mirroring `project_max_mcp_output_bytes`
/// without the trust gate (exercised separately — trust is inert in
/// dev/test builds, see `folder_trust_inert`).
#[test]
fn project_chain_deepest_file_wins() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
// Make it a git repo so the chain walks subdir → root.
git2::Repository::init(root).unwrap();
let sub = root.join("crates").join("thing");
std::fs::create_dir_all(sub.join(".kigi")).unwrap();
std::fs::create_dir_all(root.join(".kigi")).unwrap();
std::fs::write(
root.join(".kigi/config.toml"),
"[mcp]\nmax_output_bytes = 30000\n",
)
.unwrap();
// Only the repo root sets the key → root value applies at the subdir.
assert_eq!(super::project_max_mcp_output_bytes(&sub), Some(30_000));
// The subdir sets it too → deeper file wins.
std::fs::write(
sub.join(".kigi/config.toml"),
"[mcp]\nmax_output_bytes = 50000\n",
)
.unwrap();
assert_eq!(super::project_max_mcp_output_bytes(&sub), Some(50_000));
// A deeper file *without* the key does not mask the root value.
std::fs::write(sub.join(".kigi/config.toml"), "[ui]\nvim_mode = true\n").unwrap();
assert_eq!(super::project_max_mcp_output_bytes(&sub), Some(30_000));
// No .kigi files with the key anywhere → None.
std::fs::remove_file(root.join(".kigi/config.toml")).unwrap();
assert_eq!(super::project_max_mcp_output_bytes(&sub), None);
}
}