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:
commit
d6c20fc13f
2612 files changed
+1353757
No files matched your search
@@ -0,0 +1,322 @@
|
||||
//! SubagentBlock — scrollback entries for subagent lifecycle.
|
||||
//!
|
||||
//! Similar to BgTaskBlock: always collapsed, animated bullet while running,
|
||||
//! colored bullet when done. Enter / Ctrl-F opens the subagent view.
|
||||
//!
|
||||
//! Two modes:
|
||||
//! - **Blocking** (sync): Single `Started` block. Blinks while running,
|
||||
//! turns green/red when done. Text: `Subagent "description"`
|
||||
//! - **Background** (async): `Started` block stays forever (turns gray).
|
||||
//! A separate `Completed`/`Failed` block is added when done.
|
||||
//! Started text: `Subagent started: "description"`
|
||||
//! Completed text: `Subagent completed in 43s: "description"`
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use ratatui::style::Modifier;
|
||||
use ratatui::text::{Line, Span};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::app::subagent::format_subagent_meta;
|
||||
use crate::render::color::blend_color;
|
||||
use crate::render::line_utils::truncate_str;
|
||||
use crate::scrollback::block::BlockContent;
|
||||
use crate::scrollback::types::{AccentStyle, BlockContext, BlockOutput, DisplayMode};
|
||||
use crate::theme::Theme;
|
||||
use crate::util::format_duration;
|
||||
|
||||
/// What kind of subagent lifecycle event this block represents.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SubagentBlockKind {
|
||||
/// Subagent is running (or was running — `finish_running` stops animation).
|
||||
Started,
|
||||
/// Subagent completed successfully.
|
||||
Completed { elapsed: Duration },
|
||||
/// Subagent failed.
|
||||
Failed {
|
||||
elapsed: Duration,
|
||||
error: Option<String>,
|
||||
},
|
||||
/// Subagent was cancelled.
|
||||
Cancelled { elapsed: Duration },
|
||||
}
|
||||
|
||||
/// Subagent scrollback block.
|
||||
///
|
||||
/// Always collapsed, not foldable, groupable, selectable.
|
||||
/// Enter / Ctrl-F opens the subagent view.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubagentBlock {
|
||||
/// Human-readable description of the task.
|
||||
pub description: String,
|
||||
/// Child session ID (for opening the subagent view).
|
||||
pub child_session_id: String,
|
||||
/// Subagent type (e.g. "general-purpose", "explore").
|
||||
pub subagent_type: String,
|
||||
/// Named persona applied to this subagent, if any.
|
||||
pub persona: Option<String>,
|
||||
/// Role that supplied defaults for this subagent, if any.
|
||||
pub role: Option<String>,
|
||||
/// Effective model ID used by the subagent, if available.
|
||||
pub model: Option<String>,
|
||||
/// Whether the subagent was launched in background mode.
|
||||
pub is_background: bool,
|
||||
/// Lifecycle kind.
|
||||
pub kind: SubagentBlockKind,
|
||||
/// Live activity label from the child session's turn tracker.
|
||||
///
|
||||
/// Updated on each `SubagentProgress` tick while the subagent is running.
|
||||
/// Shown inline in the collapsed scrollback line (e.g. "Thinking",
|
||||
/// "Running: cargo build") so the user sees interactive progress without
|
||||
/// opening the subagent view.
|
||||
pub activity_label: Option<String>,
|
||||
}
|
||||
|
||||
impl SubagentBlock {
|
||||
/// Create a "Subagent started" block (for both sync and async).
|
||||
pub fn started(
|
||||
description: impl Into<String>,
|
||||
child_session_id: impl Into<String>,
|
||||
subagent_type: impl Into<String>,
|
||||
persona: Option<String>,
|
||||
role: Option<String>,
|
||||
model: Option<String>,
|
||||
is_background: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
description: description.into(),
|
||||
child_session_id: child_session_id.into(),
|
||||
subagent_type: subagent_type.into(),
|
||||
persona,
|
||||
role,
|
||||
model,
|
||||
is_background,
|
||||
kind: SubagentBlockKind::Started,
|
||||
activity_label: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a "Subagent completed" block (background mode only).
|
||||
pub fn completed(
|
||||
description: impl Into<String>,
|
||||
child_session_id: impl Into<String>,
|
||||
elapsed: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
description: description.into(),
|
||||
child_session_id: child_session_id.into(),
|
||||
subagent_type: String::new(),
|
||||
persona: None,
|
||||
role: None,
|
||||
model: None,
|
||||
is_background: true,
|
||||
kind: SubagentBlockKind::Completed { elapsed },
|
||||
activity_label: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a "Subagent failed" block (background mode only).
|
||||
pub fn failed(
|
||||
description: impl Into<String>,
|
||||
child_session_id: impl Into<String>,
|
||||
elapsed: Duration,
|
||||
error: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
description: description.into(),
|
||||
child_session_id: child_session_id.into(),
|
||||
subagent_type: String::new(),
|
||||
persona: None,
|
||||
role: None,
|
||||
model: None,
|
||||
is_background: true,
|
||||
kind: SubagentBlockKind::Failed { elapsed, error },
|
||||
activity_label: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a "Subagent cancelled" block (background mode only).
|
||||
pub fn cancelled(
|
||||
description: impl Into<String>,
|
||||
child_session_id: impl Into<String>,
|
||||
elapsed: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
description: description.into(),
|
||||
child_session_id: child_session_id.into(),
|
||||
subagent_type: String::new(),
|
||||
persona: None,
|
||||
role: None,
|
||||
model: None,
|
||||
is_background: true,
|
||||
kind: SubagentBlockKind::Cancelled { elapsed },
|
||||
activity_label: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
matches!(self.kind, SubagentBlockKind::Started)
|
||||
}
|
||||
}
|
||||
|
||||
/// Truncate description and wrap in quotes for display.
|
||||
fn quoted_desc(desc: &str, max_width: usize) -> String {
|
||||
// Reserve 2 chars for quotes
|
||||
if max_width <= 2 {
|
||||
return "\u{201C}\u{2026}\u{201D}".to_string(); // "…"
|
||||
}
|
||||
let inner = truncate_str(desc, max_width - 2);
|
||||
format!("\u{201C}{inner}\u{201D}")
|
||||
}
|
||||
|
||||
impl BlockContent for SubagentBlock {
|
||||
fn output(&self, ctx: &BlockContext) -> BlockOutput {
|
||||
let theme = Theme::current();
|
||||
// When selected, lift only the bold "Subagent" label to
|
||||
// `text_primary` so it reads as undimmed (mirrors `read.rs` /
|
||||
// `search.rs`, which bump only the label and leave the rest at
|
||||
// `muted`). The detail text (verb + description + meta) stays
|
||||
// muted in every state.
|
||||
let bold = if ctx.is_selected {
|
||||
theme.primary().add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
theme.muted().add_modifier(Modifier::BOLD)
|
||||
};
|
||||
let muted = theme.muted();
|
||||
let w = ctx.width as usize;
|
||||
|
||||
let line = match (&self.kind, self.is_background) {
|
||||
(SubagentBlockKind::Started, bg) => {
|
||||
let verb = if bg { "started: " } else { "running: " };
|
||||
let activity_suffix: String = self
|
||||
.activity_label
|
||||
.as_deref()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|a| format!(" \u{2014} {a}"))
|
||||
.unwrap_or_default();
|
||||
let meta = format_subagent_meta(
|
||||
self.persona.as_deref(),
|
||||
self.role.as_deref(),
|
||||
self.model.as_deref(),
|
||||
);
|
||||
// "Subagent running: " / "Subagent started: " = 18 chars
|
||||
let overhead = 18 + meta.width() + activity_suffix.width();
|
||||
let desc = quoted_desc(&self.description, w.saturating_sub(overhead));
|
||||
let mut spans = vec![
|
||||
Span::styled("Subagent ", bold),
|
||||
Span::styled(verb, muted),
|
||||
Span::styled(desc, muted),
|
||||
];
|
||||
if !activity_suffix.is_empty() {
|
||||
spans.push(Span::styled(activity_suffix, muted));
|
||||
}
|
||||
spans.push(Span::styled(meta, muted));
|
||||
Line::from(spans)
|
||||
}
|
||||
// Completed: Subagent completed in Xs: "description"
|
||||
(SubagentBlockKind::Completed { elapsed }, _) => {
|
||||
let time_str = format_duration(*elapsed);
|
||||
// "Subagent completed in Xs: " = 26 + time_str.len()
|
||||
let prefix_len = 26 + time_str.len();
|
||||
let desc = quoted_desc(&self.description, w.saturating_sub(prefix_len));
|
||||
Line::from(vec![
|
||||
Span::styled("Subagent ", bold),
|
||||
Span::styled(format!("completed in {time_str}: "), muted),
|
||||
Span::styled(desc, muted),
|
||||
])
|
||||
}
|
||||
// Failed: Subagent failed in Xs: "description"
|
||||
(SubagentBlockKind::Failed { elapsed, error }, _) => {
|
||||
let time_str = format_duration(*elapsed);
|
||||
let detail = error
|
||||
.as_deref()
|
||||
.map(|e| format!(" ({e})"))
|
||||
.unwrap_or_default();
|
||||
let prefix_len = 21 + time_str.len() + detail.len();
|
||||
let desc = quoted_desc(&self.description, w.saturating_sub(prefix_len));
|
||||
Line::from(vec![
|
||||
Span::styled("Subagent ", bold),
|
||||
Span::styled(format!("failed in {time_str}{detail}: "), muted),
|
||||
Span::styled(desc, muted),
|
||||
])
|
||||
}
|
||||
// Cancelled: Subagent cancelled in Xs: "description"
|
||||
(SubagentBlockKind::Cancelled { elapsed }, _) => {
|
||||
let time_str = format_duration(*elapsed);
|
||||
// "Subagent cancelled in Xs: " = 26 + time_str.len()
|
||||
let prefix_len = 26 + time_str.len();
|
||||
let desc = quoted_desc(&self.description, w.saturating_sub(prefix_len));
|
||||
Line::from(vec![
|
||||
Span::styled("Subagent ", bold),
|
||||
Span::styled(format!("cancelled in {time_str}: "), muted),
|
||||
Span::styled(desc, muted),
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
BlockOutput {
|
||||
lines: vec![line.into()],
|
||||
}
|
||||
}
|
||||
|
||||
fn accent(&self, ctx: &BlockContext) -> Option<AccentStyle> {
|
||||
let theme = Theme::current();
|
||||
match &self.kind {
|
||||
SubagentBlockKind::Started if ctx.is_running => {
|
||||
Some(AccentStyle::static_color(theme.accent_running))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn bullet(&self, ctx: &BlockContext) -> Option<AccentStyle> {
|
||||
let theme = Theme::current();
|
||||
match &self.kind {
|
||||
SubagentBlockKind::Started => {
|
||||
if ctx.is_running {
|
||||
let dim = ctx.appearance.scrollback.display.dim_accent;
|
||||
let dimmed = blend_color(theme.bg_base, theme.accent_running, dim)
|
||||
.unwrap_or(theme.accent_running);
|
||||
Some(AccentStyle::animated(dimmed))
|
||||
} else {
|
||||
// Finished — gray bullet (same as bg task "started" after completion)
|
||||
None
|
||||
}
|
||||
}
|
||||
SubagentBlockKind::Completed { .. } => {
|
||||
Some(AccentStyle::static_color(theme.accent_success))
|
||||
}
|
||||
SubagentBlockKind::Failed { .. } | SubagentBlockKind::Cancelled { .. } => {
|
||||
Some(AccentStyle::static_color(theme.accent_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_vpad(&self, _ctx: &BlockContext) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn has_raw_mode(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_foldable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn default_display_mode(&self) -> DisplayMode {
|
||||
DisplayMode::Collapsed
|
||||
}
|
||||
|
||||
fn is_selectable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn has_bullet(&self, _ctx: &BlockContext) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_groupable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user