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,207 @@
use crate::agent::subagent::SubagentSpawnContext;
use crate::session::SessionCommand;
use agent_client_protocol as acp;
use kigi_acp_lib::AcpAgentGatewaySender as GatewaySender;
use kigi_tools::implementations::grok_build::task::types::{SubagentRequest, SubagentResult};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::{mpsc, oneshot};
pub(crate) type GatewayOut = <acp::AgentSide as kigi_acp_lib::AcpSide>::OutMessage;
pub(crate) fn test_gateway() -> GatewaySender {
let (tx, _rx) = mpsc::unbounded_channel();
GatewaySender::new(tx)
}
/// Like `test_gateway` but returns the receiver; keep it alive for the test.
pub(crate) fn test_gateway_with_receiver() -> (GatewaySender, mpsc::UnboundedReceiver<GatewayOut>) {
let (tx, rx) = mpsc::unbounded_channel();
(GatewaySender::new(tx), rx)
}
/// `ctx_with_toggle` with a wired `parent_cmd_tx`.
pub(crate) fn ctx_with_toggle_and_cmd_tx(
toggle: HashMap<String, bool>,
) -> (
SubagentSpawnContext,
mpsc::UnboundedReceiver<SessionCommand>,
) {
let mut ctx = ctx_with_toggle(toggle);
let (tx, rx) = mpsc::unbounded_channel();
ctx.parent_cmd_tx = Some(tx);
(ctx, rx)
}
pub(crate) fn ctx_with_toggle(toggle: HashMap<String, bool>) -> SubagentSpawnContext {
let (tx, _rx) = mpsc::unbounded_channel();
SubagentSpawnContext {
lsp: None,
parent_max_turns: None,
gateway: test_gateway(),
client_hooks: Default::default(),
sampling_config: kigi_sampler::SamplerConfig {
api_key: None,
base_url: String::new(),
model: String::new(),
max_completion_tokens: None,
temperature: None,
top_p: None,
api_backend: Default::default(),
auth_scheme: Default::default(),
extra_headers: Default::default(),
context_window: 256_000,
client_version: None,
force_http1: false,
max_retries: None,
stream_tool_calls: false,
idle_timeout_secs: None,
client_identifier: None,
reasoning_effort: None,
deployment_id: None,
user_id: None,
origin_client: None,
attribution_callback: None,
bearer_resolver: None,
supports_backend_search: false,
compactions_remaining: None,
compaction_at_tokens: None,
doom_loop_recovery: None,
header_injector: None,
},
alpha_test_key: None,
auth_method_id: acp::AuthMethodId::new("test"),
model_id: acp::ModelId::new("test"),
storage_mode: crate::config::StorageMode::Local,
auth: None,
parent_cwd: PathBuf::from("/tmp"),
parent_session_id: "test-parent".into(),
yolo_mode: false,
subagent_event_tx: tx,
hunk_tracker_handle: kigi_hunk_tracker::HunkTrackerHandle::noop(),
hunk_tracking_enabled: false,
fs: Arc::new(kigi_workspace::file_system::LocalFs::new(PathBuf::from(
"/tmp",
))),
terminal: Arc::new(crate::terminal::TerminalRunner::new(
Arc::new(test_gateway()),
acp::SessionId::new("test"),
)),
session_env: Arc::new(HashMap::new()),
memory_config: None,
web_search_sampling_config: None,
web_fetch_config: Default::default(),
image_gen_config: Default::default(),
video_gen_config: Default::default(),
app_builder_deployer_config: Default::default(),
write_file_enabled: true,
goal_enabled: false,
ask_user_question_enabled: true,
parent_cmd_tx: None,
parent_session_info: None,
subagent_roles: HashMap::new(),
subagent_personas: HashMap::new(),
persona_io_summaries: Vec::new(),
parent_chat_state: None,
available_models: indexmap::IndexMap::new(),
subagent_model_overrides: HashMap::new(),
subagent_toggle: toggle,
disable_web_search: false,
todo_gate: false,
remote_settings: None,
laziness_debug_log: None,
backend_tools_enabled: true,
respect_gitignore: false,
path_not_found_hints: false,
plugin_registry: None,
models_manager: Default::default(),
file_tool_overrides: None,
agent_config: None,
hook_registry: None,
hook_workspace_root: String::new(),
parent_depth: 0,
inference_idle_timeout_secs: 600,
auto_compact_threshold_tiers: crate::agent::subagent::AutoCompactThresholdTiers::default(),
permission_handle: None,
worktree_type: crate::util::config::WorktreeType::Linked,
api_key_provider: None,
image_description_model: crate::test_support::TEST_MODEL.to_owned(),
workspace_ops: kigi_workspace::WorkspaceOps::for_test(),
auth_manager: Arc::new(crate::auth::AuthManager::new(
std::path::Path::new("/tmp/nonexistent-grok-test"),
crate::auth::GrokComConfig::default(),
)),
attribution_callback: None,
parent_agent_name: None,
parent_model_agent_type: None,
allowed_subagent_types: None,
parent_mcp_configs: vec![],
managed_mcp_state: crate::session::managed_mcp::ManagedMcpStateHandle::default(),
managed_mcp_proxy_base_url: String::new(),
parent_mcp_pool: None,
parent_tool_snapshot: None,
parent_skills: None,
parent_skills_config: kigi_agent::prompt::skills::SkillsConfig::default(),
parent_compat: kigi_tools::types::compat::CompatConfig::default(),
auto_wake_delivered: None,
task_output_tool_name: kigi_tools::reminders::task_completion::DEFAULT_TASK_OUTPUT_TOOL
.to_string(),
auto_wake_enabled: true,
goal_loop_active: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
parent_blocking_wait_depth: std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)),
parent_terminal_backend: None,
parent_notification_handle: None,
parent_scheduler_handle: None,
}
}
pub(crate) fn make_request(
subagent_type: &str,
) -> (SubagentRequest, oneshot::Receiver<SubagentResult>) {
let (tx, rx) = oneshot::channel();
let req = SubagentRequest {
id: uuid::Uuid::now_v7().to_string(),
prompt: "do something".into(),
description: "test task".into(),
subagent_type: subagent_type.into(),
parent_session_id: "test-parent".into(),
parent_prompt_id: Some("parent-prompt".into()),
resume_from: None,
cwd: None,
runtime_overrides: Default::default(),
run_in_background: false,
surface_completion: true,
fork_context: false,
result_tx: tx,
};
(req, rx)
}
#[derive(Default)]
pub(crate) struct DummyLspDispatch;
#[async_trait::async_trait]
impl kigi_tools::implementations::lsp::LspBackend for DummyLspDispatch {
fn ensure_started_background(&self) {}
async fn ensure_ready(&self) -> Result<(), String> {
Ok(())
}
fn is_ready(&self) -> bool {
true
}
async fn dispatch(
&self,
_input: &kigi_tools::implementations::lsp::LspToolInput,
) -> kigi_tools::implementations::lsp::LspToolResult {
kigi_tools::implementations::lsp::LspToolResult {
text: String::new(),
is_error: false,
}
}
async fn drain_diagnostics(
&self,
_timeout: std::time::Duration,
) -> Option<kigi_tools::implementations::lsp::DiagnosticsSummary> {
None
}
async fn notify_file_changed(&self, _path: &std::path::Path, _content: &str) {}
async fn read_diagnostics(
&self,
_paths: &[std::path::PathBuf],
) -> Vec<kigi_tools::implementations::lsp::FileDiagnosticEntry> {
vec![]
}
}
@@ -0,0 +1,30 @@
pub(crate) mod lsp_runtime;
pub(crate) const TEST_MODEL: &str = "test-model";
/// Prepend the hermetic git binary (via `GIT_BIN_PATH`) to `PATH` so that
/// `Command::new("git")` in test helpers resolves to the Bazel-provided
/// static binary instead of relying on system-installed git.
///
/// Safe to call multiple times — only the first call mutates `PATH`.
pub(crate) fn ensure_hermetic_git_on_path() {
use std::path::PathBuf;
use std::sync::Once;
static INIT: Once = Once::new();
INIT.call_once(|| {
if let Ok(git_bin) = std::env::var("GIT_BIN_PATH") {
let p = PathBuf::from(&git_bin);
let p = if p.is_relative() {
std::env::current_dir().unwrap().join(&p)
} else {
p
};
if let Some(dir) = p.parent() {
let cur = std::env::var("PATH").unwrap_or_default();
unsafe {
std::env::set_var("PATH", format!("{}:{}", dir.display(), cur));
}
}
}
});
}