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
+294
View File
@@ -0,0 +1,294 @@
//! `[[campaigns]]` overlays. Priority (first id wins): requirements > remote >
//! user > managed > system_managed. Applied after layer merge.
use serde::{Deserialize, Serialize};
use crate::config_override::{
ConfigOverrideEntry, PATCH_STRIP_KEYS, PatchPath, apply_patches, patch_touches_any,
take_patch_array,
};
pub const CAMPAIGNS_KEY: &str = "campaigns";
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct CampaignMeta {
#[serde(default, alias = "campaign_id")]
pub id: Option<String>,
}
#[derive(Debug, Clone)]
pub struct CampaignEntry {
pub id: String,
pub patch: toml::Table,
}
/// Disk campaigns grouped by source layer. Merged with the remote layer (by
/// priority, first id wins) in [`crate::loader::ConfigLayers::resolve_campaigns`].
#[derive(Debug, Clone, Default)]
pub struct CampaignOverrides {
pub requirements: Vec<CampaignEntry>,
pub user: Vec<CampaignEntry>,
pub managed: Vec<CampaignEntry>,
pub system_managed: Vec<CampaignEntry>,
}
pub fn take_campaigns(config: &mut toml::Value) -> Vec<ConfigOverrideEntry<CampaignMeta>> {
match take_patch_array::<CampaignMeta>(config, CAMPAIGNS_KEY) {
Ok(entries) => entries,
Err(e) => {
tracing::warn!(error = %e, "campaigns: failed to deserialize; ignoring entries");
Vec::new()
}
}
}
pub fn build_campaign_entries(
taken: Vec<ConfigOverrideEntry<CampaignMeta>>,
layer: &'static str,
) -> Vec<CampaignEntry> {
let mut out = Vec::with_capacity(taken.len());
for entry in taken {
let Some(id) = entry
.meta
.id
.as_deref()
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_owned)
else {
tracing::warn!(layer, "campaigns: entry missing id; skipped");
continue;
};
// Skip no-op entries (id only, no fields to overlay).
if entry.patch.is_empty() {
continue;
}
out.push(CampaignEntry {
id,
patch: entry.patch,
});
}
out
}
/// `sources` in priority order; first `id` wins.
pub fn merge_campaign_entries(sources: &[&[CampaignEntry]]) -> Vec<CampaignEntry> {
let mut seen = std::collections::HashSet::<String>::new();
let mut out = Vec::new();
for source in sources {
for entry in *source {
if !seen.insert(entry.id.clone()) {
continue;
}
out.push(entry.clone());
}
}
out
}
/// Drop dismissed ids from a priority-merged list, preserving order.
pub fn filter_active_campaigns(
merged: Vec<CampaignEntry>,
dismissed_ids: &std::collections::HashSet<String>,
) -> Vec<CampaignEntry> {
merged
.into_iter()
.filter(|e| !dismissed_ids.contains(&e.id))
.collect()
}
/// Ids of `active` campaigns whose patch touches any of `paths` — used to dismiss
/// campaigns when the user persists a value at one of those paths.
pub fn ids_touching_paths(active: &[CampaignEntry], paths: &[PatchPath]) -> Vec<String> {
active
.iter()
.filter(|e| patch_touches_any(&e.patch, paths))
.map(|e| e.id.clone())
.collect()
}
/// `active` is highest-priority-first; patches apply lowest-first (`.rev()`) so the
/// highest-priority source wins a leaf conflict.
pub fn apply_active_campaign_patches(effective: &mut toml::Value, active: &[CampaignEntry]) {
apply_patches(
effective,
active.iter().rev().map(|e| e.patch.clone()),
PATCH_STRIP_KEYS,
);
}
pub fn take_campaign_entries(config: &mut toml::Value, layer: &'static str) -> Vec<CampaignEntry> {
build_campaign_entries(take_campaigns(config), layer)
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(s: &str) -> toml::Value {
toml::from_str(s).unwrap()
}
fn models_default_patch(default: &str) -> toml::Table {
let mut models = toml::map::Map::new();
models.insert("default".into(), toml::Value::String(default.into()));
let mut t = toml::map::Map::new();
t.insert("models".into(), toml::Value::Table(models));
t
}
#[test]
fn campaign_overlays_any_field_over_user_config() {
let mut layer = parse(
r#"
[[campaigns]]
id = "c1"
[campaigns.models]
default = "new-model"
[campaigns.features]
web_fetch = true
"#,
);
let entries = take_campaign_entries(&mut layer, "managed");
assert!(layer.get(CAMPAIGNS_KEY).is_none());
assert_eq!(entries.len(), 1);
let mut effective =
parse("[models]\ndefault = \"old-model\"\n[features]\nweb_fetch = false\n");
apply_active_campaign_patches(&mut effective, &entries);
assert_eq!(effective["models"]["default"].as_str(), Some("new-model"));
assert_eq!(effective["features"]["web_fetch"].as_bool(), Some(true));
}
#[test]
fn merge_first_source_wins_duplicate_id() {
let req = [CampaignEntry {
id: "same".into(),
patch: models_default_patch("from-req"),
}];
let remote = [CampaignEntry {
id: "same".into(),
patch: models_default_patch("from-remote"),
}];
let merged = merge_campaign_entries(&[&req, &remote]);
assert_eq!(merged.len(), 1);
assert_eq!(
merged[0].patch["models"]["default"].as_str(),
Some("from-req")
);
}
#[test]
fn apply_highest_priority_wins_on_leaf_conflict() {
// Two *distinct* ids both set models.default; the higher-priority source
// (earlier in the merged list) must win the leaf.
let req = [CampaignEntry {
id: "req".into(),
patch: models_default_patch("from-req"),
}];
let managed = [CampaignEntry {
id: "managed".into(),
patch: models_default_patch("from-managed"),
}];
let merged = merge_campaign_entries(&[&req, &managed]);
assert_eq!(merged.len(), 2);
let mut effective = parse("[models]\ndefault = \"user-old\"\n");
apply_active_campaign_patches(&mut effective, &merged);
assert_eq!(effective["models"]["default"].as_str(), Some("from-req"));
}
#[test]
fn build_campaign_entries_skips_missing_id() {
// A `None` id and a whitespace-only id are both dropped (with a warn);
// only the entry carrying a real id survives.
let taken = vec![
ConfigOverrideEntry {
meta: CampaignMeta { id: None },
patch: models_default_patch("dropped-none"),
},
ConfigOverrideEntry {
meta: CampaignMeta {
id: Some(" ".into()),
},
patch: models_default_patch("dropped-blank"),
},
ConfigOverrideEntry {
meta: CampaignMeta {
id: Some("valid".into()),
},
patch: models_default_patch("kept"),
},
];
let out = build_campaign_entries(taken, "managed");
assert_eq!(out.len(), 1);
assert_eq!(out[0].id, "valid");
assert_eq!(out[0].patch["models"]["default"].as_str(), Some("kept"));
}
#[test]
fn campaign_id_alias_is_accepted_in_toml_and_does_not_leak_into_patch() {
for src in [
"[[campaigns]]\ncampaign_id = \"c1\"\n[campaigns.models]\ndefault = \"m\"\n",
"[[campaigns]]\nid = \"c1\"\n[campaigns.models]\ndefault = \"m\"\n",
] {
let mut layer = parse(src);
let entries = take_campaign_entries(&mut layer, "user");
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].id, "c1");
// The id key (either spelling) must be consumed by the meta, never
// land in the patch — a leaked key would deep-merge a junk top-level
// `id` into every effective config.
assert!(
entries[0].patch.get("id").is_none()
&& entries[0].patch.get("campaign_id").is_none(),
"id keys must not leak into the patch: {src}"
);
}
}
#[test]
fn requirements_win_over_campaign() {
use crate::loader::ConfigLayers;
// A campaign (even from a lower layer) can't override a field the admin
// set in requirements: `apply_campaign_overrides` re-merges requirements on top.
let mut layers = ConfigLayers {
user: parse("[models]\ndefault = \"user-old\"\n"),
user_requirements: Some(parse("[models]\ndefault = \"pinned\"\n")),
..Default::default()
};
layers.campaigns.user = vec![CampaignEntry {
id: "c1".into(),
patch: models_default_patch("campaign"),
}];
let effective =
layers.effective_config_with_campaigns(&[], &std::collections::HashSet::new());
assert_eq!(
effective["models"]["default"].as_str(),
Some("pinned"),
"requirements must beat a campaign for the same field"
);
}
#[test]
fn effective_config_honors_dismiss() {
use crate::loader::ConfigLayers;
// A dismissed campaign id stops overriding; the user's stored value returns.
let mut layers = ConfigLayers {
user: parse("[models]\ndefault = \"user-old\"\n"),
..Default::default()
};
layers.campaigns.managed = vec![CampaignEntry {
id: "c1".into(),
patch: models_default_patch("new"),
}];
let none = std::collections::HashSet::new();
let active = layers.effective_config_with_campaigns(&[], &none);
assert_eq!(active["models"]["default"].as_str(), Some("new"));
let dismissed: std::collections::HashSet<_> = ["c1".into()].into_iter().collect();
let off = layers.effective_config_with_campaigns(&[], &dismissed);
assert_eq!(off["models"]["default"].as_str(), Some("user-old"));
}
}