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).
520 lines
17 KiB
Rust
520 lines
17 KiB
Rust
//! Shared post-compaction reminder helpers (host-agnostic).
|
|
//!
|
|
//! Lives at the crate root rather than under a compaction-style submodule
|
|
//! because it is consumed by *both* compaction styles and both harnesses:
|
|
//!
|
|
//! - Grok chat intra FullReplace ([`crate::intra_compaction`]) and inter
|
|
//! (appends after sampling via [`append_reminder_block`])
|
|
//! - grok-build full-replace ([`crate::code_compaction`] assemble's
|
|
//! `system_reminder`)
|
|
//!
|
|
//! **What lives here:** pure formatting of the three **common** active-agent
|
|
//! sections — Running Background Tasks, TODO List, Running Subagents — plus
|
|
//! `<system-reminder>` wrapping and summary append.
|
|
//!
|
|
//! **What stays in the product host:** snapshotting, tool-name resolution, and harness-only
|
|
//! sections (files, AGENTS.md, skills, MCP, memory). Callers pass **borrowed
|
|
//! views** (`&str` over live state) so long fields (commands, todo content,
|
|
//! descriptions, ids) are not cloned just to format.
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Borrowed views over harness live state (no long-string clones)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Model-facing poll/cancel tool names from the current toolset.
|
|
/// Never hard-code: a client manifest can rename them.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct SubagentToolNames<'a> {
|
|
pub poll: &'a str,
|
|
pub cancel: &'a str,
|
|
}
|
|
|
|
/// Status of a todo item in the post-compaction reminder.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum TodoStatus {
|
|
Pending,
|
|
InProgress,
|
|
Completed,
|
|
Cancelled,
|
|
}
|
|
|
|
impl TodoStatus {
|
|
pub fn is_actionable(self) -> bool {
|
|
matches!(self, Self::Pending | Self::InProgress)
|
|
}
|
|
|
|
pub fn tag(self) -> &'static str {
|
|
match self {
|
|
Self::Pending => "[pending]",
|
|
Self::InProgress => "[in_progress]",
|
|
Self::Completed => "[completed]",
|
|
Self::Cancelled => "[cancelled]",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct TodoItem<'a> {
|
|
pub id: &'a str,
|
|
pub content: &'a str,
|
|
pub status: TodoStatus,
|
|
}
|
|
|
|
/// Still-running background task. `task_id` is rendered verbatim.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct BackgroundTask<'a> {
|
|
pub task_id: &'a str,
|
|
pub command: &'a str,
|
|
/// Parenthetical status (typically `"running"`).
|
|
pub status: &'a str,
|
|
pub tool_name: Option<&'a str>,
|
|
}
|
|
|
|
/// Still-running sub-agent. `subagent_id` is rendered verbatim.
|
|
///
|
|
/// `subagent_type` / `description` are optional so chat (no type, optional
|
|
/// desc) and build (both present) share one line format.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct RunningSubagent<'a> {
|
|
pub subagent_id: &'a str,
|
|
pub subagent_type: Option<&'a str>,
|
|
pub description: Option<&'a str>,
|
|
pub elapsed_secs: u64,
|
|
}
|
|
|
|
/// Borrowed active-agent state for reminder rendering.
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub struct ActiveAgentReminderState<'a> {
|
|
pub running_commands: &'a [BackgroundTask<'a>],
|
|
pub todos: &'a [TodoItem<'a>],
|
|
pub running_subagents: &'a [RunningSubagent<'a>],
|
|
}
|
|
|
|
impl ActiveAgentReminderState<'_> {
|
|
pub fn is_empty(&self) -> bool {
|
|
self.running_commands.is_empty()
|
|
&& self.running_subagents.is_empty()
|
|
&& !self.has_actionable_todos()
|
|
}
|
|
|
|
pub fn has_actionable_todos(&self) -> bool {
|
|
self.todos.iter().any(|t| t.status.is_actionable())
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Section formatters
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// `## Running Background Tasks`, or `None` when empty.
|
|
pub fn section_background_tasks(tasks: &[BackgroundTask<'_>]) -> Option<String> {
|
|
if tasks.is_empty() {
|
|
return None;
|
|
}
|
|
let lines = tasks
|
|
.iter()
|
|
.map(|t| match t.tool_name {
|
|
Some(tool) => format!(
|
|
"- \"{}\": `{}` ({}, {})",
|
|
t.task_id, t.command, t.status, tool
|
|
),
|
|
None => format!("- \"{}\": `{}` ({})", t.task_id, t.command, t.status),
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
Some(format!(
|
|
"## Running Background Tasks\n\
|
|
These tasks are still running:\n{lines}"
|
|
))
|
|
}
|
|
|
|
/// `## TODO List` for actionable items, or `None` when none. Completed/
|
|
/// cancelled collapse to a count trailer.
|
|
pub fn section_todo_list(todos: &[TodoItem<'_>]) -> Option<String> {
|
|
let active: Vec<_> = todos
|
|
.iter()
|
|
.filter(|t| t.status.is_actionable())
|
|
.map(|t| format!("- {} {}: {}", t.status.tag(), t.id, t.content))
|
|
.collect();
|
|
if active.is_empty() {
|
|
return None;
|
|
}
|
|
let completed = todos
|
|
.iter()
|
|
.filter(|t| t.status == TodoStatus::Completed)
|
|
.count();
|
|
let cancelled = todos
|
|
.iter()
|
|
.filter(|t| t.status == TodoStatus::Cancelled)
|
|
.count();
|
|
let trailer = match (completed, cancelled) {
|
|
(0, 0) => String::new(),
|
|
(c, 0) => format!("\n({c} completed)"),
|
|
(0, k) => format!("\n({k} cancelled)"),
|
|
(c, k) => format!("\n({c} completed, {k} cancelled)"),
|
|
};
|
|
Some(format!(
|
|
"## TODO List\n\
|
|
This is your task list from before the conversation was compacted — it is still \
|
|
active. Keep working through the items below and update their status as you make \
|
|
progress:\n{}{trailer}",
|
|
active.join("\n"),
|
|
))
|
|
}
|
|
|
|
/// `## Running Subagents`, or `None` when empty. Omit entirely when tool
|
|
/// names cannot be resolved rather than point at wrong names.
|
|
pub fn section_running_subagents(
|
|
subagents: &[RunningSubagent<'_>],
|
|
tools: &SubagentToolNames<'_>,
|
|
) -> Option<String> {
|
|
if subagents.is_empty() {
|
|
return None;
|
|
}
|
|
let lines = subagents
|
|
.iter()
|
|
.map(format_subagent_line)
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
Some(format!(
|
|
"## Running Subagents\n\
|
|
These subagents were launched before this compaction and are still running. \
|
|
Use `{}` with the subagent_id to check their status or retrieve results. \
|
|
Use `{}` with the subagent_id to cancel a subagent.\n{lines}",
|
|
tools.poll, tools.cancel
|
|
))
|
|
}
|
|
|
|
fn format_subagent_line(s: &RunningSubagent<'_>) -> String {
|
|
let mut head = format!("subagent_id: `{}`", s.subagent_id);
|
|
if let Some(ty) = s.subagent_type {
|
|
head.push_str(", type: `");
|
|
head.push_str(ty);
|
|
head.push('`');
|
|
}
|
|
if let Some(desc) = s.description {
|
|
head.push_str(", task: \"");
|
|
head.push_str(desc);
|
|
head.push('"');
|
|
}
|
|
format!("- {head} (running for {}s)", s.elapsed_secs)
|
|
}
|
|
|
|
/// Common sections in order: Background Tasks → TODO → Subagents.
|
|
/// Empty kinds omitted; subagents also omitted when `subagent_tools` is `None`.
|
|
pub fn format_active_agent_sections(
|
|
state: &ActiveAgentReminderState<'_>,
|
|
subagent_tools: Option<&SubagentToolNames<'_>>,
|
|
) -> Vec<String> {
|
|
let mut sections = Vec::with_capacity(3);
|
|
if let Some(s) = section_background_tasks(state.running_commands) {
|
|
sections.push(s);
|
|
}
|
|
if let Some(s) = section_todo_list(state.todos) {
|
|
sections.push(s);
|
|
}
|
|
if let Some(tools) = subagent_tools
|
|
&& let Some(s) = section_running_subagents(state.running_subagents, tools)
|
|
{
|
|
sections.push(s);
|
|
}
|
|
sections
|
|
}
|
|
|
|
/// Wrap non-empty sections in `<system-reminder>…</system-reminder>`.
|
|
pub fn wrap_system_reminder(sections: impl IntoIterator<Item = impl AsRef<str>>) -> Option<String> {
|
|
let mut body = String::new();
|
|
for s in sections {
|
|
let s = s.as_ref();
|
|
if s.trim().is_empty() {
|
|
continue;
|
|
}
|
|
if !body.is_empty() {
|
|
body.push_str("\n\n");
|
|
}
|
|
body.push_str(s);
|
|
}
|
|
if body.is_empty() {
|
|
None
|
|
} else {
|
|
Some(format!("<system-reminder>\n{body}\n</system-reminder>"))
|
|
}
|
|
}
|
|
|
|
/// Full active-agent-state `<system-reminder>`, or `None` when nothing to preserve.
|
|
pub fn format_active_agent_reminder(
|
|
state: &ActiveAgentReminderState<'_>,
|
|
subagent_tools: Option<&SubagentToolNames<'_>>,
|
|
) -> Option<String> {
|
|
wrap_system_reminder(format_active_agent_sections(state, subagent_tools))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Summary injection
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Append a trailing block to a compaction summary, separated by a blank line.
|
|
/// Returns `summary` unchanged when `reminder` is `None` or blank.
|
|
pub fn append_reminder_block(summary: String, reminder: Option<&str>) -> String {
|
|
match reminder {
|
|
Some(reminder) if !reminder.trim().is_empty() => format!("{summary}\n\n{reminder}"),
|
|
_ => summary,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn tools_native() -> SubagentToolNames<'static> {
|
|
SubagentToolNames {
|
|
poll: "get_task_output",
|
|
cancel: "kill_task",
|
|
}
|
|
}
|
|
|
|
fn tools_renamed() -> SubagentToolNames<'static> {
|
|
SubagentToolNames {
|
|
poll: "get_command_or_subagent_output",
|
|
cancel: "kill_command_or_subagent",
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn empty_state_is_none() {
|
|
assert!(
|
|
format_active_agent_reminder(
|
|
&ActiveAgentReminderState::default(),
|
|
Some(&tools_native())
|
|
)
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn missing_tool_names_omits_subagent_section_only() {
|
|
let agents = [RunningSubagent {
|
|
subagent_id: "sa-1",
|
|
subagent_type: None,
|
|
description: Some("x"),
|
|
elapsed_secs: 1,
|
|
}];
|
|
let state = ActiveAgentReminderState {
|
|
running_subagents: &agents,
|
|
..Default::default()
|
|
};
|
|
assert!(format_active_agent_reminder(&state, None).is_none());
|
|
|
|
let cmds = [BackgroundTask {
|
|
task_id: "bg-1",
|
|
command: "npm run dev",
|
|
status: "running",
|
|
tool_name: Some("run_terminal_command"),
|
|
}];
|
|
let state = ActiveAgentReminderState {
|
|
running_commands: &cmds,
|
|
..Default::default()
|
|
};
|
|
let out = format_active_agent_reminder(&state, None).expect("reminder");
|
|
assert!(out.contains("## Running Background Tasks"));
|
|
assert!(!out.contains("## Running Subagents"));
|
|
}
|
|
|
|
#[test]
|
|
fn renders_chat_style_subagent_ids_verbatim() {
|
|
let agents = [
|
|
RunningSubagent {
|
|
subagent_id: "019ea7f0-cb66-7aa2-9a09-488a3a795795",
|
|
subagent_type: None,
|
|
description: Some("deploy staging"),
|
|
elapsed_secs: 42,
|
|
},
|
|
RunningSubagent {
|
|
subagent_id: "sa-2",
|
|
subagent_type: None,
|
|
description: None,
|
|
elapsed_secs: 5,
|
|
},
|
|
];
|
|
let state = ActiveAgentReminderState {
|
|
running_subagents: &agents,
|
|
..Default::default()
|
|
};
|
|
let out = format_active_agent_reminder(&state, Some(&tools_native())).expect("reminder");
|
|
assert!(out.starts_with("<system-reminder>"));
|
|
assert!(out.ends_with("</system-reminder>"));
|
|
assert!(out.contains("subagent_id: `019ea7f0-cb66-7aa2-9a09-488a3a795795`"));
|
|
assert!(out.contains("task: \"deploy staging\" (running for 42s)"));
|
|
assert!(out.contains("subagent_id: `sa-2` (running for 5s)"));
|
|
assert!(!out.contains("task-019ea7f0"));
|
|
assert!(!out.contains("type:"));
|
|
}
|
|
|
|
#[test]
|
|
fn renders_build_style_subagent_with_type() {
|
|
let agents = [RunningSubagent {
|
|
subagent_id: "sub-1",
|
|
subagent_type: Some("explore"),
|
|
description: Some("find files"),
|
|
elapsed_secs: 5,
|
|
}];
|
|
let state = ActiveAgentReminderState {
|
|
running_subagents: &agents,
|
|
..Default::default()
|
|
};
|
|
let out = format_active_agent_reminder(&state, Some(&tools_renamed())).expect("reminder");
|
|
assert!(out.contains(
|
|
"- subagent_id: `sub-1`, type: `explore`, task: \"find files\" (running for 5s)"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn uses_renamed_tool_names_verbatim() {
|
|
let agents = [RunningSubagent {
|
|
subagent_id: "sa-1",
|
|
subagent_type: None,
|
|
description: Some("x"),
|
|
elapsed_secs: 1,
|
|
}];
|
|
let state = ActiveAgentReminderState {
|
|
running_subagents: &agents,
|
|
..Default::default()
|
|
};
|
|
let out = format_active_agent_reminder(&state, Some(&tools_renamed())).expect("reminder");
|
|
assert!(out.contains("get_command_or_subagent_output"));
|
|
assert!(!out.contains("get_task_output"));
|
|
}
|
|
|
|
#[test]
|
|
fn renders_background_tasks() {
|
|
let cmds = [
|
|
BackgroundTask {
|
|
task_id: "019f1723-a9f0-76f2-98ae-56af965922f6",
|
|
command: "npm run dev",
|
|
status: "running",
|
|
tool_name: Some("run_terminal_command"),
|
|
},
|
|
BackgroundTask {
|
|
task_id: "bg-2",
|
|
command: "cargo watch -x test",
|
|
status: "running",
|
|
tool_name: None,
|
|
},
|
|
];
|
|
let state = ActiveAgentReminderState {
|
|
running_commands: &cmds,
|
|
..Default::default()
|
|
};
|
|
let out = format_active_agent_reminder(&state, None).expect("reminder");
|
|
assert!(out.contains(
|
|
"- \"019f1723-a9f0-76f2-98ae-56af965922f6\": `npm run dev` (running, run_terminal_command)"
|
|
));
|
|
assert!(out.contains("- \"bg-2\": `cargo watch -x test` (running)"));
|
|
}
|
|
|
|
#[test]
|
|
fn renders_todo_list_without_tool_names() {
|
|
let todos = [
|
|
TodoItem {
|
|
id: "1",
|
|
content: "scaffold the app",
|
|
status: TodoStatus::Completed,
|
|
},
|
|
TodoItem {
|
|
id: "2",
|
|
content: "wire the API",
|
|
status: TodoStatus::InProgress,
|
|
},
|
|
TodoItem {
|
|
id: "3",
|
|
content: "write tests",
|
|
status: TodoStatus::Pending,
|
|
},
|
|
];
|
|
let state = ActiveAgentReminderState {
|
|
todos: &todos,
|
|
..Default::default()
|
|
};
|
|
let out = format_active_agent_reminder(&state, None).expect("reminder");
|
|
assert!(out.contains("- [in_progress] 2: wire the API"));
|
|
assert!(out.contains("- [pending] 3: write tests"));
|
|
assert!(out.contains("(1 completed)"));
|
|
assert!(!out.contains("scaffold the app"));
|
|
}
|
|
|
|
#[test]
|
|
fn only_completed_todos_is_none() {
|
|
let todos = [TodoItem {
|
|
id: "1",
|
|
content: "done",
|
|
status: TodoStatus::Completed,
|
|
}];
|
|
let state = ActiveAgentReminderState {
|
|
todos: &todos,
|
|
..Default::default()
|
|
};
|
|
assert!(format_active_agent_reminder(&state, None).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn section_order_background_todo_subagent() {
|
|
let cmds = [BackgroundTask {
|
|
task_id: "t1",
|
|
command: "npm run dev",
|
|
status: "running",
|
|
tool_name: None,
|
|
}];
|
|
let todos = [TodoItem {
|
|
id: "2",
|
|
content: "wire the API",
|
|
status: TodoStatus::InProgress,
|
|
}];
|
|
let agents = [RunningSubagent {
|
|
subagent_id: "sa-1",
|
|
subagent_type: None,
|
|
description: Some("deploy staging"),
|
|
elapsed_secs: 1,
|
|
}];
|
|
let state = ActiveAgentReminderState {
|
|
running_commands: &cmds,
|
|
todos: &todos,
|
|
running_subagents: &agents,
|
|
};
|
|
let out = format_active_agent_reminder(&state, Some(&tools_native())).expect("reminder");
|
|
let bg = out.find("## Running Background Tasks").expect("bg");
|
|
let todo = out.find("## TODO List").expect("todo");
|
|
let sub = out.find("## Running Subagents").expect("sub");
|
|
assert!(bg < todo && todo < sub, "order wrong:\n{out}");
|
|
}
|
|
|
|
#[test]
|
|
fn wrap_system_reminder_joins_and_skips_blank() {
|
|
let out = wrap_system_reminder(["## A\nx", "", " ", "## B\ny"]).expect("wrapped");
|
|
assert_eq!(
|
|
out,
|
|
"<system-reminder>\n## A\nx\n\n## B\ny\n</system-reminder>"
|
|
);
|
|
assert!(wrap_system_reminder(std::iter::empty::<&str>()).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn appends_after_blank_line() {
|
|
assert_eq!(
|
|
append_reminder_block("SUMMARY".to_string(), Some("REMINDER")),
|
|
"SUMMARY\n\nREMINDER"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn append_noop_when_none_or_blank() {
|
|
assert_eq!(
|
|
append_reminder_block("SUMMARY".to_string(), None),
|
|
"SUMMARY"
|
|
);
|
|
assert_eq!(
|
|
append_reminder_block("SUMMARY".to_string(), Some(" \n\t ")),
|
|
"SUMMARY"
|
|
);
|
|
}
|
|
}
|