§9 acceptance: grep-zero sweep — every internal x.ai/grok identifier renamed
The PRD's first acceptance gate now holds: grep -RinE '\bx\.ai\b|grok' crates/ --include='*.rs' → 0 matches (exempt: NOTICE and third-party license archives, README provenance, and the required 'Based on Grok Build Open Source' attribution, now sourced from version_attribution.txt). Wire-visible renames (both sides in this repo, changed in lockstep): - Auth method id 'grok.com' → 'kimi-code' (AuthMethodKind::KimiCode). - Every x.ai/* and _x.ai/* ACP ext method and meta key → kigi/* / _kigi/* (~200 names; grokShell → kigiShell). Session-file replay keeps a read-side alias for the legacy '_x.ai/session/update' method so existing updates.jsonl histories load; writes emit only the new name (both directions test-pinned). - Agent types grok-build* → kigi* with a documented legacy-prefix alias at resolution time so persisted sessions keep resolving. - ToolNamespace/BuiltinAgentName GrokBuild* → Kigi* (wire snake_case kigi/kigi_concise/kigi_hashline; schema regenerated); grok_build implementation dirs renamed to kigi*. - x-grok-* headers → x-kigi-*, __GROK_* sentinels → __KIGI_*, themes grokday/groknight → kigiday/kiginight (old persisted values fall back to the default theme), web_fetch allowlist xAI hosts → kimi.com + moonshot platforms, changelog CDN → this repo, grok-build changelog archives deleted. - BYOK default endpoint removed: [endpoints] api_base_url is now truly optional with NO default — consumers fail fast with the flag name when unset (no silent x.ai egress). Mock harnesses inject it explicitly. - System-prompt identity fixed: 'released by xAI' → 'an unofficial community CLI for Kimi' (template + regenerated encrypted form). Also repaired pre-existing grok-era test debt found by the sweep: the stale trace_classify default-model pin, the grok-pager UA label test, pty-harness stale-binary reuse and non-hermetic moonshot routing (a PTY test could previously reach the real api.moonshot.cn), and the outdated oauth fixture scope key. Gates: §9 grep 0; fmt clean; workspace check/clippy 0/0 (-D warnings); FULL cargo test --workspace: 234 suites, 21,961 passed, 0 failed; deny advisories ok.
This commit is contained in:
@@ -643,7 +643,7 @@ mod tests {
|
||||
// background colour on the label span so a regression that drops
|
||||
// the chip-vs-modal colour alignment gets caught.
|
||||
//
|
||||
// Use the unquantized `groknight()` theme directly so warning and
|
||||
// Use the unquantized `kiginight()` theme directly so warning and
|
||||
// bg_base remain distinguishable in the test env — `Theme::current()`
|
||||
// collapses both to ANSI `Reset` on 16-colour terminals, which
|
||||
// would defeat the assertion.
|
||||
@@ -654,7 +654,7 @@ mod tests {
|
||||
2,
|
||||
0,
|
||||
);
|
||||
let t = Theme::groknight();
|
||||
let t = Theme::kiginight();
|
||||
let line = goal_status_line(&g, &t, false, 0, None, 0);
|
||||
// The label span is the one whose content starts with "Goal:".
|
||||
let label_span = line
|
||||
@@ -677,7 +677,7 @@ mod tests {
|
||||
2,
|
||||
0,
|
||||
);
|
||||
let t = Theme::groknight();
|
||||
let t = Theme::kiginight();
|
||||
let line = goal_status_line(&g, &t, false, 0, None, 0);
|
||||
let label_span = line
|
||||
.spans
|
||||
@@ -811,7 +811,7 @@ mod tests {
|
||||
#[test]
|
||||
fn mcp_status_line_uses_dim_directory_color() {
|
||||
// The chip must render in `theme.gray_dim` to match the directory path.
|
||||
let t = Theme::groknight();
|
||||
let t = Theme::kiginight();
|
||||
let progress = McpInitProgress {
|
||||
total: 2,
|
||||
connected: 0,
|
||||
|
||||
@@ -197,7 +197,7 @@ pub struct AgentsModalState {
|
||||
/// Snapshot of bundle catalog used to merge persona lists.
|
||||
bundle: BundleState,
|
||||
/// Resolved startup agent name (same chain as shell: `[agent]`, `KIGI_AGENT`,
|
||||
/// model `agentType`, then `grok-build`).
|
||||
/// model `agentType`, then `kigi`).
|
||||
pub default_agent: String,
|
||||
/// Agent running in the current session (`session/info` `agentName`).
|
||||
pub active_agent: Option<String>,
|
||||
@@ -211,12 +211,12 @@ pub struct AgentsModalState {
|
||||
pub persona_expanded: std::collections::HashSet<usize>,
|
||||
}
|
||||
/// Built-in agent names that should be shown to the user.
|
||||
/// Skips internal variants (GrokBuildConcise, GrokBuildPlan,
|
||||
/// GrokBuildPlanNoSubagents, GrokBuildAskUser, Codex, Opencode,
|
||||
/// CursorExtended, GrokBuildOrchestrator).
|
||||
/// Skips internal variants (KigiConcise, KigiPlan,
|
||||
/// KigiPlanNoSubagents, KigiAskUser, Codex, Opencode,
|
||||
/// CursorExtended, KigiOrchestrator).
|
||||
fn user_visible_builtins() -> &'static [BuiltinAgentName] {
|
||||
&[
|
||||
BuiltinAgentName::GrokBuild,
|
||||
BuiltinAgentName::Kigi,
|
||||
BuiltinAgentName::GeneralPurpose,
|
||||
BuiltinAgentName::Explore,
|
||||
BuiltinAgentName::Plan,
|
||||
|
||||
@@ -1476,11 +1476,11 @@ mod tests {
|
||||
DashboardRowId::TopLevel(AgentId(0)),
|
||||
fields("Response", &["hello"]),
|
||||
);
|
||||
panel.model_name = Some("Grok 4 Fast".to_string());
|
||||
panel.model_name = Some("Kigi 4 Fast".to_string());
|
||||
panel.auto_approve = true;
|
||||
let bottom = badge_row(&panel, 6);
|
||||
assert!(
|
||||
bottom.contains("Grok 4 Fast"),
|
||||
bottom.contains("Kigi 4 Fast"),
|
||||
"model on bottom border: {bottom:?}"
|
||||
);
|
||||
assert!(
|
||||
@@ -1497,10 +1497,10 @@ mod tests {
|
||||
];
|
||||
q.request_id = Some(1);
|
||||
let mut qpanel = PeekPanelState::new(DashboardRowId::TopLevel(AgentId(0)), q);
|
||||
qpanel.model_name = Some("Grok 4 Fast".to_string());
|
||||
qpanel.model_name = Some("Kigi 4 Fast".to_string());
|
||||
let qbottom = badge_row(&qpanel, 8);
|
||||
assert!(
|
||||
qbottom.contains("Grok 4 Fast"),
|
||||
qbottom.contains("Kigi 4 Fast"),
|
||||
"model shows in question/approval mode: {qbottom:?}",
|
||||
);
|
||||
|
||||
@@ -1509,7 +1509,7 @@ mod tests {
|
||||
DashboardRowId::TopLevel(AgentId(0)),
|
||||
fields("Response", &["hi"]),
|
||||
);
|
||||
plain.model_name = Some("Grok 4 Fast".to_string());
|
||||
plain.model_name = Some("Kigi 4 Fast".to_string());
|
||||
plain.auto_approve = false;
|
||||
let plain_bottom = badge_row(&plain, 6);
|
||||
assert!(
|
||||
@@ -1523,7 +1523,7 @@ mod tests {
|
||||
DashboardRowId::TopLevel(AgentId(0)),
|
||||
fields("Response", &["hi"]),
|
||||
);
|
||||
planp.model_name = Some("Grok 4 Fast".to_string());
|
||||
planp.model_name = Some("Kigi 4 Fast".to_string());
|
||||
planp.plan_mode = true;
|
||||
let plan_bottom = badge_row(&planp, 6);
|
||||
assert!(
|
||||
|
||||
@@ -2494,7 +2494,7 @@ fn render_empty_state(buf: &mut Buffer, area: Rect, theme: &Theme, loading: bool
|
||||
/// chrome reflects what Enter will do: dispatch a new session vs.
|
||||
/// enqueue / send a prompt to the currently-selected agent.
|
||||
/// Paint a short right-aligned feedback badge onto the dispatch box's
|
||||
/// **top border** (e.g. `✗ Session no longer exists`, `✓ Theme: Grok
|
||||
/// **top border** (e.g. `✗ Session no longer exists`, `✓ Theme: Kigi
|
||||
/// Day`), in a neutral accent colour. The message is painted VERBATIM:
|
||||
/// it already carries its own status glyph — errors are built via
|
||||
/// [`DashboardState::set_error_toast`] (`✗`), while successes / info
|
||||
@@ -4661,7 +4661,7 @@ mod tests {
|
||||
let mut state = DashboardState::new();
|
||||
// A distinct absolute path outside $HOME (rendered verbatim) that
|
||||
// differs from the process cwd. No git cache entry → no branch span.
|
||||
state.cwd = std::path::PathBuf::from("/grok-staged-cwd-marker");
|
||||
state.cwd = std::path::PathBuf::from("/kigi-staged-cwd-marker");
|
||||
|
||||
let mut buf = Buffer::empty(area);
|
||||
render_header(&mut buf, area, &theme, &rows, &mut state);
|
||||
@@ -4670,7 +4670,7 @@ mod tests {
|
||||
.map(|x| buf[(x, 0)].symbol().to_string())
|
||||
.collect();
|
||||
assert!(
|
||||
top_row.contains("/grok-staged-cwd-marker"),
|
||||
top_row.contains("/kigi-staged-cwd-marker"),
|
||||
"header must render the staged cwd, not the process cwd; got: {top_row:?}",
|
||||
);
|
||||
}
|
||||
@@ -5629,12 +5629,12 @@ mod tests {
|
||||
let theme = Theme::current();
|
||||
let mut state = DashboardState::new();
|
||||
let check = crate::glyphs::check_mark();
|
||||
state.error_toast = Some(format!("{check} Theme: Grok Day"));
|
||||
state.error_toast = Some(format!("{check} Theme: Kigi Day"));
|
||||
let _ = render_dispatch(&mut buf, Rect::new(0, 0, 80, 3), &theme, &mut state, None);
|
||||
|
||||
let top_row: String = (0..80).map(|x| buf[(x, 0)].symbol().to_string()).collect();
|
||||
assert!(
|
||||
top_row.contains(&format!("{check} Theme: Grok Day")),
|
||||
top_row.contains(&format!("{check} Theme: Kigi Day")),
|
||||
"badge must paint the message verbatim, got: {top_row:?}",
|
||||
);
|
||||
assert!(
|
||||
@@ -7051,7 +7051,7 @@ mod tests {
|
||||
let area = Rect::new(0, 0, 80, 24);
|
||||
// A temp dir with a `.git` child so the toggle is eligible (hermetic,
|
||||
// unlike depending on the test's real cwd being a repo).
|
||||
let repo = std::env::temp_dir().join("grok-loc-wt-toggle-repo-test");
|
||||
let repo = std::env::temp_dir().join("kigi-loc-wt-toggle-repo-test");
|
||||
std::fs::create_dir_all(repo.join(".git")).expect("mk .git");
|
||||
let mut modal =
|
||||
LocationPickerState::new(vec![], repo.clone(), std::collections::HashMap::new());
|
||||
@@ -7111,7 +7111,7 @@ mod tests {
|
||||
let area = Rect::new(0, 0, 80, 24);
|
||||
let mut modal = LocationPickerState::new(
|
||||
vec![],
|
||||
std::path::PathBuf::from("/grok-not-a-repo-xyz-12345"),
|
||||
std::path::PathBuf::from("/kigi-not-a-repo-xyz-12345"),
|
||||
std::collections::HashMap::new(),
|
||||
);
|
||||
let mut buf = Buffer::empty(area);
|
||||
|
||||
@@ -1665,12 +1665,12 @@ mod tests {
|
||||
fn append_roster_rows_uses_model_id_as_secondary_line() {
|
||||
let empty = std::collections::BTreeSet::new();
|
||||
let entry = RosterEntry {
|
||||
model_id: Some("grok-4.5".to_string()),
|
||||
model_id: Some("kigi-4.5".to_string()),
|
||||
..roster_entry_with("m", Some("Fix the bug"), RosterActivity::Dormant)
|
||||
};
|
||||
let rows = collect_roster(&[entry], &empty);
|
||||
assert_eq!(rows.len(), 1);
|
||||
assert_eq!(rows[0].secondary_line.as_deref(), Some("grok-4.5"));
|
||||
assert_eq!(rows[0].secondary_line.as_deref(), Some("kigi-4.5"));
|
||||
}
|
||||
/// Without a model id there's genuinely nothing to show, so the
|
||||
/// second line stays empty rather than rendering a placeholder.
|
||||
@@ -1742,7 +1742,7 @@ mod tests {
|
||||
/// the list uncluttered).
|
||||
#[test]
|
||||
fn idle_local_agent_without_message_has_blank_secondary() {
|
||||
let agent = make_idle_agent_with_model(Some("grok-4.5"));
|
||||
let agent = make_idle_agent_with_model(Some("kigi-4.5"));
|
||||
assert_eq!(classify_top_level(&agent), RowState::Idle);
|
||||
let row = top_level_row(AgentId(0), &agent, false, false, None);
|
||||
assert_eq!(
|
||||
|
||||
@@ -4445,7 +4445,7 @@ pub fn load_persisted_from_path(path: &std::path::Path) -> Option<PersistedDashb
|
||||
/// + rename, so concurrent readers never observe a half-truncated file.
|
||||
pub fn write_persisted(p: &PersistedDashboard) -> std::io::Result<()> {
|
||||
let path = config_path()
|
||||
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "no grok home"))?;
|
||||
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "no kigi home"))?;
|
||||
write_persisted_to_path(&path, p)
|
||||
}
|
||||
|
||||
@@ -4788,8 +4788,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn compact_cwd_strips_home() {
|
||||
let p = Path::new("/Users/alice/projects/grok");
|
||||
assert_eq!(compact_cwd(p, Some("/Users/alice")), "~/projects/grok");
|
||||
let p = Path::new("/Users/alice/projects/kigi");
|
||||
assert_eq!(compact_cwd(p, Some("/Users/alice")), "~/projects/kigi");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -170,11 +170,11 @@ pub fn plugin_group(plugin: &kigi_hooks_plugins_types::PluginInfo) -> PluginGrou
|
||||
use kigi_hooks_plugins_types::{PluginOrigin, PluginScope};
|
||||
|
||||
match &plugin.origin {
|
||||
Some(PluginOrigin::ProjectGrok) => PluginGroup::new(0, "origin:project", "Project"),
|
||||
Some(PluginOrigin::ProjectKigi) => PluginGroup::new(0, "origin:project", "Project"),
|
||||
Some(PluginOrigin::ProjectClaude) => {
|
||||
PluginGroup::new(1, "origin:project-claude", "Project (Claude)")
|
||||
}
|
||||
Some(PluginOrigin::UserGrok) => PluginGroup::new(2, "origin:user", "User"),
|
||||
Some(PluginOrigin::UserKigi) => PluginGroup::new(2, "origin:user", "User"),
|
||||
Some(PluginOrigin::UserClaude)
|
||||
| Some(PluginOrigin::ClaudeInstalled { marketplace: None }) => {
|
||||
PluginGroup::new(3, "origin:user-claude", "User (Claude)")
|
||||
@@ -192,7 +192,7 @@ pub fn plugin_group(plugin: &kigi_hooks_plugins_types::PluginInfo) -> PluginGrou
|
||||
..
|
||||
}) => PluginGroup {
|
||||
rank: 5,
|
||||
key: format!("grok-mp:{source}"),
|
||||
key: format!("kigi-mp:{source}"),
|
||||
label: source.clone(),
|
||||
},
|
||||
Some(PluginOrigin::MarketplaceInstall {
|
||||
@@ -208,7 +208,7 @@ pub fn plugin_group(plugin: &kigi_hooks_plugins_types::PluginInfo) -> PluginGrou
|
||||
}
|
||||
Some(source) => PluginGroup {
|
||||
rank: 5,
|
||||
key: format!("grok-mp:{source}"),
|
||||
key: format!("kigi-mp:{source}"),
|
||||
label: source.to_string(),
|
||||
},
|
||||
None => PluginGroup::new(2, "origin:user", "User"),
|
||||
@@ -2021,9 +2021,9 @@ pub(crate) fn mcp_section_children_hidden(
|
||||
/// Returns `(label, is_custom)` where `is_custom` means the source was added
|
||||
/// via hooks-paths and can be removed.
|
||||
pub fn derive_source_label(source_dir: &str) -> (String, bool) {
|
||||
let grok = kigi_config::kigi_home();
|
||||
let kigi = kigi_config::kigi_home();
|
||||
let source_path = std::path::Path::new(source_dir);
|
||||
// Plugin / installed-plugin dirs, under the user grok home (KIGI_SHARE_DIR-aware)
|
||||
// Plugin / installed-plugin dirs, under the user kigi home (KIGI_SHARE_DIR-aware)
|
||||
// or a project-scoped `{cwd}/.kigi/<subdir>/`. Returns the first path
|
||||
// component after the subdir (the plugin's install directory name).
|
||||
let plugin_name = |subdir: &str| -> Option<String> {
|
||||
@@ -2033,8 +2033,8 @@ pub fn derive_source_label(source_dir: &str) -> (String, bool) {
|
||||
.map(|c| c.as_os_str().to_string_lossy().into_owned())
|
||||
.filter(|s| !s.is_empty())
|
||||
};
|
||||
// User grok home (KIGI_SHARE_DIR-aware).
|
||||
if let Ok(rest) = source_path.strip_prefix(grok.join(subdir))
|
||||
// User kigi home (KIGI_SHARE_DIR-aware).
|
||||
if let Ok(rest) = source_path.strip_prefix(kigi.join(subdir))
|
||||
&& let Some(name) = first_comp(rest)
|
||||
{
|
||||
return Some(name);
|
||||
@@ -2054,7 +2054,7 @@ pub fn derive_source_label(source_dir: &str) -> (String, bool) {
|
||||
return (format!("Plugin: {name}"), false);
|
||||
}
|
||||
// Global hooks under $KIGI_SHARE_DIR/hooks
|
||||
let global_hooks = grok.join("hooks");
|
||||
let global_hooks = kigi.join("hooks");
|
||||
let global_str = global_hooks.display().to_string();
|
||||
if source_dir == global_str || source_dir.starts_with(&format!("{global_str}/")) {
|
||||
return ("Global hooks".into(), false);
|
||||
@@ -2069,7 +2069,7 @@ pub fn derive_source_label(source_dir: &str) -> (String, bool) {
|
||||
}
|
||||
// Custom directory — removable
|
||||
let display = {
|
||||
if let Ok(rest) = source_path.strip_prefix(&grok) {
|
||||
if let Ok(rest) = source_path.strip_prefix(&kigi) {
|
||||
let prefix = crate::util::display_kigi_home_prefix();
|
||||
let rest_str = rest.to_string_lossy();
|
||||
let rest_trimmed = rest_str.strip_prefix('/').unwrap_or(&rest_str);
|
||||
@@ -2138,7 +2138,7 @@ fn skill_source_str(skill: &SkillInfo) -> String {
|
||||
match cs {
|
||||
kigi_tools::types::config_source::ConfigSource::User { path } => {
|
||||
if crate::util::is_under_user_kigi_home(path) {
|
||||
crate::util::display_user_grok_path("skills")
|
||||
crate::util::display_user_kigi_path("skills")
|
||||
} else if path.display().to_string().contains("/.claude/") {
|
||||
"~/.claude/skills".into()
|
||||
} else {
|
||||
@@ -3461,7 +3461,7 @@ mod tests {
|
||||
fn derive_source_label_detects_project_scoped_plugins() {
|
||||
// Regression: project-scoped `{cwd}/.kigi/plugins/<name>/` must label as
|
||||
// a (non-removable) plugin, not a removable "Custom" source. The user
|
||||
// grok-home branch is KIGI_SHARE_DIR-aware; this covers the project fallback.
|
||||
// kigi-home branch is KIGI_SHARE_DIR-aware; this covers the project fallback.
|
||||
let (label, is_custom) = derive_source_label("/repo/work/.kigi/plugins/my-plugin/hooks");
|
||||
assert_eq!(label, "Plugin: my-plugin");
|
||||
assert!(!is_custom);
|
||||
@@ -4844,14 +4844,14 @@ mod tests {
|
||||
fn plugin_group_maps_each_origin_variant() {
|
||||
use kigi_hooks_plugins_types::PluginOrigin;
|
||||
for (origin, rank, key, label) in [
|
||||
(PluginOrigin::ProjectGrok, 0, "origin:project", "Project"),
|
||||
(PluginOrigin::ProjectKigi, 0, "origin:project", "Project"),
|
||||
(
|
||||
PluginOrigin::ProjectClaude,
|
||||
1,
|
||||
"origin:project-claude",
|
||||
"Project (Claude)",
|
||||
),
|
||||
(PluginOrigin::UserGrok, 2, "origin:user", "User"),
|
||||
(PluginOrigin::UserKigi, 2, "origin:user", "User"),
|
||||
(
|
||||
PluginOrigin::UserClaude,
|
||||
3,
|
||||
@@ -4878,7 +4878,7 @@ mod tests {
|
||||
git_url: Some("https://example.com/r.git".into()),
|
||||
},
|
||||
5,
|
||||
"grok-mp:xAI Official",
|
||||
"kigi-mp:xAI Official",
|
||||
"xAI Official",
|
||||
),
|
||||
(
|
||||
@@ -4938,7 +4938,7 @@ mod tests {
|
||||
let mut mp = make_plugin("mp-tool");
|
||||
mp.marketplace_source = Some("xAI Official".into());
|
||||
let group = plugin_group(&mp);
|
||||
assert_eq!(group.key, "grok-mp:xAI Official");
|
||||
assert_eq!(group.key, "kigi-mp:xAI Official");
|
||||
assert_eq!(group.label, "xAI Official");
|
||||
|
||||
let mut direct = make_plugin("direct-tool");
|
||||
@@ -4955,7 +4955,7 @@ mod tests {
|
||||
assert_eq!(plugin_group(&unknown).key, "origin:user");
|
||||
|
||||
unknown.marketplace_source = Some("xAI Official".into());
|
||||
assert_eq!(plugin_group(&unknown).key, "grok-mp:xAI Official");
|
||||
assert_eq!(plugin_group(&unknown).key, "kigi-mp:xAI Official");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -4968,7 +4968,7 @@ mod tests {
|
||||
marketplace: "claude-market".into(),
|
||||
},
|
||||
),
|
||||
make_plugin_with_origin("user-tool", PluginOrigin::UserGrok),
|
||||
make_plugin_with_origin("user-tool", PluginOrigin::UserKigi),
|
||||
make_plugin_with_origin("claude-tool", PluginOrigin::UserClaude),
|
||||
]);
|
||||
let buf = render_plugins_into_buffer(&mut state, 100, 40);
|
||||
@@ -5001,7 +5001,7 @@ mod tests {
|
||||
fn plugins_render_multiple_plugins_under_one_group() {
|
||||
use kigi_hooks_plugins_types::PluginOrigin;
|
||||
let mut state = plugins_modal_state(vec![
|
||||
make_plugin_with_origin("solo-tool", PluginOrigin::UserGrok),
|
||||
make_plugin_with_origin("solo-tool", PluginOrigin::UserKigi),
|
||||
make_plugin_with_origin(
|
||||
"catalog-tool",
|
||||
PluginOrigin::ClaudeMarketplace {
|
||||
@@ -5057,7 +5057,7 @@ mod tests {
|
||||
#[test]
|
||||
fn plugins_collapsed_group_hides_rows_and_search_forces_open() {
|
||||
use kigi_hooks_plugins_types::PluginOrigin;
|
||||
let mut plugin = make_plugin_with_origin("user-tool", PluginOrigin::UserGrok);
|
||||
let mut plugin = make_plugin_with_origin("user-tool", PluginOrigin::UserKigi);
|
||||
plugin.root = "/opt/p1".into();
|
||||
let mut state = plugins_modal_state(vec![plugin]);
|
||||
state.plugins_collapsed_groups.insert("origin:user".into());
|
||||
@@ -5097,7 +5097,7 @@ mod tests {
|
||||
let mut disabled = make_plugin_with_origin("off-tool", PluginOrigin::UserClaude);
|
||||
disabled.enabled = false;
|
||||
let mut state = plugins_modal_state(vec![
|
||||
make_plugin_with_origin("user-tool", PluginOrigin::UserGrok),
|
||||
make_plugin_with_origin("user-tool", PluginOrigin::UserKigi),
|
||||
disabled,
|
||||
]);
|
||||
state.plugins_filter = StatusFilter::Disabled;
|
||||
|
||||
@@ -182,7 +182,7 @@ mod tests {
|
||||
/// both cfgs (the dev half is pinned by the constant's shape, the same
|
||||
/// limitation as the `/debug` visibility test).
|
||||
#[test]
|
||||
fn grok_fps_env_enables_hud_where_dev_overlay_absent() {
|
||||
fn kigi_fps_env_enables_hud_where_dev_overlay_absent() {
|
||||
for truthy in ["1", "full", " "] {
|
||||
assert_eq!(
|
||||
FpsHud::with_env(Some(truthy.into())).enabled(),
|
||||
|
||||
@@ -1473,11 +1473,11 @@ mod tests {
|
||||
#[test]
|
||||
fn render_per_model_breakdown_shows_each_model() {
|
||||
let mut goal = make_goal();
|
||||
goal.live_tokens_by_model = vec![("grok-4".into(), 12_300), ("grok-3-mini".into(), 8_000)];
|
||||
goal.live_tokens_by_model = vec![("kigi-4".into(), 12_300), ("kigi-3-mini".into(), 8_000)];
|
||||
let text = render_to_text(&goal);
|
||||
assert!(text.contains("grok-4"), "first model must render:\n{text}");
|
||||
assert!(text.contains("kigi-4"), "first model must render:\n{text}");
|
||||
assert!(
|
||||
text.contains("grok-3-mini"),
|
||||
text.contains("kigi-3-mini"),
|
||||
"second model must render:\n{text}"
|
||||
);
|
||||
// Compact token formatting is reused.
|
||||
|
||||
@@ -230,7 +230,7 @@ pub fn convert_list_response(resp: McpsListResponse) -> Vec<McpServerInfo> {
|
||||
servers
|
||||
}
|
||||
|
||||
/// Patch a single server row in-place from an `x.ai/mcp/server_status`
|
||||
/// Patch a single server row in-place from an `kigi/mcp/server_status`
|
||||
/// push.
|
||||
///
|
||||
/// Finds the row by `name` and updates its `status` (and optionally its
|
||||
|
||||
@@ -958,17 +958,17 @@ pub fn apply_doc_mouse_scroll(kind: crossterm::event::MouseEventKind, scroll: &m
|
||||
}
|
||||
const DOCS_USER_GUIDE_REL: &str = "docs/user-guide";
|
||||
/// Prefer keeping the on-disk path when width is tight.
|
||||
fn fit_docs_ask_grok_tip(docs_path: &str, width: usize) -> String {
|
||||
fn fit_docs_ask_kigi_tip(docs_path: &str, width: usize) -> String {
|
||||
use crate::render::line_utils::truncate_str;
|
||||
if width == 0 {
|
||||
return String::new();
|
||||
}
|
||||
let long =
|
||||
format!("Tip · Ask Grok about the docs ({docs_path}) — e.g. \"how do I set up MCP?\"");
|
||||
format!("Tip · Ask Kigi about the docs ({docs_path}) — e.g. \"how do I set up MCP?\"");
|
||||
if long.width() <= width {
|
||||
return long;
|
||||
}
|
||||
let short = format!("Tip · Ask Grok about the docs · {docs_path}");
|
||||
let short = format!("Tip · Ask Kigi about the docs · {docs_path}");
|
||||
if short.width() <= width {
|
||||
return short;
|
||||
}
|
||||
@@ -1054,8 +1054,8 @@ pub fn render_doc_picker_overlay(
|
||||
};
|
||||
let (picker_area, tip_area) = split_content_for_tip_footer(mca.content);
|
||||
if let Some(tip_rect) = tip_area {
|
||||
let docs_path = crate::util::display_user_grok_path(DOCS_USER_GUIDE_REL);
|
||||
let tip_line = fit_docs_ask_grok_tip(&docs_path, tip_rect.width as usize);
|
||||
let docs_path = crate::util::display_user_kigi_path(DOCS_USER_GUIDE_REL);
|
||||
let tip_line = fit_docs_ask_kigi_tip(&docs_path, tip_rect.width as usize);
|
||||
render_centered_tip_footer(buf, tip_rect, theme, &tip_line);
|
||||
}
|
||||
const NARROW_THRESHOLD: u16 = 70;
|
||||
@@ -1276,7 +1276,7 @@ mod palette_tests {
|
||||
#[cfg(test)]
|
||||
mod doc_picker_tip_tests {
|
||||
use super::{
|
||||
ActiveModal, DOCS_USER_GUIDE_REL, fit_docs_ask_grok_tip, howto_list_modal,
|
||||
ActiveModal, DOCS_USER_GUIDE_REL, fit_docs_ask_kigi_tip, howto_list_modal,
|
||||
render_doc_picker_overlay,
|
||||
};
|
||||
use ratatui::buffer::Buffer;
|
||||
@@ -1284,16 +1284,16 @@ mod doc_picker_tip_tests {
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
#[test]
|
||||
fn fit_docs_tip_prefers_path_and_never_overflows() {
|
||||
let path = crate::util::display_user_grok_path(DOCS_USER_GUIDE_REL);
|
||||
let path = crate::util::display_user_kigi_path(DOCS_USER_GUIDE_REL);
|
||||
let long =
|
||||
format!("Tip · Ask Grok about the docs ({path}) — e.g. \"how do I set up MCP?\"");
|
||||
let short = format!("Tip · Ask Grok about the docs · {path}");
|
||||
format!("Tip · Ask Kigi about the docs ({path}) — e.g. \"how do I set up MCP?\"");
|
||||
let short = format!("Tip · Ask Kigi about the docs · {path}");
|
||||
let path_only = format!("Tip · {path}");
|
||||
assert_eq!(fit_docs_ask_grok_tip(&path, long.width()), long);
|
||||
assert_eq!(fit_docs_ask_grok_tip(&path, short.width()), short);
|
||||
assert_eq!(fit_docs_ask_grok_tip(&path, path_only.width()), path_only);
|
||||
assert_eq!(fit_docs_ask_kigi_tip(&path, long.width()), long);
|
||||
assert_eq!(fit_docs_ask_kigi_tip(&path, short.width()), short);
|
||||
assert_eq!(fit_docs_ask_kigi_tip(&path, path_only.width()), path_only);
|
||||
for w in [5usize, 12, 20, 30] {
|
||||
let line = fit_docs_ask_grok_tip(&path, w);
|
||||
let line = fit_docs_ask_kigi_tip(&path, w);
|
||||
assert!(line.width() <= w, "overflow at {w}: {line:?}");
|
||||
}
|
||||
}
|
||||
@@ -1328,7 +1328,7 @@ mod doc_picker_tip_tests {
|
||||
all.push('\n');
|
||||
}
|
||||
assert!(
|
||||
all.contains("Tip") && all.contains("Ask Grok"),
|
||||
all.contains("Tip") && all.contains("Ask Kigi"),
|
||||
"missing tip footer:\n{all}"
|
||||
);
|
||||
assert!(
|
||||
|
||||
@@ -572,7 +572,7 @@ fn render_tab_bar(
|
||||
// not `theme.gray_dim`. At ANSI16 `gray_dim` collapses to the
|
||||
// softer slot (silver on White) which leaves text at ~1.2:1
|
||||
// contrast — fine for the modal frame's one-cell border line
|
||||
// but unreadable as text glyphs on grokday.
|
||||
// but unreadable as text glyphs on kigiday.
|
||||
let style = if is_active {
|
||||
if state.tabs_focused && !is_embedded {
|
||||
Style::default()
|
||||
@@ -1210,7 +1210,7 @@ mod tests {
|
||||
use ratatui::layout::Rect;
|
||||
|
||||
let theme = Theme::current();
|
||||
const TEXT: &str = "Tip · Ask Grok about the docs";
|
||||
const TEXT: &str = "Tip · Ask Kigi about the docs";
|
||||
let row_text = |width: u16| {
|
||||
let area = Rect {
|
||||
x: 0,
|
||||
@@ -1229,7 +1229,7 @@ mod tests {
|
||||
let start = wide.find("Tip").expect("Tip");
|
||||
let trailing = wide.chars().rev().take_while(|c| *c == ' ').count();
|
||||
assert!(start.abs_diff(trailing) <= 1, "not centered: {wide:?}");
|
||||
assert!(wide.contains("Ask Grok about the docs"));
|
||||
assert!(wide.contains("Ask Kigi about the docs"));
|
||||
|
||||
let tiny = row_text(10);
|
||||
assert!(tiny.contains("Tip"));
|
||||
|
||||
@@ -1614,7 +1614,7 @@ fn char_wrap(s: &str, width: usize) -> Vec<String> {
|
||||
/// RejectOnce renders as a freeform input row (matching question view style)
|
||||
/// using the same 1-based shortcut number as its prefix:
|
||||
/// ```text
|
||||
/// 3 [ ] Tell Grok what to do differently
|
||||
/// 3 [ ] Tell Kigi what to do differently
|
||||
/// 3 [x] ❯ my followup message preview...
|
||||
/// ```
|
||||
///
|
||||
@@ -2216,7 +2216,7 @@ mod tests {
|
||||
state.description = vec![
|
||||
"{".to_string(),
|
||||
" \"issue\": \"ABC-123\",".to_string(),
|
||||
" \"body\": \"hello from grok\"".to_string(),
|
||||
" \"body\": \"hello from kigi\"".to_string(),
|
||||
"}".to_string(),
|
||||
];
|
||||
state.options = vec![acp::PermissionOption::new(
|
||||
@@ -2242,7 +2242,7 @@ mod tests {
|
||||
"args JSON not rendered:\n{text}"
|
||||
);
|
||||
assert!(
|
||||
text.contains("\"body\": \"hello from grok\""),
|
||||
text.contains("\"body\": \"hello from kigi\""),
|
||||
"args JSON not rendered:\n{text}"
|
||||
);
|
||||
// Option row still visible below the args.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use agent_client_protocol as acp;
|
||||
use kigi_acp_lib::AcpResult;
|
||||
|
||||
pub use kigi_tools::implementations::grok_build::exit_plan_mode::{
|
||||
pub use kigi_tools::implementations::kigi::exit_plan_mode::{
|
||||
ExitPlanModeExtRequest, ExitPlanModeExtResponse,
|
||||
};
|
||||
|
||||
|
||||
@@ -153,10 +153,10 @@ pub fn suggestion_size(text: &str) -> (usize, usize) {
|
||||
}
|
||||
|
||||
/// Resolve the client-side model hint sent with the suggestion request:
|
||||
/// env override > `grok-build-0.1` when the catalog offers it > `None`.
|
||||
/// env override > `kigi-0.1` when the catalog offers it > `None`.
|
||||
///
|
||||
/// The hint is one tier of the shell-side resolution (env > config.toml >
|
||||
/// remote settings > this hint > `grok-build-0.1` default): the shell
|
||||
/// remote settings > this hint > `kigi-0.1` default): the shell
|
||||
/// catalog-guards the effective model and skips the request entirely when
|
||||
/// it is not sampleable — the session model is never used for suggestion
|
||||
/// calls.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
//! ← top vpad (configurable)
|
||||
//! ❯ type here, text wraps ← prefix + TextArea
|
||||
//! continuation of long input... ← TextArea continuation
|
||||
//! grok-3 · yolo ← info line (optional)
|
||||
//! kigi-3 · yolo ← info line (optional)
|
||||
//! ```
|
||||
//!
|
||||
//! The accent line (┃) and selection box are rendered by the caller.
|
||||
@@ -1099,7 +1099,7 @@ impl PromptWidget {
|
||||
{
|
||||
// The row's trailing space is its args separator; absorb
|
||||
// an existing plain-text one so accepting mid-token in
|
||||
// `/mod grok-4` yields `/model grok-4`, not `/model grok-4`
|
||||
// `/mod kigi-4` yields `/model kigi-4`, not `/model kigi-4`
|
||||
// — absorb (rather than trim the insert) so the cursor
|
||||
// lands after the separator, in the args phase. Never
|
||||
// absorb an element's byte: a chip's leading space is chip
|
||||
@@ -1125,7 +1125,7 @@ impl PromptWidget {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
// Replace the args range (e.g., "gr" → "Grok 4 Fast").
|
||||
// Replace the args range (e.g., "gr" → "Kigi 4 Fast").
|
||||
if let Some(ref range) = snap.args_range
|
||||
&& range.end <= text_len
|
||||
&& self.textarea.text().is_char_boundary(range.start)
|
||||
@@ -3012,7 +3012,7 @@ impl PromptWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom divider: ╰──────────grok-3 · flags──╯
|
||||
// Bottom divider: ╰──────────kigi-3 · flags──╯
|
||||
// Guard on actual allocated height, not requested `info_block`: during
|
||||
// resize the layout may squeeze the info block to 0 rows, leaving
|
||||
// chunks[2].y past the buffer boundary.
|
||||
|
||||
@@ -251,7 +251,7 @@
|
||||
TerminalName::Rio,
|
||||
TerminalName::Foot,
|
||||
TerminalName::JetBrains,
|
||||
TerminalName::GrokDesktop,
|
||||
TerminalName::KigiDesktop,
|
||||
TerminalName::Vte,
|
||||
TerminalName::Terminator,
|
||||
TerminalName::WindowsTerminal,
|
||||
@@ -277,7 +277,7 @@
|
||||
| TerminalName::Rio
|
||||
| TerminalName::Foot
|
||||
| TerminalName::JetBrains
|
||||
| TerminalName::GrokDesktop
|
||||
| TerminalName::KigiDesktop
|
||||
| TerminalName::Vte
|
||||
| TerminalName::Terminator
|
||||
| TerminalName::WindowsTerminal
|
||||
@@ -397,7 +397,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
let mut img = test_image();
|
||||
img.source_path = Some(PathBuf::from("/tmp/grok-test-image.png"));
|
||||
img.source_path = Some(PathBuf::from("/tmp/kigi-test-image.png"));
|
||||
|
||||
let mut pw = ghostty_prompt();
|
||||
pw.insert_image(img).unwrap();
|
||||
@@ -407,12 +407,12 @@
|
||||
"chip text should be path-free: {full:?}"
|
||||
);
|
||||
assert!(
|
||||
!full.contains("/tmp/grok-test-image.png"),
|
||||
!full.contains("/tmp/kigi-test-image.png"),
|
||||
"source path must not appear in the buffer chip: {full:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
pw.images[0].source_path.as_deref(),
|
||||
Some(std::path::Path::new("/tmp/grok-test-image.png")),
|
||||
Some(std::path::Path::new("/tmp/kigi-test-image.png")),
|
||||
"source_path retained on the PastedImage record"
|
||||
);
|
||||
|
||||
@@ -420,7 +420,7 @@
|
||||
let selected = pw.textarea.selected_text().expect("selected text");
|
||||
assert!(selected.contains("[Image #1]"));
|
||||
assert!(
|
||||
!selected.contains("/tmp/grok-test-image.png"),
|
||||
!selected.contains("/tmp/kigi-test-image.png"),
|
||||
"select-all must not copy the filepath from the chip"
|
||||
);
|
||||
}
|
||||
@@ -1559,7 +1559,7 @@
|
||||
let models = crate::acp::model_state::ModelState::default();
|
||||
|
||||
// Cursor inside the command token with args already present.
|
||||
pw.textarea.insert_str("/mod grok-4");
|
||||
pw.textarea.insert_str("/mod kigi-4");
|
||||
pw.textarea.set_cursor(3);
|
||||
pw.refresh_slash(&models);
|
||||
|
||||
@@ -1577,7 +1577,7 @@
|
||||
assert!(pw.accept_slash_completion(&models));
|
||||
assert_eq!(
|
||||
pw.textarea.text(),
|
||||
"/model grok-4",
|
||||
"/model kigi-4",
|
||||
"the row's trailing space must not stack on the existing separator"
|
||||
);
|
||||
// Absorb (not trim-the-insert): the cursor must land after the
|
||||
@@ -1664,25 +1664,25 @@
|
||||
|
||||
let mut pw = PromptWidget::new();
|
||||
let mut models = crate::acp::model_state::ModelState::default();
|
||||
let model_id = agent_client_protocol::ModelId::new(Arc::from("grok-4.5"));
|
||||
let model_id = agent_client_protocol::ModelId::new(Arc::from("kigi-4.5"));
|
||||
models.available.insert(
|
||||
model_id.clone(),
|
||||
agent_client_protocol::ModelInfo::new(model_id, "Grok 4.5".to_string()),
|
||||
agent_client_protocol::ModelInfo::new(model_id, "Kigi 4.5".to_string()),
|
||||
);
|
||||
|
||||
// Type "/model gr" and position cursor at end (in args).
|
||||
pw.textarea.insert_str("/model gr");
|
||||
// Type "/model ki" and position cursor at end (in args).
|
||||
pw.textarea.insert_str("/model ki");
|
||||
pw.refresh_slash(&models);
|
||||
|
||||
let snap = pw.slash_snapshot();
|
||||
assert!(snap.open, "arg suggestions should be open");
|
||||
assert!(snap.args_range.is_some());
|
||||
|
||||
// Accept arg completion → should replace "gr" with "Grok 4.5".
|
||||
// Accept arg completion → should replace "ki" with "Kigi 4.5".
|
||||
pw.accept_slash_completion(&models);
|
||||
let text = pw.textarea.text().to_string();
|
||||
assert!(
|
||||
text.contains("Grok 4.5"),
|
||||
text.contains("Kigi 4.5"),
|
||||
"arg should be replaced, got: {:?}",
|
||||
text
|
||||
);
|
||||
@@ -4303,7 +4303,7 @@
|
||||
// -- Predicted-next-prompt suggestion through PromptWidget ----------------
|
||||
|
||||
/// Widget with an active gate and a loaded suggestion — the state right
|
||||
/// after a turn ends with `x.ai/suggestPrompt` resolved.
|
||||
/// after a turn ends with `kigi/suggestPrompt` resolved.
|
||||
fn widget_with_prompt_suggestion(text: &str) -> PromptWidget {
|
||||
let mut pw = PromptWidget::new();
|
||||
pw.prompt_suggestion_active = true;
|
||||
|
||||
@@ -14,7 +14,7 @@ use std::time::Instant;
|
||||
|
||||
use kigi_acp_lib::AcpResult;
|
||||
use kigi_markdown::StreamingMarkdownRenderer;
|
||||
pub use kigi_tools::implementations::grok_build::ask_user_question::{
|
||||
pub use kigi_tools::implementations::kigi::ask_user_question::{
|
||||
AskUserQuestionMode, Question, QuestionOption,
|
||||
};
|
||||
use ratatui::buffer::Buffer;
|
||||
@@ -68,7 +68,7 @@ pub enum QuestionFocus {
|
||||
}
|
||||
|
||||
/// Pager-internal origin for a locally-opened question (one that was NOT
|
||||
/// driven by an ACP `x.ai/ask_user_question` request).
|
||||
/// driven by an ACP `kigi/ask_user_question` request).
|
||||
///
|
||||
/// Drives what `submit_question_answers` returns when the user submits.
|
||||
/// Mutually exclusive with `QuestionViewState.response_tx`: a local
|
||||
@@ -118,7 +118,7 @@ pub enum LocalQuestionKind {
|
||||
|
||||
/// Complete state for the question view overlay.
|
||||
///
|
||||
/// Created when an `x.ai/ask_user_question` ext-method request arrives;
|
||||
/// Created when an `kigi/ask_user_question` ext-method request arrives;
|
||||
/// destroyed on submit, skip, or cancel.
|
||||
///
|
||||
/// Not `Clone` because it owns a `oneshot::Sender` for the ACP response.
|
||||
@@ -171,7 +171,7 @@ pub struct QuestionViewState {
|
||||
/// `Some(1)` = Skip interview.
|
||||
pub bottom_panel_index: Option<usize>,
|
||||
/// `Some` when this question was opened locally (e.g. by `/fork`)
|
||||
/// instead of by an ACP `x.ai/ask_user_question` request. `None` for
|
||||
/// instead of by an ACP `kigi/ask_user_question` request. `None` for
|
||||
/// ACP questions (preserves today's behaviour).
|
||||
///
|
||||
/// Mutually exclusive with `response_tx`: a local question never has
|
||||
@@ -210,7 +210,7 @@ impl QuestionViewState {
|
||||
|
||||
/// Create a new question view state with an ACP response sender.
|
||||
///
|
||||
/// Called by the `ExtMethod` handler when a blocking `x.ai/ask_user_question`
|
||||
/// Called by the `ExtMethod` handler when a blocking `kigi/ask_user_question`
|
||||
/// request arrives from the shell coordinator.
|
||||
pub fn with_response_tx(
|
||||
tool_call_id: String,
|
||||
@@ -724,7 +724,7 @@ impl QuestionViewState {
|
||||
/// the current freeform text so the caller can load it into the prompt.
|
||||
///
|
||||
/// No-op returning an empty string when `no_freeform` is set: such
|
||||
/// questions (e.g. the SuperGrok upsell) have no freeform row, so
|
||||
/// questions (e.g. the subscription upsell) have no freeform row, so
|
||||
/// `InputMode` must be unreachable. Callers gate on `no_freeform` /
|
||||
/// [`Self::is_on_freeform_row`] too; this is defense in depth.
|
||||
pub fn activate_freeform_input(&mut self) -> String {
|
||||
@@ -812,10 +812,9 @@ impl QuestionViewState {
|
||||
/// - Notes included when freeform text is non-empty and selected.
|
||||
pub fn build_accepted_response(
|
||||
&self,
|
||||
) -> kigi_tools::implementations::grok_build::ask_user_question::AskUserQuestionExtResponse
|
||||
{
|
||||
) -> kigi_tools::implementations::kigi::ask_user_question::AskUserQuestionExtResponse {
|
||||
use indexmap::IndexMap;
|
||||
use kigi_tools::implementations::grok_build::ask_user_question::{
|
||||
use kigi_tools::implementations::kigi::ask_user_question::{
|
||||
AskUserQuestionExtResponse, QuestionAnnotation,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
@@ -899,7 +898,7 @@ impl QuestionViewState {
|
||||
/// double-send.
|
||||
pub fn send_ext_response(
|
||||
&mut self,
|
||||
response: kigi_tools::implementations::grok_build::ask_user_question::AskUserQuestionExtResponse,
|
||||
response: kigi_tools::implementations::kigi::ask_user_question::AskUserQuestionExtResponse,
|
||||
) -> bool {
|
||||
let Some(tx) = self.response_tx.take() else {
|
||||
return false;
|
||||
@@ -2545,7 +2544,7 @@ mod tests {
|
||||
|
||||
// ── no_freeform ────────────────────────────────────────────────────
|
||||
|
||||
/// `no_freeform` questions (e.g. the SuperGrok upsell) have no "Other"
|
||||
/// `no_freeform` questions (e.g. the subscription upsell) have no "Other"
|
||||
/// row, so activating freeform input must be impossible: focus stays in
|
||||
/// Navigation and nothing gets marked selected. Regression test for the
|
||||
/// upsell modal letting the user type after clicking under the last
|
||||
|
||||
@@ -43,7 +43,7 @@ pub enum QueueRowOrigin {
|
||||
/// any idle-drained prompt). Edits mutate the local queue directly.
|
||||
Local,
|
||||
/// A server-authoritative `shared_prompt_queues` entry (plain prompt queued
|
||||
/// while running). Edits route to the agent as `x.ai/queue/*`.
|
||||
/// while running). Edits route to the agent as `kigi/queue/*`.
|
||||
Server,
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ pub struct QueuePane {
|
||||
/// Theme kind at the last render. Used to detect a theme switch and
|
||||
/// refresh `list_style`, whose `selection_bg` is captured from the theme
|
||||
/// (otherwise the focused-row highlight keeps the previous theme's
|
||||
/// `bg_highlight` — e.g. GrokNight's dark band leaking into GrokDay).
|
||||
/// `bg_highlight` — e.g. KigiNight's dark band leaking into KigiDay).
|
||||
last_theme: ThemeKind,
|
||||
/// Shared visibility/focus state.
|
||||
pub overlay: OverlayState,
|
||||
@@ -827,8 +827,8 @@ impl QueuePane {
|
||||
// Detect a theme switch and refresh the list chrome style. Its
|
||||
// `selection_bg` (the focused-row highlight) is captured from the
|
||||
// theme's `bg_highlight`; without this it would keep the theme active
|
||||
// at construction (default GrokNight, dark) after the user switches —
|
||||
// painting a dark band on a light GrokDay canvas.
|
||||
// at construction (default KigiNight, dark) after the user switches —
|
||||
// painting a dark band on a light KigiDay canvas.
|
||||
let current_theme = Theme::current_kind();
|
||||
if current_theme != self.last_theme {
|
||||
self.last_theme = current_theme;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! A compact top-right overlay painting a per-frame snapshot of the scroll
|
||||
//! state machine ([`MouseScrollState::debug_snapshot`]) plus the active
|
||||
//! scrollback's viewport facts, inside a REAL session with the REAL event
|
||||
//! loop. Recipe: `KIGI_FPS=1 KIGI_SCROLL_DEBUG=1 grok --resume <session>`,
|
||||
//! loop. Recipe: `KIGI_FPS=1 KIGI_SCROLL_DEBUG=1 kigi --resume <session>`,
|
||||
//! then flip `scroll_mode` / `scroll_lines` / `invert_scroll` /
|
||||
//! `scroll_speed` in `/settings` to compare variants live. For event-exact
|
||||
//! capture beyond this per-frame sampling, add `KIGI_SCROLL_LOG=1` — the
|
||||
|
||||
@@ -157,7 +157,7 @@ impl SourceFilter {
|
||||
|
||||
/// Returns `true` if a session with the given `source` string passes the filter.
|
||||
///
|
||||
/// grok.com conversations carry `source == "conversation"` and live remotely,
|
||||
/// kimi.com conversations carry `source == "conversation"` and live remotely,
|
||||
/// so they pass the `Remote` filter (and `All`) but not `Local`.
|
||||
pub fn matches(self, source: &str) -> bool {
|
||||
match self {
|
||||
@@ -1192,7 +1192,7 @@ mod tests {
|
||||
assert!(!SourceFilter::Remote.matches("local"));
|
||||
assert!(!SourceFilter::Remote.matches("cursor"));
|
||||
|
||||
// grok.com conversations are remote: visible under All + Remote, not Local.
|
||||
// kimi.com conversations are remote: visible under All + Remote, not Local.
|
||||
assert!(SourceFilter::All.matches("conversation"));
|
||||
assert!(SourceFilter::Remote.matches("conversation"));
|
||||
assert!(!SourceFilter::Local.matches("conversation"));
|
||||
|
||||
@@ -1285,8 +1285,8 @@ fn render_row_list_with_search_bar(
|
||||
|
||||
fn render_docs_footer(buf: &mut Buffer, area: Rect, theme: &Theme) {
|
||||
const LONG: &str =
|
||||
"Tip · Ask Grok: \"change theme to grokday\" or \"what does compact mode do?\"";
|
||||
const SHORT: &str = "Tip · Ask Grok to change a setting";
|
||||
"Tip · Ask Kigi: \"change theme to kigiday\" or \"what does compact mode do?\"";
|
||||
const SHORT: &str = "Tip · Ask Kigi to change a setting";
|
||||
let text = modal_window::fit_tip_line(&[LONG, SHORT], area.width as usize);
|
||||
modal_window::render_centered_tip_footer(buf, area, theme, text.as_ref());
|
||||
}
|
||||
@@ -1743,7 +1743,7 @@ const PICKER_MARKER_W: u16 = 1;
|
||||
/// The chooser already scrolls within the viewport when the focused choice
|
||||
/// falls off-screen (`picker_scroll_offset`); this limit exists so catalogs
|
||||
/// stay intentionally curated rather than unbounded. Sized to fit the full
|
||||
/// Grok STT language list (25 codes + client-only `auto` = 26) with headroom.
|
||||
/// Kigi STT language list (25 codes + client-only `auto` = 26) with headroom.
|
||||
pub(crate) const MAX_PICKER_CHOICES: usize = 32;
|
||||
|
||||
/// Render the shared sub-pane header (bold title row + word-wrapped description)
|
||||
@@ -6572,8 +6572,8 @@ mod tests {
|
||||
let registry = SettingsRegistry::from_entries(vec![synthetic_meta]);
|
||||
let snapshot = PagerLocalSnapshot {
|
||||
available_models: vec![(
|
||||
"Grok Test".to_string(),
|
||||
acp::ModelId::new(Arc::from("grok-test")),
|
||||
"Kigi Test".to_string(),
|
||||
acp::ModelId::new(Arc::from("kigi-test")),
|
||||
)],
|
||||
..PagerLocalSnapshot::default()
|
||||
};
|
||||
@@ -6596,7 +6596,7 @@ mod tests {
|
||||
/// buffers that fit entirely within the visible window.
|
||||
#[test]
|
||||
fn render_editing_value_cursor_at_logical_position_when_buffer_fits() {
|
||||
let mut s = editor_render_fixture("Grok Test", 4); // cursor between "Grok" and " Test"
|
||||
let mut s = editor_render_fixture("Kigi Test", 4); // cursor between "Kigi" and " Test"
|
||||
let area = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -7280,9 +7280,9 @@ mod tests {
|
||||
#[test]
|
||||
fn picking_enum_esc_dispatches_preview_revert_for_each_key() {
|
||||
let cases: &[(&str, &str)] = &[
|
||||
("theme", "groknight"),
|
||||
("auto_dark_theme", "groknight"),
|
||||
("auto_light_theme", "grokday"),
|
||||
("theme", "kiginight"),
|
||||
("auto_dark_theme", "kiginight"),
|
||||
("auto_light_theme", "kigiday"),
|
||||
];
|
||||
for &(key, original) in cases {
|
||||
let mut s = make_state();
|
||||
@@ -7331,18 +7331,18 @@ mod tests {
|
||||
s.mode = SettingsModalMode::PickingEnum {
|
||||
key: "theme",
|
||||
choices_idx: 0,
|
||||
original_value: SettingValue::Enum("groknight"),
|
||||
original_value: SettingValue::Enum("kiginight"),
|
||||
supports_preview: true,
|
||||
};
|
||||
let outcome = handle_settings_key(&mut s, &KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
match outcome {
|
||||
SettingsKeyOutcome::Action(Action::PreviewTheme(name)) => {
|
||||
assert_eq!(
|
||||
name, "groknight",
|
||||
name, "kiginight",
|
||||
"Esc revert must dispatch the original canonical"
|
||||
);
|
||||
}
|
||||
other => panic!("expected Action::PreviewTheme(\"groknight\") on Esc, got {other:?}"),
|
||||
other => panic!("expected Action::PreviewTheme(\"kiginight\") on Esc, got {other:?}"),
|
||||
}
|
||||
assert!(matches!(s.mode, SettingsModalMode::Browse));
|
||||
}
|
||||
@@ -9000,14 +9000,14 @@ mod tests {
|
||||
assert!(
|
||||
validation_error.is_some(),
|
||||
"validation_error must be Some for unknown model 'a' \
|
||||
(catalog has 'Grok 4 Fast' only)",
|
||||
(catalog has 'Kigi 4 Fast' only)",
|
||||
);
|
||||
}
|
||||
_ => panic!("mode must remain EditingValue after char input"),
|
||||
}
|
||||
|
||||
// Enter on a buffer that fails the KnownModel validator
|
||||
// (catalog has 'Grok 4 Fast'; "a" doesn't match) is
|
||||
// (catalog has 'Kigi 4 Fast'; "a" doesn't match) is
|
||||
// Unchanged — commit refused.
|
||||
let outcome =
|
||||
handle_settings_key(&mut s, &KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
@@ -9053,8 +9053,8 @@ mod tests {
|
||||
let mut s = make_state();
|
||||
s.mode = SettingsModalMode::EditingValue {
|
||||
key: "default_model",
|
||||
buffer: "grok-4".to_string(),
|
||||
cursor_byte: "grok-4".len(),
|
||||
buffer: "kigi-4".to_string(),
|
||||
cursor_byte: "kigi-4".len(),
|
||||
validation_error: None,
|
||||
};
|
||||
let outcome = handle_settings_key(&mut s, &KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
@@ -9880,7 +9880,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// -- User-feedback follow-up: always reserve a blank line between
|
||||
// the "Tip · Ask Grok…" docs footer and the keybindings hints.
|
||||
// the "Tip · Ask Kigi…" docs footer and the keybindings hints.
|
||||
//
|
||||
// Before this fix, when the hints wrapped to 2 lines (narrow modal
|
||||
// widths) the chrome's 2-row footer was fully consumed by hint
|
||||
@@ -10683,7 +10683,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// SHORT path: width that fits SHORT but not LONG.
|
||||
// SHORT = "Tip · Ask Grok to change a setting" (34 cells);
|
||||
// SHORT = "Tip · Ask Kigi to change a setting" (34 cells);
|
||||
// LONG ≈ 73 cells. width=40 lands in the SHORT band.
|
||||
let (row_short, tip_start_short, trailing_short) = render(40);
|
||||
assert!(
|
||||
@@ -10691,8 +10691,8 @@ mod tests {
|
||||
"width=40 must render SHORT path (contains `change a setting`): {row_short:?}",
|
||||
);
|
||||
assert!(
|
||||
!row_short.contains("grokday"),
|
||||
"width=40 must NOT render LONG path (no `grokday`): {row_short:?}",
|
||||
!row_short.contains("kigiday"),
|
||||
"width=40 must NOT render LONG path (no `kigiday`): {row_short:?}",
|
||||
);
|
||||
assert!(
|
||||
tip_start_short.abs_diff(trailing_short) <= 1,
|
||||
@@ -10736,7 +10736,7 @@ mod tests {
|
||||
let mut tip_y: Option<u16> = None;
|
||||
for y in 0..area.height {
|
||||
let txt = buf_row_text(&buf, y, area.x, area.width);
|
||||
if txt.contains("Tip") && txt.contains("Ask Grok") {
|
||||
if txt.contains("Tip") && txt.contains("Ask Kigi") {
|
||||
tip_y = Some(y);
|
||||
break;
|
||||
}
|
||||
@@ -11044,17 +11044,17 @@ mod tests {
|
||||
// For preview-supporting enums (theme), the breadcrumb-
|
||||
// click revert dispatches `Action::PreviewTheme(original)`.
|
||||
// The original canonical for the default theme is
|
||||
// `"groknight"`. Tightened from the previous `Action(_) |
|
||||
// `"kiginight"`. Tightened from the previous `Action(_) |
|
||||
// Changed` to lock in the revert contract.
|
||||
match outcome {
|
||||
SettingsKeyOutcome::Action(Action::PreviewTheme(orig)) => {
|
||||
assert_eq!(
|
||||
orig, "groknight",
|
||||
orig, "kiginight",
|
||||
"breadcrumb-click revert must carry the original canonical",
|
||||
);
|
||||
}
|
||||
other => panic!(
|
||||
"expected Action(PreviewTheme(\"groknight\")) — the keyboard \
|
||||
"expected Action(PreviewTheme(\"kiginight\")) — the keyboard \
|
||||
Esc-equivalent revert — got {other:?}",
|
||||
),
|
||||
}
|
||||
@@ -11098,7 +11098,7 @@ mod tests {
|
||||
}
|
||||
other => panic!("expected PickingEnum, got {other:?}"),
|
||||
};
|
||||
// Pick a different index. The default theme is `groknight`
|
||||
// Pick a different index. The default theme is `kiginight`
|
||||
// (index 1 per the registry); advance to index 0 to ensure
|
||||
// we're navigating to a different value.
|
||||
let target_idx = if advanced_idx == 0 { 1 } else { 0 };
|
||||
@@ -11158,11 +11158,11 @@ mod tests {
|
||||
key, "theme",
|
||||
"OpenResetConfirm key must be the active picker setting",
|
||||
);
|
||||
// Default theme is `groknight`; entering the picker
|
||||
// captures `original_value = current value = groknight`,
|
||||
// Default theme is `kiginight`; entering the picker
|
||||
// captures `original_value = current value = kiginight`,
|
||||
// so the revert dispatches with that canonical.
|
||||
assert_eq!(
|
||||
orig, "groknight",
|
||||
orig, "kiginight",
|
||||
"PreviewTheme revert must carry the original canonical",
|
||||
);
|
||||
}
|
||||
@@ -11628,9 +11628,9 @@ mod tests {
|
||||
/// regardless of how much the bg tokens differ in luma.
|
||||
///
|
||||
/// The previous name `_title_bg_is_darker_than_content_bg` was
|
||||
/// misleading: on dark themes (GrokNight, TokyoNight, RosePine
|
||||
/// misleading: on dark themes (KigiNight, TokyoNight, RosePine
|
||||
/// Moon) `bg_visual` is actually *lighter* than `bg_highlight`;
|
||||
/// only on the GrokDay light theme is title darker. The
|
||||
/// only on the KigiDay light theme is title darker. The
|
||||
/// contract that the rendering code actually relies on is "title
|
||||
/// uses the heavier / more-saturated `bg_visual` token, content
|
||||
/// uses `bg_highlight`, plus an UNDERLINED title modifier for
|
||||
@@ -11686,14 +11686,14 @@ mod tests {
|
||||
// raw theme directly so this assertion survives `NO_COLOR`
|
||||
// / 256-color quantization.
|
||||
let raw_theme = match crate::theme::Theme::current_kind() {
|
||||
crate::theme::ThemeKind::GrokNight => crate::theme::Theme::groknight(),
|
||||
crate::theme::ThemeKind::KigiNight => crate::theme::Theme::kiginight(),
|
||||
crate::theme::ThemeKind::TokyoNight => crate::theme::Theme::tokyonight(),
|
||||
crate::theme::ThemeKind::GrokDay => crate::theme::Theme::grokday(),
|
||||
crate::theme::ThemeKind::KigiDay => crate::theme::Theme::kigiday(),
|
||||
crate::theme::ThemeKind::RosePineMoon => crate::theme::Theme::rosepine_moon(),
|
||||
// Resolved via `Theme::current()` rather than a constructor
|
||||
// because `theme::oscura` is a private module.
|
||||
crate::theme::ThemeKind::OscuraMidnight => crate::theme::Theme::current(),
|
||||
crate::theme::ThemeKind::Auto => crate::theme::Theme::groknight(),
|
||||
crate::theme::ThemeKind::Auto => crate::theme::Theme::kiginight(),
|
||||
};
|
||||
assert_ne!(
|
||||
raw_theme.bg_visual, raw_theme.bg_highlight,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! unnecessary network requests.
|
||||
//!
|
||||
//! ACP integration: on text change (after debounce), sends an
|
||||
//! `x.ai/suggest` request through the Effect pipeline. Stale responses
|
||||
//! `kigi/suggest` request through the Effect pipeline. Stale responses
|
||||
//! are discarded via generation tracking.
|
||||
|
||||
/// Source of a shell command suggestion.
|
||||
@@ -46,14 +46,14 @@ pub(crate) struct GhostTextState {
|
||||
pub(crate) generation: u64,
|
||||
}
|
||||
|
||||
/// Parsed ghost suggestion from an ACP `x.ai/suggest` response.
|
||||
/// Parsed ghost suggestion from an ACP `kigi/suggest` response.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GhostSuggestionParsed {
|
||||
pub suffix: String,
|
||||
pub source: SuggestionSource,
|
||||
}
|
||||
|
||||
/// A single completion item from an ACP `x.ai/suggest` response.
|
||||
/// A single completion item from an ACP `kigi/suggest` response.
|
||||
// `Default` (empty item) exists for downstream test fixtures — functional-
|
||||
// update construction (`..Default::default()`) keeps out-of-crate literals
|
||||
// (e.g. kigi-pager-minimal's) compiling when optional fields are added.
|
||||
@@ -87,7 +87,7 @@ impl CompletionItemParsed {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsed response from an ACP `x.ai/suggest` request.
|
||||
/// Parsed response from an ACP `kigi/suggest` request.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SuggestResponseParsed {
|
||||
pub ghost: Option<GhostSuggestionParsed>,
|
||||
@@ -96,7 +96,7 @@ pub struct SuggestResponseParsed {
|
||||
}
|
||||
|
||||
impl SuggestResponseParsed {
|
||||
/// Parse a raw JSON value from an ACP `x.ai/suggest` response.
|
||||
/// Parse a raw JSON value from an ACP `kigi/suggest` response.
|
||||
pub fn from_json(value: &serde_json::Value) -> Option<Self> {
|
||||
let result = value.get("result").unwrap_or(value);
|
||||
let generation = result.get("generation")?.as_u64()?;
|
||||
@@ -197,7 +197,7 @@ pub enum SuggestionAction {
|
||||
Debounce { generation: u64 },
|
||||
}
|
||||
|
||||
/// Wire `limit` for `x.ai/suggest` fetches. Matches the shell file
|
||||
/// Wire `limit` for `kigi/suggest` fetches. Matches the shell file
|
||||
/// provider's ranked-result cap (`MAX_RESULTS` in the shell crate's
|
||||
/// `file_provider.rs`): the provider ranks BEFORE capping, the dropdown
|
||||
/// renders 6 rows and scrolls the rest. Both fetch sites (Tab and the
|
||||
@@ -326,7 +326,7 @@ pub struct SuggestionController {
|
||||
/// Whether AI-powered suggestions are enabled.
|
||||
/// Resolved at construction from `KIGI_SUGGESTIONS_AI` env var.
|
||||
pub ai_enabled: bool,
|
||||
/// Model to use for AI suggestions. Sent in the `x.ai/suggest` request.
|
||||
/// Model to use for AI suggestions. Sent in the `kigi/suggest` request.
|
||||
/// Resolved at construction from `KIGI_SUGGESTIONS_AI_MODEL` env var.
|
||||
pub ai_model: Option<String>,
|
||||
}
|
||||
@@ -734,7 +734,7 @@ impl SuggestionController {
|
||||
generation == self.generation
|
||||
}
|
||||
|
||||
/// Called when an ACP `x.ai/suggest` response arrives, with the text and
|
||||
/// Called when an ACP `kigi/suggest` response arrives, with the text and
|
||||
/// cursor the request was built from (the anchor item `replace_range`
|
||||
/// offsets index into, and the position Tab targets). Takes ownership to
|
||||
/// avoid copying strings. Discards stale responses.
|
||||
|
||||
@@ -1112,7 +1112,7 @@ fn tab_decision_rangeless_rows_never_get_token_semantics() {
|
||||
let mut sc = decision_controller("ls | gr");
|
||||
sc.dropdown.items = vec![
|
||||
rangeless,
|
||||
span_item("grokfile", 5..7, SuggestionSource::FilePath),
|
||||
span_item("kigifile", 5..7, SuggestionSource::FilePath),
|
||||
];
|
||||
assert_eq!(sc.tab_decision("ls | gr", "ls | gr".len()), TabAction::Open);
|
||||
|
||||
|
||||
@@ -2791,7 +2791,7 @@ mod tests {
|
||||
fn entry_label_includes_meta() {
|
||||
let mut info = make_info();
|
||||
info.persona = Some("researcher".into());
|
||||
info.model = Some("grok-3".into());
|
||||
info.model = Some("kigi-3".into());
|
||||
let entry = TaskEntry::from_subagent(&info);
|
||||
let label = match &entry {
|
||||
TaskEntry::Agent { label, .. } => label.as_str(),
|
||||
@@ -2802,7 +2802,7 @@ mod tests {
|
||||
"label should contain capitalized persona: {label}",
|
||||
);
|
||||
assert!(
|
||||
label.contains("grok-3"),
|
||||
label.contains("kigi-3"),
|
||||
"label should contain model: {label}",
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user