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:
@@ -0,0 +1,214 @@
|
||||
//! Binary crash blob format ("GCRX").
|
||||
//!
|
||||
//! The signal handler writes this format using only `libc::write` (no allocation).
|
||||
//! The startup reader parses it in normal Rust context.
|
||||
|
||||
/// Magic bytes identifying a valid crash file.
|
||||
pub const MAGIC: [u8; 4] = *b"GCRX";
|
||||
|
||||
/// Current format version.
|
||||
pub const VERSION: u8 = 1;
|
||||
|
||||
/// Maximum backtrace frames captured in the signal handler.
|
||||
pub const MAX_FRAMES: usize = 64;
|
||||
|
||||
/// Length of the null-padded version string field.
|
||||
pub const VERSION_STRING_LEN: usize = 32;
|
||||
|
||||
/// Fixed header size (before the variable-length frames array).
|
||||
///
|
||||
/// Layout:
|
||||
/// - magic: 4 bytes
|
||||
/// - version: 1 byte
|
||||
/// - signal: 1 byte
|
||||
/// - si_code: 4 bytes (i32, little-endian)
|
||||
/// - si_addr: 8 bytes (u64, little-endian)
|
||||
/// - pid: 4 bytes (u32, little-endian)
|
||||
/// - timestamp: 8 bytes (u64, little-endian)
|
||||
/// - n_frames: 2 bytes (u16, little-endian)
|
||||
/// - app_version: 32 bytes (null-padded UTF-8)
|
||||
pub const HEADER_SIZE: usize = 4 + 1 + 1 + 4 + 8 + 4 + 8 + 2 + VERSION_STRING_LEN;
|
||||
|
||||
/// Total maximum file size: header + 64 frames * 8 bytes each.
|
||||
pub const MAX_FILE_SIZE: usize = HEADER_SIZE + MAX_FRAMES * 8;
|
||||
|
||||
/// Parsed crash data from a `last-crash.bin` file.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CrashBlob {
|
||||
pub signal: u8,
|
||||
pub si_code: i32,
|
||||
pub si_addr: u64,
|
||||
pub pid: u32,
|
||||
pub timestamp: u64,
|
||||
pub frames: Vec<usize>,
|
||||
pub app_version: String,
|
||||
}
|
||||
|
||||
impl CrashBlob {
|
||||
/// Parse a crash blob from bytes. Returns `None` if the data is invalid.
|
||||
pub fn parse(data: &[u8]) -> Option<Self> {
|
||||
if data.len() < HEADER_SIZE {
|
||||
return None;
|
||||
}
|
||||
if data[0..4] != MAGIC {
|
||||
return None;
|
||||
}
|
||||
if data[4] != VERSION {
|
||||
return None;
|
||||
}
|
||||
|
||||
let signal = data[5];
|
||||
let si_code = i32::from_le_bytes([data[6], data[7], data[8], data[9]]);
|
||||
let si_addr = u64::from_le_bytes([
|
||||
data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17],
|
||||
]);
|
||||
let pid = u32::from_le_bytes([data[18], data[19], data[20], data[21]]);
|
||||
let timestamp = u64::from_le_bytes([
|
||||
data[22], data[23], data[24], data[25], data[26], data[27], data[28], data[29],
|
||||
]);
|
||||
let n_frames = u16::from_le_bytes([data[30], data[31]]) as usize;
|
||||
|
||||
let version_bytes = &data[32..32 + VERSION_STRING_LEN];
|
||||
let app_version = std::str::from_utf8(version_bytes)
|
||||
.unwrap_or("")
|
||||
.trim_end_matches('\0')
|
||||
.to_string();
|
||||
|
||||
if n_frames > MAX_FRAMES {
|
||||
return None;
|
||||
}
|
||||
let frames_start = HEADER_SIZE;
|
||||
let frames_end = frames_start + n_frames * 8;
|
||||
if data.len() < frames_end {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut frames = Vec::with_capacity(n_frames);
|
||||
for i in 0..n_frames {
|
||||
let offset = frames_start + i * 8;
|
||||
let addr = u64::from_le_bytes([
|
||||
data[offset],
|
||||
data[offset + 1],
|
||||
data[offset + 2],
|
||||
data[offset + 3],
|
||||
data[offset + 4],
|
||||
data[offset + 5],
|
||||
data[offset + 6],
|
||||
data[offset + 7],
|
||||
]);
|
||||
frames.push(addr as usize);
|
||||
}
|
||||
|
||||
Some(CrashBlob {
|
||||
signal,
|
||||
si_code,
|
||||
si_addr,
|
||||
pid,
|
||||
timestamp,
|
||||
frames,
|
||||
app_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Helpers for writing fields in the signal handler using raw byte copies.
|
||||
/// These are used by `handler.rs` — all operations are on a pre-allocated
|
||||
/// static buffer, no allocation involved.
|
||||
pub mod writer {
|
||||
use super::{MAGIC, VERSION, VERSION_STRING_LEN};
|
||||
|
||||
/// Write the crash blob header into `buf`, returning the number of bytes written.
|
||||
/// The caller must ensure `buf` is at least `HEADER_SIZE` bytes.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This is called from a signal handler. The buffer must be valid and large enough.
|
||||
pub unsafe fn write_header(
|
||||
buf: &mut [u8],
|
||||
signal: u8,
|
||||
si_code: i32,
|
||||
si_addr: u64,
|
||||
pid: u32,
|
||||
timestamp: u64,
|
||||
n_frames: u16,
|
||||
app_version: &[u8],
|
||||
) -> usize {
|
||||
buf[0..4].copy_from_slice(&MAGIC);
|
||||
buf[4] = VERSION;
|
||||
buf[5] = signal;
|
||||
buf[6..10].copy_from_slice(&si_code.to_le_bytes());
|
||||
buf[10..18].copy_from_slice(&si_addr.to_le_bytes());
|
||||
buf[18..22].copy_from_slice(&pid.to_le_bytes());
|
||||
buf[22..30].copy_from_slice(×tamp.to_le_bytes());
|
||||
buf[30..32].copy_from_slice(&n_frames.to_le_bytes());
|
||||
|
||||
// Null-pad the version string field.
|
||||
let version_field = &mut buf[32..32 + VERSION_STRING_LEN];
|
||||
version_field.fill(0);
|
||||
let copy_len = app_version.len().min(VERSION_STRING_LEN);
|
||||
version_field[..copy_len].copy_from_slice(&app_version[..copy_len]);
|
||||
|
||||
32 + VERSION_STRING_LEN
|
||||
}
|
||||
|
||||
/// Write a single frame pointer into `buf` at the given offset.
|
||||
/// Returns the new offset.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure `buf[offset..offset+8]` is valid.
|
||||
pub unsafe fn write_frame(buf: &mut [u8], offset: usize, addr: usize) -> usize {
|
||||
buf[offset..offset + 8].copy_from_slice(&(addr as u64).to_le_bytes());
|
||||
offset + 8
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn roundtrip_crash_blob() {
|
||||
let mut buf = [0u8; MAX_FILE_SIZE];
|
||||
let version = b"0.1.169-alpha.2";
|
||||
let frames: &[usize] = &[0xdead_beef, 0xcafe_babe, 0x1234_5678];
|
||||
|
||||
unsafe {
|
||||
let mut offset = writer::write_header(
|
||||
&mut buf,
|
||||
10, // SIGBUS on macOS
|
||||
2, // BUS_ADRERR
|
||||
0x7f8a_1234_0000,
|
||||
42,
|
||||
1_712_678_587,
|
||||
frames.len() as u16,
|
||||
version,
|
||||
);
|
||||
for &frame in frames {
|
||||
offset = writer::write_frame(&mut buf, offset, frame);
|
||||
}
|
||||
|
||||
let blob = CrashBlob::parse(&buf[..offset]).expect("parse should succeed");
|
||||
assert_eq!(blob.signal, 10);
|
||||
assert_eq!(blob.si_code, 2);
|
||||
assert_eq!(blob.si_addr, 0x7f8a_1234_0000);
|
||||
assert_eq!(blob.pid, 42);
|
||||
assert_eq!(blob.timestamp, 1_712_678_587);
|
||||
assert_eq!(blob.frames, frames);
|
||||
assert_eq!(blob.app_version, "0.1.169-alpha.2");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_bad_magic() {
|
||||
let mut buf = [0u8; HEADER_SIZE];
|
||||
buf[0..4].copy_from_slice(b"NOPE");
|
||||
assert!(CrashBlob::parse(&buf).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_truncated_data() {
|
||||
assert!(CrashBlob::parse(&[]).is_none());
|
||||
assert!(CrashBlob::parse(&MAGIC).is_none());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user