§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:
2026-07-18 02:48:46 -04:00
parent 86e3724310
commit 6f31415ed6
1056 changed files with 8410 additions and 18307 deletions
+2 -2
View File
@@ -2,7 +2,7 @@
//!
//! Parses the `_meta` JSON from `SessionNotification` into a struct with
//! typed fields. All fields are `Option` — gracefully degrades when
//! grok-shell hasn't been updated or meta is absent.
//! kigi-shell hasn't been updated or meta is absent.
use serde::{Deserialize, Serialize};
@@ -154,7 +154,7 @@ mod tests {
#[test]
fn parse_missing_new_fields() {
// Simulate old grok-shell that doesn't send streamStartMs/turnStartMs
// Simulate old kigi-shell that doesn't send streamStartMs/turnStartMs
let meta_json = json!({
"totalTokens": 1000u64,
"agentTimestampMs": 1700000000000i64,
+40 -40
View File
@@ -54,8 +54,8 @@ pub struct AcpConnection {
pub rx: AcpClientRx,
/// Available models and current selection.
pub models: ModelState,
/// Whether the agent is a grok-shell instance.
pub is_grok_shell: bool,
/// Whether the agent is a kigi-shell instance.
pub is_kigi_shell: bool,
/// Auth methods advertised by the agent.
pub auth_methods: Vec<acp::AuthMethod>,
/// Cancellation token to stop the agent.
@@ -64,9 +64,9 @@ pub struct AcpConnection {
/// Seeded into every new `AgentSession` so autocomplete has shell builtins
/// and skills immediately, before any `AvailableCommandsUpdate` arrives.
pub available_commands: Vec<acp::AvailableCommand>,
/// Whether interactive login is required (deferred auth for `grok.com`).
/// Whether interactive login is required (deferred auth for `kimi-code`).
pub needs_login: bool,
/// Login button label from `AuthMethod.name` (e.g., "grok.com", "Acme Corp").
/// Login button label from `AuthMethod.name` (e.g., "kimi-code", "Acme Corp").
pub login_label: Option<String>,
/// The auth method ID to use for login (copied from the first advertised method).
pub login_method_id: Option<acp::AuthMethodId>,
@@ -83,7 +83,7 @@ pub struct AcpConnection {
/// resolved by the shell (remote settings / config / env; default OFF) and
/// advertised in `InitializeResponse.meta.sessionRecap`. The client gates
/// its automatic away-recap poll and the manual `/recap` on this so a
/// disabled feature produces zero `x.ai/recap` traffic. Defaults to `false`
/// disabled feature produces zero `kigi/recap` traffic. Defaults to `false`
/// when absent (e.g. an older shell that predates the feature).
pub session_recap_available: bool,
/// `AuthManager` for pager-side authenticated channels.
@@ -183,14 +183,14 @@ pub async fn connect(cancel: &CancellationToken, flags: ConnectFlags) -> Result<
// Spawn the agent
let memory_config = agent_config.memory_config.clone();
let spawned = spawn::spawn_grok_shell(agent_config, cancel, memory_config).await?;
let spawned = spawn::spawn_kigi_shell(agent_config, cancel, memory_config).await?;
let auth_manager = spawned.auth_manager.clone();
let (tx, rx) = (spawned.channel.tx, spawned.channel.rx);
// Initialize
let (
models,
is_grok_shell,
is_kigi_shell,
auth_methods,
default_auth_method_id,
available_commands,
@@ -218,7 +218,7 @@ pub async fn connect(cancel: &CancellationToken, flags: ConnectFlags) -> Result<
tx,
rx,
models,
is_grok_shell,
is_kigi_shell,
auth_methods,
cancel: spawned.cancel,
available_commands,
@@ -291,7 +291,7 @@ pub async fn connect_via_leader(
let (
models,
is_grok_shell,
is_kigi_shell,
auth_methods,
default_auth_method_id,
available_commands,
@@ -329,7 +329,7 @@ pub async fn connect_via_leader(
tx,
rx,
models,
is_grok_shell,
is_kigi_shell,
auth_methods,
cancel: bridge.cancel,
available_commands,
@@ -439,10 +439,10 @@ fn client_capabilities_meta(flags: &ConnectFlags) -> serde_json::Value {
let hunk_mode =
crate::settings::canonical_hunk_tracker_mode(flags.hunk_tracker_mode.as_deref());
serde_json::json!({
"x.ai/incrementalBashOutput": true,
"x.ai/hunkTracker": { "mode": hunk_mode },
"x.ai/bashOutputNoColor": true,
"x.ai/gitHeadChanged": true,
"kigi/incrementalBashOutput": true,
"kigi/hunkTracker": { "mode": hunk_mode },
"kigi/bashOutputNoColor": true,
"kigi/gitHeadChanged": true,
})
}
@@ -482,11 +482,11 @@ async fn initialize(
let resp: acp::InitializeResponse = acp_send(req, tx).await?;
// Check if this is a grok-shell agent
let is_grok_shell = resp
// Check if this is a kigi-shell agent
let is_kigi_shell = resp
.meta
.as_ref()
.and_then(|m| m.get("grokShell"))
.and_then(|m| m.get("kigiShell"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
@@ -514,7 +514,7 @@ async fn initialize(
Ok((
models,
is_grok_shell,
is_kigi_shell,
resp.auth_methods,
default_auth_method_id,
available_commands,
@@ -545,7 +545,7 @@ pub fn parse_session_recap_available(meta: Option<&acp::Meta>) -> bool {
/// Determine whether interactive login is needed based on the advertised auth methods.
///
/// Matches TUI startup behavior: if the first method is `grok.com`, defer auth
/// Matches TUI startup behavior: if the first method is `kimi-code`, defer auth
/// and show the login-aware welcome flow. Otherwise, authenticate eagerly.
///
/// Returns `(needs_login, login_label, login_method_id, auth_start_mode)`.
@@ -589,7 +589,7 @@ pub fn startup_auth_metadata(
///
/// Used when eager auth (cached_token / API key) fails and we need to fall
/// back to the welcome screen with a working login button. Scans the list
/// for a `grok.com` or `oidc` method — these are the ones that can trigger
/// for a `kimi-code` or `oidc` method — these are the ones that can trigger
/// a browser-based re-auth flow.
pub fn find_interactive_login_method(
auth_methods: &[acp::AuthMethod],
@@ -767,7 +767,7 @@ mod tests {
#[test]
fn parse_available_commands_missing_key_returns_empty() {
let meta = serde_json::json!({ "grokShell": true });
let meta = serde_json::json!({ "kigiShell": true });
let cmds = parse_available_commands(meta.as_object());
assert!(cmds.is_empty());
}
@@ -801,7 +801,7 @@ mod tests {
#[test]
fn parse_session_recap_available_defaults_off_when_missing() {
let meta = serde_json::json!({ "grokShell": true, "cancelRewind": true });
let meta = serde_json::json!({ "kigiShell": true, "cancelRewind": true });
assert!(!parse_session_recap_available(meta.as_object()));
assert!(!parse_session_recap_available(None));
}
@@ -832,28 +832,28 @@ mod tests {
}
#[test]
fn startup_auth_grok_com_no_provider_needs_login_pending() {
let methods = vec![make_auth_method("grok.com", "grok.com", None)];
fn startup_auth_kigi_com_no_provider_needs_login_pending() {
let methods = vec![make_auth_method("kimi-code", "kimi-code", None)];
let (needs, label, method_id, mode) = startup_auth_metadata(&methods);
assert!(needs);
assert_eq!(label.as_deref(), Some("grok.com"));
assert_eq!(method_id.as_ref().unwrap().0.as_ref(), "grok.com");
assert_eq!(label.as_deref(), Some("kimi-code"));
assert_eq!(method_id.as_ref().unwrap().0.as_ref(), "kimi-code");
assert_eq!(mode, AuthStartMode::Pending);
}
#[test]
fn startup_auth_grok_com_with_external_provider_command() {
fn startup_auth_kigi_com_with_external_provider_command() {
let meta = serde_json::json!({ "external_provider": true });
let methods = vec![make_auth_method("grok.com", "Acme Corp", Some(meta))];
let methods = vec![make_auth_method("kimi-code", "Acme Corp", Some(meta))];
let (needs, label, method_id, mode) = startup_auth_metadata(&methods);
assert!(needs);
assert_eq!(label.as_deref(), Some("Acme Corp"));
assert_eq!(method_id.as_ref().unwrap().0.as_ref(), "grok.com");
assert_eq!(method_id.as_ref().unwrap().0.as_ref(), "kimi-code");
assert_eq!(mode, AuthStartMode::Command);
}
#[test]
fn startup_auth_non_grok_com_no_login() {
fn startup_auth_non_kigi_com_no_login() {
let methods = vec![make_auth_method("api-key", "API Key", None)];
let (needs, label, method_id, mode) = startup_auth_metadata(&methods);
assert!(!needs);
@@ -890,7 +890,7 @@ mod tests {
// enterprise-style: model has `env_key` set and the env var resolves,
// so the shell-side predicate returns true.
has_external_api_key: true,
// Realistic enterprise user: no cached session token, default `grok.com`
// Realistic enterprise user: no cached session token, default `kimi-code`
// login (no enterprise OIDC).
has_cached_token: false,
login_label: None,
@@ -916,27 +916,27 @@ mod tests {
/// `auth_methods.first()`. This locks the failure mode of the regression:
/// if a future refactor makes the pager scan past `.first()`, this test
/// stops being equivalent to
/// `startup_auth_grok_com_no_provider_needs_login_pending` above and
/// `startup_auth_kigi_com_no_provider_needs_login_pending` above and
/// either passes or fails on a meaningful new code path.
#[test]
fn startup_auth_xai_api_key_not_first_still_requires_login() {
use kigi_shell::agent::auth_method::{KIGI_COM_METHOD_ID, XAI_API_KEY_METHOD_ID};
use kigi_shell::agent::auth_method::{KIMI_CODE_METHOD_ID, XAI_API_KEY_METHOD_ID};
let methods = vec![
make_auth_method(KIGI_COM_METHOD_ID, "Grok", None),
make_auth_method(KIMI_CODE_METHOD_ID, "Kigi", None),
make_auth_method(XAI_API_KEY_METHOD_ID, "xai.api_key", None),
];
let (needs, _, _, _) = startup_auth_metadata(&methods);
assert!(
needs,
"with grok.com first, the pager must require login -- pinning \
"with kimi.com first, the pager must require login -- pinning \
the BAD-ordering failure mode (xai.api_key not first)",
);
}
#[test]
fn startup_auth_method_id_is_copied_not_synthesized() {
let methods = vec![make_auth_method("grok.com", "My Login", None)];
let methods = vec![make_auth_method("kimi-code", "My Login", None)];
let (_, _, method_id, _) = startup_auth_metadata(&methods);
// Verify it's the exact same ID from the method, not hardcoded
assert_eq!(&method_id.unwrap(), methods[0].id());
@@ -945,7 +945,7 @@ mod tests {
#[test]
fn startup_auth_external_provider_false_is_pending() {
let meta = serde_json::json!({ "external_provider": false });
let methods = vec![make_auth_method("grok.com", "grok.com", Some(meta))];
let methods = vec![make_auth_method("kimi-code", "kimi-code", Some(meta))];
let (_, _, _, mode) = startup_auth_metadata(&methods);
assert_eq!(mode, AuthStartMode::Pending);
}
@@ -1033,12 +1033,12 @@ mod tests {
// Rows 1 & 2 of the truth table: nothing set, and a set-but-blank value,
// both advertise the `agent_only` default (never `""` → AllDirty).
let absent = client_capabilities_meta(&ConnectFlags::default());
assert_eq!(absent["x.ai/hunkTracker"]["mode"], "agent_only");
assert_eq!(absent["kigi/hunkTracker"]["mode"], "agent_only");
let blank = client_capabilities_meta(&ConnectFlags {
hunk_tracker_mode: Some(" ".into()),
..Default::default()
});
assert_eq!(blank["x.ai/hunkTracker"]["mode"], "agent_only");
assert_eq!(blank["kigi/hunkTracker"]["mode"], "agent_only");
}
#[test]
@@ -1050,7 +1050,7 @@ mod tests {
hunk_tracker_mode: Some(raw.into()),
..Default::default()
});
assert_eq!(meta["x.ai/hunkTracker"]["mode"], "off", "raw={raw}");
assert_eq!(meta["kigi/hunkTracker"]["mode"], "off", "raw={raw}");
}
}
}
+9 -13
View File
@@ -74,7 +74,7 @@ impl ModelState {
}
}
/// Machine-readable model ID string for the current model (e.g. "grok-4.5").
/// Machine-readable model ID string for the current model (e.g. "kigi-4.5").
pub fn current_model_id_str(&self) -> Option<&str> {
Some(self.current.as_ref()?.0.as_ref())
}
@@ -94,7 +94,7 @@ impl ModelState {
///
/// Honors an explicit `acceptsImages` bool, else an `inputModalities` array
/// containing `"image"`. DEFAULTS TO `true` when neither key is present:
/// correct today (all current Grok models accept images, so nothing is
/// correct today (all current Kigi models accept images, so nothing is
/// suppressed) and forward-compatible (suppresses non-vision models once the
/// ACP server populates the key). Populating that key server-side is a
/// separate change.
@@ -223,7 +223,7 @@ impl ModelState {
/// Map a typed/selected effort token to its canonical value for the current
/// model. Accepts a menu option id (case-insensitive) or a canonical level
/// that appears as a **value** in that model's menu. Levels the model does
/// not offer (e.g. `none` on grok-4.5) are rejected so we fail in the TUI
/// not offer (e.g. `none` on kigi-4.5) are rejected so we fail in the TUI
/// instead of sending a blocked effort to the API.
pub fn resolve_effort_token(&self, token: &str) -> Option<ReasoningEffort> {
match self.current.as_ref() {
@@ -249,7 +249,7 @@ impl ModelState {
}
// Canonical level (e.g. "high", "max"→xhigh) only if the model menu
// actually offers that value — not free-form power-user aliases that
// would 400 on the server (e.g. `none` on grok-4.5).
// would 400 on the server (e.g. `none` on kigi-4.5).
let parsed = token.parse::<ReasoningEffort>().ok()?;
options
.iter()
@@ -410,21 +410,17 @@ mod tests {
#[test]
fn update_catalog_preserves_user_effort_when_model_unchanged() {
let id = acp::ModelId::new(Arc::from("grok-build"));
let id = acp::ModelId::new(Arc::from("kigi"));
let mut state = ModelState::default();
state.available.insert(
id.clone(),
model_with_effort("grok-build", "Grok Build", "high"),
);
state
.available
.insert(id.clone(), model_with_effort("kigi", "Kigi", "high"));
state.set_current(id.clone(), Some(ReasoningEffort::Xhigh));
assert_eq!(state.reasoning_effort, Some(ReasoningEffort::Xhigh));
// The broadcast carries the model's static default (high) for the same model.
let mut refreshed = IndexMap::new();
refreshed.insert(
id.clone(),
model_with_effort("grok-build", "Grok Build", "high"),
);
refreshed.insert(id.clone(), model_with_effort("kigi", "Kigi", "high"));
state.update_catalog(refreshed, Some(id.clone()));
assert_eq!(
+3 -3
View File
@@ -1,6 +1,6 @@
//! Agent spawning — creates the agent process and ACP channels.
//!
//! Simplified to only support GrokShell (in-process) mode.
//! Simplified to only support KigiShell (in-process) mode.
//! Subprocess and remote modes can be added later if needed.
use std::rc::Rc;
@@ -30,10 +30,10 @@ pub struct SpawnedAgent {
pub auth_manager: std::sync::Arc<AuthManager>,
}
/// Spawn a GrokShell agent in a background thread.
/// Spawn a KigiShell agent in a background thread.
///
/// Returns the ACP client channel for communication and a cancellation token.
pub async fn spawn_grok_shell(
pub async fn spawn_kigi_shell(
agent_config: AgentConfig,
cancel: &CancellationToken,
memory_config: Option<kigi_shell::config::MemoryConfig>,
+5 -5
View File
@@ -255,7 +255,7 @@ pub struct AcpUpdateTracker {
/// Tool call IDs marked as background (`is_background=true`).
///
/// First-detection (no scrollback entry yet): defers entry creation until
/// `x.ai/task_backgrounded` creates a `BgTask` block.
/// `kigi/task_backgrounded` creates a `BgTask` block.
/// Late-detection (Execute block already exists): suppresses further output
/// streaming; the existing block is demoted by `handle_task_backgrounded`.
///
@@ -2129,7 +2129,7 @@ fn task_ids_from_raw_input(raw: &serde_json::Value) -> Vec<String> {
}
/// Check if a tool call is a background execute (`is_background=true`).
///
/// These are deferred from scrollback — the `x.ai/task_backgrounded`
/// These are deferred from scrollback — the `kigi/task_backgrounded`
/// notification creates a `BgTask` block instead of an `Execute` block.
///
/// Eager ACP messages often use `kind=Other` with `title=run_terminal_command`
@@ -2602,7 +2602,7 @@ fn make_relative_path(path: &str) -> String {
mod tests {
use super::*;
use std::sync::Arc;
/// Default meta with no timestamps (simulates old grok-shell or tests that
/// Default meta with no timestamps (simulates old kigi-shell or tests that
/// don't care about timing).
fn meta() -> NotificationMeta {
NotificationMeta::default()
@@ -4602,7 +4602,7 @@ mod tests {
"stream A message should be finished"
);
}
/// No stream_start_ms (old grok-shell) should not break anything.
/// No stream_start_ms (old kigi-shell) should not break anything.
#[test]
fn no_stream_start_ms_preserves_existing_behavior() {
let mut sb = ScrollbackState::new();
@@ -5554,7 +5554,7 @@ mod tests {
.status(acp::ToolCallStatus::Pending)
}
#[test]
fn is_task_tool_recognizes_grok_build_variant() {
fn is_task_tool_recognizes_kigi_variant() {
assert!(is_task_tool(&initial_tool_call("tc1", "task")));
let mut with_variant = initial_tool_call("tc2", "anything");
with_variant.raw_input = Some(serde_json::json!({ "variant" : "Task" }));
@@ -62,7 +62,7 @@ pub(super) fn route_bg_task_stdout(
true // Consumed — don't pass to tracker
}
/// Handle `x.ai/task_backgrounded` — a bash command transitioned to background.
/// Handle `kigi/task_backgrounded` — a bash command transitioned to background.
///
/// Creates a `BgTaskState` in the central store and sets up the
/// `tool_call_id → task_id` correlation for stdout routing.
@@ -74,7 +74,7 @@ pub(super) fn route_bg_task_stdout(
pub(super) fn handle_task_backgrounded(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
// Parse the SessionNotification envelope
let Ok(session_notif) = serde_json::from_str::<SessionNotification>(notif.params.get()) else {
tracing::warn!("Failed to parse x.ai/task_backgrounded");
tracing::warn!("Failed to parse kigi/task_backgrounded");
return false;
};
@@ -241,7 +241,7 @@ pub(super) fn handle_task_backgrounded(notif: &acp::ExtNotification, app: &mut A
is_active
}
/// Handle `x.ai/monitor_event` — background task or monitor emitted new output.
/// Handle `kigi/monitor_event` — background task or monitor emitted new output.
pub(super) fn handle_monitor_event(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
let Ok(session_notif) = serde_json::from_str::<SessionNotification>(notif.params.get()) else {
return false;
@@ -414,16 +414,16 @@ pub(super) fn handle_scheduled_task_inject_prompt(
let payload: serde_json::Value = match serde_json::from_str(notif.params.get()) {
Ok(v) => v,
Err(e) => {
tracing::warn!(error = %e, "Failed to parse x.ai/scheduled_task_inject_prompt");
tracing::warn!(error = %e, "Failed to parse kigi/scheduled_task_inject_prompt");
return false;
}
};
let Some(session_id) = payload["sessionId"].as_str() else {
tracing::warn!("x.ai/scheduled_task_inject_prompt: missing or non-string sessionId");
tracing::warn!("kigi/scheduled_task_inject_prompt: missing or non-string sessionId");
return false;
};
let Some(prompt) = payload["prompt"].as_str().filter(|s| !s.is_empty()) else {
tracing::warn!("x.ai/scheduled_task_inject_prompt: missing or empty prompt");
tracing::warn!("kigi/scheduled_task_inject_prompt: missing or empty prompt");
return false;
};
let task_id = payload["taskId"].as_str().unwrap_or("unknown");
@@ -441,7 +441,7 @@ pub(super) fn handle_scheduled_task_inject_prompt(
};
// Only the driver injects + runs the scheduled prompt. In leader mode the
// `x.ai/scheduled_task_inject_prompt` notification is routed by the leader
// `kigi/scheduled_task_inject_prompt` notification is routed by the leader
// to the SINGLE session driver (see `is_scheduled_task_inject_prompt` in
// leader/server.rs), so any client that receives it IS the driver and must
// enqueue + run it — including a client that attached via `session/load`
@@ -551,7 +551,7 @@ pub(super) fn handle_git_head_changed(notif: &acp::ExtNotification, app: &mut Ap
pub(super) fn handle_task_completed(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
// The payload is a SessionNotification wrapping TaskCompleted { task_snapshot }
let Ok(session_notif) = serde_json::from_str::<SessionNotification>(notif.params.get()) else {
tracing::warn!("Failed to parse x.ai/task_completed");
tracing::warn!("Failed to parse kigi/task_completed");
return false;
};
@@ -11,7 +11,7 @@ pub(super) const MAX_FOLLOW_UP_LABEL: usize = 256;
/// retained `follow_up_seen` ring.
pub(super) const MAX_RESPONSE_ID_LEN: usize = 128;
/// Deserialize shape of the `x.ai/follow_ups` params emitted by the shell
/// Deserialize shape of the `kigi/follow_ups` params emitted by the shell
/// translator: `{ response_id, suggestions: [{ label, .. }] }`. The keys are
/// prost-derived snake_case — NOT camelCase like most other pager
/// notification payloads — so this struct must match snake_case verbatim.
@@ -32,13 +32,13 @@ pub(super) struct FollowUpsParams {
prompt_id: Option<String>,
/// Reserved replay marker carrier. Absent in v1 (the shell never sets
/// it); honored from day one so future replay producers need no pager
/// change. Parsed loosely as a JSON value to read the `"x.ai/replayed"`
/// change. Parsed loosely as a JSON value to read the `"kigi/replayed"`
/// key (a slash-bearing key prost cannot model as a field).
#[serde(default, rename = "_meta")]
meta: Option<serde_json::Value>,
}
/// A single `x.ai/follow_ups` suggestion. Only the human-facing `label` is
/// A single `kigi/follow_ups` suggestion. Only the human-facing `label` is
/// consumed; `properties` / `tool_overrides` (also in the wire shape) are
/// ignored.
#[derive(serde::Deserialize)]
@@ -60,11 +60,11 @@ pub(super) fn sanitize_suggestion(label: &str) -> String {
cleaned.trim().to_owned()
}
/// Handle `x.ai/follow_ups` — render follow-up suggestion chips for the
/// Handle `kigi/follow_ups` — render follow-up suggestion chips for the
/// latest assistant response.
///
/// Newest-response-wins keying lives in [`AgentView::apply_follow_ups`]. The
/// reserved `_meta["x.ai/replayed"] == true` marker suppresses rendering (it
/// reserved `_meta["kigi/replayed"] == true` marker suppresses rendering (it
/// is absent today and treated as optional). The params carry no session id,
/// so chips target the active agent; a background agent's follow-ups would
/// mis-route — a forwarding obligation for the shell to add a session id.
@@ -77,7 +77,7 @@ pub(super) fn handle_follow_ups(notif: &acp::ExtNotification, app: &mut AppView)
if params
.meta
.as_ref()
.and_then(|m| m.get("x.ai/replayed"))
.and_then(|m| m.get("kigi/replayed"))
.and_then(|v| v.as_bool())
.unwrap_or(false)
{
@@ -1,6 +1,6 @@
use super::*;
/// Handle `x.ai/ask_user_question` ext-method.
/// Handle `kigi/ask_user_question` ext-method.
///
/// Parses the typed request, creates a `QuestionViewState` with the
/// `response_tx` stashed, and opens the question overlay. The pager does
@@ -14,7 +14,7 @@ pub(crate) fn handle_ask_user_question(
app: &mut AppView,
) -> bool {
use crate::views::question_view::QuestionViewState;
use kigi_tools::implementations::grok_build::ask_user_question::{
use kigi_tools::implementations::kigi::ask_user_question::{
AskUserQuestionExtRequest, AskUserQuestionExtResponse,
};
@@ -119,7 +119,7 @@ pub(crate) fn handle_ask_user_question(
is_active
}
/// Handle an `x.ai/exit_plan_mode` ext_method request.
/// Handle an `kigi/exit_plan_mode` ext_method request.
///
/// Creates a `PlanApprovalViewState` overlay for interactive approval.
///
@@ -46,7 +46,7 @@ pub(super) fn handle_mcp_init_progress(notif: &acp::ExtNotification, app: &mut A
is_active
}
/// Handle `x.ai/mcp/tools_changed` and `x.ai/mcp_initialized`.
/// Handle `kigi/mcp/tools_changed` and `kigi/mcp_initialized`.
///
/// Routing rules (verified against the four shell emit sites in
/// `kigi-shell/src/session/acp_session.rs` — toggle-tool ~L6661,
@@ -78,8 +78,8 @@ pub(super) fn handle_mcp_init_progress(notif: &acp::ExtNotification, app: &mut A
pub(super) fn handle_mcp_tools_changed(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
let method = notif.method.as_ref();
// Both `x.ai/mcp_initialized` and (newer shell)
// `x.ai/mcp/tools_changed` carry `sessionId`. Route by it so a
// Both `kigi/mcp_initialized` and (newer shell)
// `kigi/mcp/tools_changed` carry `sessionId`. Route by it so a
// background agent's notification updates *its* state — not
// whichever agent is foregrounded. Unknown and subagent (child)
// sessions are dropped; a missing sessionId falls back to the
@@ -119,7 +119,7 @@ pub(super) fn handle_mcp_tools_changed(notif: &acp::ExtNotification, app: &mut A
let mut redraw = false;
// `mcp_initialized` clears the matched agent's connecting indicator.
if method == "x.ai/mcp_initialized"
if method == "kigi/mcp_initialized"
&& let Some(agent) = app.agents.get_mut(&id)
&& agent.mcp_init_progress.take().is_some()
{
@@ -163,7 +163,7 @@ pub(super) fn agent_has_pending_mcps_fetch(app: &AppView, agent_id: AgentId) ->
})
}
/// Handle `x.ai/mcp/server_status`.
/// Handle `kigi/mcp/server_status`.
///
/// Routes by the notification's `sessionId` via
/// [`find_session_match`] — the matched agent's extensions modal is
@@ -206,7 +206,7 @@ pub(super) fn handle_mcp_server_status(notif: &acp::ExtNotification, app: &mut A
let Ok(payload) = serde_json::from_str::<McpServerStatusPayload>(notif.params.get()) else {
tracing::warn!(
"Failed to parse x.ai/mcp/server_status: {}",
"Failed to parse kigi/mcp/server_status: {}",
&notif.params.get()
[..crate::render::line_utils::floor_char_boundary(notif.params.get(), 100)]
);
@@ -263,7 +263,7 @@ pub(super) fn handle_mcp_server_status(notif: &acp::ExtNotification, app: &mut A
tracing::warn!(
server = %payload.name,
error = %e,
"x.ai/mcp/server_status: tools field present but not Vec<McpToolEntry>; status still applied"
"kigi/mcp/server_status: tools field present but not Vec<McpToolEntry>; status still applied"
);
None
}
@@ -273,7 +273,7 @@ pub(super) fn handle_mcp_server_status(notif: &acp::ExtNotification, app: &mut A
mutated && is_active
}
/// Handle `x.ai/mcp/servers_updated`.
/// Handle `kigi/mcp/servers_updated`.
///
/// Emitted by the shell from `MvpAgent` on managed-config resolve and
/// on config reload (`crates/codegen/kigi-shell/src/agent/mvp_agent.rs`
@@ -2,8 +2,8 @@
//!
//! Routes incoming [`AcpClientMessage`] notifications to the appropriate
//! agent's tracker, queues permission requests for interactive handling,
//! and xAI session extension notifications (`x.ai/session_notification` and
//! replay-path `x.ai/session/update`).
//! and xAI session extension notifications (`kigi/session_notification` and
//! replay-path `kigi/session/update`).
use std::collections::hash_map::Entry;
use std::path::PathBuf;
@@ -591,39 +591,39 @@ pub(crate) fn handle(msg: AcpClientMessage, app: &mut AppView) -> bool {
/// Handle an xAI extension notification.
///
/// Dispatches on method string:
/// - `x.ai/session_notification` / `x.ai/session/update` → per-agent session updates
/// - `kigi/session_notification` / `kigi/session/update` → per-agent session updates
fn handle_ext_notification(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
match notif.method.as_ref() {
"x.ai/session_notification" | "x.ai/session/update" => {
"kigi/session_notification" | "kigi/session/update" => {
handle_session_notification(notif, app)
}
"x.ai/follow_ups" => handle_follow_ups(notif, app),
"x.ai/task_backgrounded" => handle_task_backgrounded(notif, app),
"x.ai/task_completed" => handle_task_completed(notif, app),
"x.ai/models/update" => handle_models_update(notif, app),
"x.ai/settings/update" => handle_settings_update(notif, app),
"x.ai/sessions/changed" => handle_sessions_changed(notif, app),
"x.ai/queue/changed" => handle_queue_changed(notif, app),
"kigi/follow_ups" => handle_follow_ups(notif, app),
"kigi/task_backgrounded" => handle_task_backgrounded(notif, app),
"kigi/task_completed" => handle_task_completed(notif, app),
"kigi/models/update" => handle_models_update(notif, app),
"kigi/settings/update" => handle_settings_update(notif, app),
"kigi/sessions/changed" => handle_sessions_changed(notif, app),
"kigi/queue/changed" => handle_queue_changed(notif, app),
// TODO(prompt_complete-deprecation): Legacy removal (gated): durable turn_completed is already consumed via finalize_turn_from_terminal; keep & re-point the lost-RPC reconcile to the durable rail before deleting.
"x.ai/session/prompt_complete" => handle_prompt_complete(notif, app),
"x.ai/session/interjection" => handle_interjection(notif, app),
"x.ai/monitor_event" => handle_monitor_event(notif, app),
"x.ai/scheduled_task_created" => handle_scheduled_task_created(notif, app),
"x.ai/scheduled_task_fired" => handle_scheduled_task_fired(notif, app),
"x.ai/scheduled_task_deleted" => handle_scheduled_task_deleted(notif, app),
"x.ai/scheduled_task_inject_prompt" => handle_scheduled_task_inject_prompt(notif, app),
"x.ai/git_head_changed" => handle_git_head_changed(notif, app),
"x.ai/mcp/init_progress" => handle_mcp_init_progress(notif, app),
"x.ai/mcp/tools_changed" | "x.ai/mcp_initialized" => handle_mcp_tools_changed(notif, app),
"x.ai/mcp/server_status" if push_server_status_enabled() => {
"kigi/session/prompt_complete" => handle_prompt_complete(notif, app),
"kigi/session/interjection" => handle_interjection(notif, app),
"kigi/monitor_event" => handle_monitor_event(notif, app),
"kigi/scheduled_task_created" => handle_scheduled_task_created(notif, app),
"kigi/scheduled_task_fired" => handle_scheduled_task_fired(notif, app),
"kigi/scheduled_task_deleted" => handle_scheduled_task_deleted(notif, app),
"kigi/scheduled_task_inject_prompt" => handle_scheduled_task_inject_prompt(notif, app),
"kigi/git_head_changed" => handle_git_head_changed(notif, app),
"kigi/mcp/init_progress" => handle_mcp_init_progress(notif, app),
"kigi/mcp/tools_changed" | "kigi/mcp_initialized" => handle_mcp_tools_changed(notif, app),
"kigi/mcp/server_status" if push_server_status_enabled() => {
handle_mcp_server_status(notif, app)
}
"x.ai/mcp/servers_updated" => handle_mcp_servers_updated(notif, app),
"kigi/mcp/servers_updated" => handle_mcp_servers_updated(notif, app),
_ => false,
}
}
/// Handle `x.ai/session/interjection` — the leader broadcasts this
/// Handle `kigi/session/interjection` — the leader broadcasts this
/// sessionId-bearing notification to every attached client when a mid-turn
/// interjection is queued (emitted from the session actor's `Interject`
/// command handler). Each client renders the interjection as a scrollback
@@ -638,7 +638,7 @@ fn handle_ext_notification(notif: &acp::ExtNotification, app: &mut AppView) -> b
/// renders, so legacy shells degrade to "render everywhere" rather than drop.
fn handle_interjection(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
let Ok(parsed) = serde_json::from_str::<serde_json::Value>(notif.params.get()) else {
tracing::warn!("Failed to parse x.ai/session/interjection");
tracing::warn!("Failed to parse kigi/session/interjection");
return false;
};
let Some(session_id) = parsed.get("sessionId").and_then(|v| v.as_str()) else {
@@ -672,7 +672,7 @@ fn handle_interjection(notif: &acp::ExtNotification, app: &mut AppView) -> bool
// Interjecting into a parked wait continues the turn below this block —
// the withheld "Worked for …" marker must not fire late beneath it
// (shared-queue interjects render only via this broadcast, and the shell
// emits the queue-emptying `x.ai/queue/changed` right after it).
// emits the queue-emptying `kigi/queue/changed` right after it).
agent.suppress_parked_marker_on_interject();
is_active
}
@@ -684,8 +684,8 @@ fn handle_interjection(notif: &acp::ExtNotification, app: &mut AppView) -> bool
/// immediately (for unknown methods).
fn handle_ext_method(ext: kigi_acp_lib::AcpArgs<acp::ExtRequest>, app: &mut AppView) -> bool {
match ext.request.method.as_ref() {
"x.ai/ask_user_question" => handle_ask_user_question(ext, app),
"x.ai/exit_plan_mode" => handle_exit_plan_mode(ext, app),
"kigi/ask_user_question" => handle_ask_user_question(ext, app),
"kigi/exit_plan_mode" => handle_exit_plan_mode(ext, app),
unknown => {
tracing::warn!("Unknown ext_method: {unknown}");
ext.response_tx
@@ -11,7 +11,7 @@ pub(crate) fn is_server_initiated_prompt(prompt_id: &str) -> bool {
/// Cron turns are synthetic (so [`is_server_initiated_prompt`] is also true for
/// them), but UNLIKE auto-wake / subagent-completion turns they are
/// CLIENT-driven via `MvpAgent::prompt()` and therefore DO emit a matching
/// `x.ai/session/prompt_complete` turn-end signal. A viewer can thus safely
/// `kigi/session/prompt_complete` turn-end signal. A viewer can thus safely
/// enter `TurnRunning` for them (the exit exists, so it won't strand) — which is
/// what lets the dashboard show a running `/loop` session as Working.
pub(crate) fn is_scheduler_fired_prompt(prompt_id: &str) -> bool {
@@ -39,7 +39,7 @@ pub(crate) fn is_wake_prompt(prompt_id: &str) -> bool {
/// Whether a running `prompt_id` is adoptable — i.e. safe to bind as the
/// viewer's `current_prompt_id` and show as a live `TurnRunning`. The invariant:
/// adoptable iff the turn emits a terminal `x.ai/session/prompt_complete`, the
/// adoptable iff the turn emits a terminal `kigi/session/prompt_complete`, the
/// only non-interactive way a viewer leaves `TurnRunning`. That holds for
/// user-driven turns and `/loop` (`scheduler-fired-…`) fires — both run via
/// `MvpAgent::prompt()` — and is false for actor-run synthetic turns
@@ -19,7 +19,7 @@ pub(crate) struct PendingRunningAdoption {
pub turn_ended: bool,
}
/// Wire payload of `x.ai/session/prompt_complete`, emitted by
/// Wire payload of `kigi/session/prompt_complete`, emitted by
/// `MvpAgent::prompt()` on the shell after every turn.
///
/// `Serialize` is derived so tests construct payloads through the same type
@@ -62,7 +62,7 @@ pub(super) fn handle_queue_changed(notif: &acp::ExtNotification, app: &mut AppVi
let Ok(changed) =
serde_json::from_str::<crate::app::prompt_queue::QueueChanged>(notif.params.get())
else {
tracing::warn!("Failed to parse x.ai/queue/changed");
tracing::warn!("Failed to parse kigi/queue/changed");
return false;
};
@@ -113,7 +113,7 @@ pub(super) fn handle_queue_changed(notif: &acp::ExtNotification, app: &mut AppVi
local_current_prompt_id = %local_current_prompt_id,
entry_count = changed.entries.len(),
entries = ?recv_entry_ids,
"received x.ai/queue/changed broadcast",
"received kigi/queue/changed broadcast",
);
let rekeyed_echo_ids = app.apply_queue_changed(changed);
@@ -384,7 +384,7 @@ pub(super) fn handle_queue_changed(notif: &acp::ExtNotification, app: &mut AppVi
/// TODO(prompt_complete-deprecation): Legacy removal (gated): durable turn_completed is already consumed via finalize_turn_from_terminal; keep & re-point the lost-RPC reconcile to the durable rail before deleting.
pub(super) fn handle_prompt_complete(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
let Ok(payload) = serde_json::from_str::<PromptCompletePayload>(notif.params.get()) else {
tracing::warn!("Failed to parse x.ai/session/prompt_complete");
tracing::warn!("Failed to parse kigi/session/prompt_complete");
return false;
};
let session_id = payload.session_id.as_str();
@@ -101,7 +101,7 @@ pub(super) fn advance_reconnect_cursor(agent: &mut AgentView, meta: &mut Notific
agent.last_seen_event_id = Some(id);
}
}
/// Handle `x.ai/session_notification` and replay-path `x.ai/session/update`.
/// Handle `kigi/session_notification` and replay-path `kigi/session/update`.
///
/// Routes by `session_id` so events for an inactive agent still mutate that
/// agent's state. The redraw decision is gated on whether the matched agent
@@ -133,7 +133,7 @@ pub(super) fn handle_session_notification(notif: &acp::ExtNotification, app: &mu
tracing::debug!(
session_id = session_notif.session_id.0.as_ref(),
method = notif.method.as_ref(),
"load-race: x.ai/session_notification DROPPED — no agent matches session_id"
"load-race: kigi/session_notification DROPPED — no agent matches session_id"
);
return false;
}
@@ -159,7 +159,7 @@ pub(super) fn handle_session_notification(notif: &acp::ExtNotification, app: &mu
agent,
&meta,
session_notif.session_id.0.as_ref(),
"x.ai/session/update",
"kigi/session/update",
) {
return false;
}
@@ -174,7 +174,7 @@ pub(super) fn handle_session_notification(notif: &acp::ExtNotification, app: &mu
session_id = session_notif.session_id.0.as_ref(),
event_seq = meta.event_seq,
last_applied = agent.last_applied_xai_event_seq,
"x.ai/session update DROPPED by dedup highwater (event_seq <= last_applied)"
"kigi/session update DROPPED by dedup highwater (event_seq <= last_applied)"
);
return false;
}
@@ -1,14 +1,14 @@
use super::*;
use serde::Deserialize;
/// Handle `x.ai/models/update` — model list changed (etag-triggered refresh).
/// Handle `kigi/models/update` — model list changed (etag-triggered refresh).
pub(super) fn handle_models_update(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
if let Ok(model_state) = serde_json::from_str::<acp::SessionModelState>(notif.params.get()) {
use crate::acp::model_state::ModelState;
let new_models = ModelState::from(Some(model_state));
tracing::info!(
count = new_models.available.len(),
"models updated via x.ai/models/update"
"models updated via kigi/models/update"
);
let shell_fallback_current = new_models.current.clone();
@@ -46,15 +46,15 @@ pub(super) fn handle_models_update(notif: &acp::ExtNotification, app: &mut AppVi
}
true
} else {
tracing::warn!("Failed to parse x.ai/models/update");
tracing::warn!("Failed to parse kigi/models/update");
false
}
}
/// Handle `x.ai/settings/update` — remote settings refreshed on `/new`.
/// Handle `kigi/settings/update` — remote settings refreshed on `/new`.
pub(super) fn handle_settings_update(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
let Ok(update) = serde_json::from_str::<PagerSettingsUpdate>(notif.params.get()) else {
tracing::warn!("Failed to parse x.ai/settings/update");
tracing::warn!("Failed to parse kigi/settings/update");
return false;
};
@@ -221,7 +221,7 @@ pub(super) fn handle_settings_update(notif: &acp::ExtNotification, app: &mut App
}
}
tracing::info!("settings updated via x.ai/settings/update");
tracing::info!("settings updated via kigi/settings/update");
true
}
@@ -249,7 +249,7 @@ pub(super) fn apply_soft_default_permission_mode(
}
/// Tell live sessions to leave Auto on the mid-session kill-switch: fire the
/// `x.ai/yolo_mode_changed` notification the agent maps to
/// `kigi/yolo_mode_changed` notification the agent maps to
/// `SetAutoMode { enabled: false }`, fire-and-forget over the shared ACP channel.
/// The notification is CLIENT-scoped (the agent applies it to every session of
/// the sending client), so one send covers all affected sessions. `yolo_mode` is
@@ -264,7 +264,7 @@ pub(super) fn notify_sessions_leave_auto(app: &AppView, session_ids: &[acp::Sess
"permission_mode": "ask",
});
let notification = acp::ExtNotification::new(
"x.ai/yolo_mode_changed",
"kigi/yolo_mode_changed",
serde_json::value::to_raw_value(&params)
.expect("serialize yolo_mode_changed params")
.into(),
@@ -277,12 +277,12 @@ pub(super) fn notify_sessions_leave_auto(app: &AppView, session_ids: &[acp::Sess
let _ = app.acp_tx.send(args.into());
}
/// Handle `x.ai/sessions/changed` — the leader broadcasts roster
/// Handle `kigi/sessions/changed` — the leader broadcasts roster
/// upserts/removals to all clients (FleetView dashboard).
pub(super) fn handle_sessions_changed(notif: &acp::ExtNotification, app: &mut AppView) -> bool {
let Ok(changed) = serde_json::from_str::<crate::app::roster::RosterChanged>(notif.params.get())
else {
tracing::warn!("Failed to parse x.ai/sessions/changed");
tracing::warn!("Failed to parse kigi/sessions/changed");
return false;
};
let mut affected = false;
@@ -297,7 +297,7 @@ pub(super) fn handle_sessions_changed(notif: &acp::ExtNotification, app: &mut Ap
affected
}
/// Deserialization type for the `x.ai/settings/update` notification payload.
/// Deserialization type for the `kigi/settings/update` notification payload.
///
/// This is intentionally a separate struct from `SettingsUpdateNotification` in
/// `kigi-shell/src/agent/mvp_agent.rs`. The shell side derives `Serialize`
@@ -98,6 +98,6 @@ pub(crate) fn finalize_killed_subagent(
let Ok(params) = serde_json::value::to_raw_value(&payload) else {
return false;
};
let notif = acp::ExtNotification::new("x.ai/session/update", params.into());
let notif = acp::ExtNotification::new("kigi/session/update", params.into());
handle_ext_notification(&notif, app)
}
@@ -2,7 +2,7 @@
use super::*;
/// Regression (resume sync): the on-disk replay stream re-emits persisted
/// notifications through the generic `x.ai/session/update` envelope. A
/// notifications through the generic `kigi/session/update` envelope. A
/// background `monitor`/bash task (`TaskBackgrounded`) must restore into
/// `bg_tasks` on a resumed / second terminal — not be dropped by the
/// default match arm — so the idle "watching" status line and the Tasks pane
@@ -24,7 +24,7 @@
description: None,
};
handle(
make_ext_session_notification_with_method("sess-1", "x.ai/session/update", update),
make_ext_session_notification_with_method("sess-1", "kigi/session/update", update),
&mut app,
);
@@ -51,7 +51,7 @@
handle(
make_ext_session_notification_with_method(
"sess-1",
"x.ai/session/update",
"kigi/session/update",
XaiSessionUpdate::ScheduledTaskCreated {
task_id: "loop-1".into(),
prompt: "check deploy".into(),
@@ -72,7 +72,7 @@
handle(
make_ext_session_notification_with_method(
"sess-1",
"x.ai/session/update",
"kigi/session/update",
XaiSessionUpdate::ScheduledTaskDeleted {
task_id: "loop-1".into(),
},
@@ -177,7 +177,7 @@
meta: None,
};
let raw = serde_json::value::to_raw_value(&notif).unwrap();
let notif = acp::ExtNotification::new("x.ai/task_backgrounded", raw.into());
let notif = acp::ExtNotification::new("kigi/task_backgrounded", raw.into());
assert!(handle_task_backgrounded(&notif, &mut app));
let agent = app.agents.get(&AgentId(0)).unwrap();
@@ -232,7 +232,7 @@
meta: None,
};
let raw = serde_json::value::to_raw_value(&notif).unwrap();
let notif = acp::ExtNotification::new("x.ai/task_backgrounded", raw.into());
let notif = acp::ExtNotification::new("kigi/task_backgrounded", raw.into());
assert!(handle_task_backgrounded(&notif, &mut app));
let agent = app.agents.get(&AgentId(0)).unwrap();
@@ -81,10 +81,10 @@
let params = serde_json::json!({
"response_id": "resp-1",
"suggestions": [{ "label": "x" }],
"_meta": { "x.ai/replayed": true },
"_meta": { "kigi/replayed": true },
});
let notif = acp::ExtNotification::new(
"x.ai/follow_ups",
"kigi/follow_ups",
serde_json::value::to_raw_value(&params).unwrap().into(),
);
let affected = handle_ext_notification(&notif, &mut app);
@@ -103,7 +103,7 @@
];
for params in bad {
let notif = acp::ExtNotification::new(
"x.ai/follow_ups",
"kigi/follow_ups",
serde_json::value::to_raw_value(&params).unwrap().into(),
);
let affected = handle_ext_notification(&notif, &mut app);
@@ -214,10 +214,10 @@
let params = serde_json::json!({
"response_id": "resp-1",
"suggestions": [{ "label": "x" }],
"_meta": { "x.ai/replayed": false },
"_meta": { "kigi/replayed": false },
});
let notif = acp::ExtNotification::new(
"x.ai/follow_ups",
"kigi/follow_ups",
serde_json::value::to_raw_value(&params).unwrap().into(),
);
assert!(
@@ -235,7 +235,7 @@
serde_json::json!({ "response_id": "r", "suggestions": [null] }),
] {
let notif = acp::ExtNotification::new(
"x.ai/follow_ups",
"kigi/follow_ups",
serde_json::value::to_raw_value(&bad).unwrap().into(),
);
assert!(
@@ -38,7 +38,7 @@
"token_baseline": 100,
"finished_subagent_tokens": 99,
"live_subagent_tokens": 4_321,
"live_tokens_by_model": [["grok-4", 6_000], ["grok-3", 4_000]],
"live_tokens_by_model": [["kigi-4", 6_000], ["kigi-3", 4_000]],
"live_context_pct": 42,
"live_turn_count": 7,
"live_tool_call_count": 11,
@@ -54,7 +54,7 @@
}
});
let raw = serde_json::value::to_raw_value(&raw_payload).unwrap();
let request = acp::ExtNotification::new("x.ai/session_notification", raw.into());
let request = acp::ExtNotification::new("kigi/session_notification", raw.into());
let (tx, _rx) = tokio::sync::oneshot::channel();
let msg = AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
request,
@@ -94,7 +94,7 @@
assert_eq!(goal.live_subagent_tokens, Some(4_321));
assert_eq!(
goal.live_tokens_by_model,
vec![("grok-4".to_owned(), 6_000), ("grok-3".to_owned(), 4_000)],
vec![("kigi-4".to_owned(), 6_000), ("kigi-3".to_owned(), 4_000)],
"populated per-model breakdown must round-trip wire->display"
);
assert_eq!(goal.live_context_pct, Some(42));
@@ -147,7 +147,7 @@
let (tx, _rx) = tokio::sync::oneshot::channel();
handle(
AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
request: acp::ExtNotification::new("x.ai/session_notification", raw.into()),
request: acp::ExtNotification::new("kigi/session_notification", raw.into()),
response_tx: tx,
}),
app,
@@ -372,7 +372,7 @@
}
});
let raw = serde_json::value::to_raw_value(&raw_payload).unwrap();
let request = acp::ExtNotification::new("x.ai/session_notification", raw.into());
let request = acp::ExtNotification::new("kigi/session_notification", raw.into());
let (tx, _rx) = tokio::sync::oneshot::channel();
let msg = AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
request,
@@ -127,7 +127,7 @@
}))
.unwrap();
let msg = AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
request: acp::ExtRequest::new("x.ai/ask_user_question", raw.into()),
request: acp::ExtRequest::new("kigi/ask_user_question", raw.into()),
response_tx: tx,
});
@@ -168,7 +168,7 @@
}))
.unwrap();
let msg = AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
request: acp::ExtRequest::new("x.ai/ask_user_question", raw.into()),
request: acp::ExtRequest::new("kigi/ask_user_question", raw.into()),
response_tx: tx,
});
@@ -270,7 +270,7 @@
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
handle(
AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
response_tx: tx,
}),
&mut app,
@@ -317,7 +317,7 @@
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
handle(
AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
response_tx: tx,
}),
&mut app,
@@ -355,7 +355,7 @@
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
handle(
AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
response_tx: tx,
}),
&mut app,
@@ -2,7 +2,7 @@
use super::*;
/// Regression: a shared-queue interjection renders only via the broadcast,
/// and the shell emits the queue-emptying `x.ai/queue/changed` right after
/// and the shell emits the queue-emptying `kigi/queue/changed` right after
/// it — which used to fire the withheld parked marker BELOW the just-
/// rendered user message ("Worked for …" under the follow-up, flipped
/// transcript order). The broadcast must consume the marker slot instead.
@@ -680,7 +680,7 @@
);
}
/// `x.ai/task_backgrounded` arriving after the skipped zero-work wait
/// `kigi/task_backgrounded` arriving after the skipped zero-work wait
/// re-evaluates and restores the park.
#[test]
fn task_backgrounded_after_zero_work_wait_all_restores_park() {
@@ -716,7 +716,7 @@
#[test]
fn interjection_notification_pushes_block_to_matching_session() {
// Multi-client fix: an interjection typed in one pane is broadcast by
// the shell as x.ai/session/interjection; EVERY attached pane (incl.
// the shell as kigi/session/interjection; EVERY attached pane (incl.
// the originator, which no longer pushes a local block) renders it.
let mut app = make_app_with_agent("sess-view");
let affected =
@@ -117,7 +117,7 @@
#[test]
fn mcp_initialized_clears_progress() {
// x.ai/mcp_initialized must set mcp_init_progress to None.
// kigi/mcp_initialized must set mcp_init_progress to None.
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
agent.mcp_init_progress = Some(crate::app::agent_view::McpInitProgress {
@@ -408,7 +408,7 @@
// NB: no `status`.
});
let raw = serde_json::value::to_raw_value(&payload).unwrap();
let notif = acp::ExtNotification::new("x.ai/mcp/server_status", raw.into());
let notif = acp::ExtNotification::new("kigi/mcp/server_status", raw.into());
let redraw = handle_mcp_server_status(&notif, &mut app);
assert!(!redraw, "malformed payload must not request a redraw");
@@ -151,7 +151,7 @@ pub(super) fn interjection_broadcast(
text: &str,
) -> acp::ExtNotification {
acp::ExtNotification::new(
"x.ai/session/interjection",
"kigi/session/interjection",
std::sync::Arc::from(
serde_json::value::to_raw_value(
&serde_json::json!({ "sessionId" : session_id, "text" : text, }),
@@ -233,7 +233,7 @@ pub(super) fn follow_ups_ext(
{ "response_id" : response_id, "suggestions" : suggestions, }
);
acp::ExtNotification::new(
"x.ai/follow_ups",
"kigi/follow_ups",
std::sync::Arc::from(serde_json::value::to_raw_value(&params).unwrap()),
)
}
@@ -251,7 +251,7 @@ pub(super) fn follow_ups_ext_with_prompt(
suggestions, }
);
acp::ExtNotification::new(
"x.ai/follow_ups",
"kigi/follow_ups",
std::sync::Arc::from(serde_json::value::to_raw_value(&params).unwrap()),
)
}
@@ -263,7 +263,7 @@ pub(super) fn group_tool_verbs_settings_update(
None => serde_json::json!({}),
};
acp::ExtNotification::new(
"x.ai/settings/update",
"kigi/settings/update",
std::sync::Arc::from(serde_json::value::to_raw_value(&params).unwrap()),
)
}
@@ -275,7 +275,7 @@ pub(super) fn collapsed_edit_blocks_settings_update(
None => serde_json::json!({}),
};
acp::ExtNotification::new(
"x.ai/settings/update",
"kigi/settings/update",
std::sync::Arc::from(serde_json::value::to_raw_value(&params).unwrap()),
)
}
@@ -289,7 +289,7 @@ pub(super) fn subagent_ext_replay(
"eventId" : event_id }, }
);
acp::ExtNotification::new(
"x.ai/session/update",
"kigi/session/update",
std::sync::Arc::from(serde_json::value::to_raw_value(&params).unwrap()),
)
}
@@ -315,7 +315,7 @@ pub(super) fn make_exit_plan_ext_with_tool_call_id(
),
)
.unwrap();
let request = acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into());
let request = acp::ExtRequest::new("kigi/exit_plan_mode", raw.into());
let (tx, rx) = tokio::sync::oneshot::channel();
(
kigi_acp_lib::AcpArgs {
@@ -359,11 +359,11 @@ pub(super) fn queue_changed_ext(session_id: &str, ids: &[&str]) -> acp::ExtNotif
.collect();
let params = serde_json::json!({ "sessionId" : session_id, "entries" : entries });
acp::ExtNotification::new(
"x.ai/queue/changed",
"kigi/queue/changed",
std::sync::Arc::from(serde_json::value::to_raw_value(&params).unwrap()),
)
}
/// Build a `x.ai/queue/changed` notification carrying `runningPromptId`.
/// Build a `kigi/queue/changed` notification carrying `runningPromptId`.
pub(super) fn queue_changed_running(
session_id: &str,
ids: &[&str],
@@ -386,7 +386,7 @@ pub(super) fn queue_changed_running(
params["runningPromptId"] = serde_json::Value::String(r.to_string());
}
acp::ExtNotification::new(
"x.ai/queue/changed",
"kigi/queue/changed",
std::sync::Arc::from(serde_json::value::to_raw_value(&params).unwrap()),
)
}
@@ -469,7 +469,7 @@ pub(super) fn tool_call_block_count(agent: &AgentView) -> usize {
pub(super) fn make_inject_notif(payload: &serde_json::Value) -> acp::ExtNotification {
let raw = serde_json::value::to_raw_value(payload).unwrap();
acp::ExtNotification::new(
"x.ai/scheduled_task_inject_prompt",
"kigi/scheduled_task_inject_prompt",
std::sync::Arc::from(raw),
)
}
@@ -491,7 +491,7 @@ pub(super) fn make_fired_notif(
meta: None,
};
let raw = serde_json::value::to_raw_value(&notif).unwrap();
acp::ExtNotification::new("x.ai/scheduled_task_fired", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/scheduled_task_fired", std::sync::Arc::from(raw))
}
/// Set up an app with two agents; the active view points to agent 1, but
/// agent 0 owns the scheduled task. Handlers that gate on `active_view`
@@ -531,7 +531,7 @@ pub(super) fn make_created_ext_notif(
meta: None,
};
let raw = serde_json::value::to_raw_value(&notif).unwrap();
acp::ExtNotification::new("x.ai/scheduled_task_created", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/scheduled_task_created", std::sync::Arc::from(raw))
}
pub(super) fn make_deleted_ext_notif(
session_id: &str,
@@ -545,7 +545,7 @@ pub(super) fn make_deleted_ext_notif(
meta: None,
};
let raw = serde_json::value::to_raw_value(&notif).unwrap();
acp::ExtNotification::new("x.ai/scheduled_task_deleted", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/scheduled_task_deleted", std::sync::Arc::from(raw))
}
pub(super) fn make_token_notification_message(
session_id: &str,
@@ -697,7 +697,7 @@ pub(super) fn xai_model_switch_notif(
meta: Some(serde_json::json!({ "eventId" : event_id })),
};
acp::ExtNotification::new(
"x.ai/session/update",
"kigi/session/update",
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
)
}
@@ -711,7 +711,7 @@ pub(super) fn xai_unhandled_notif(
meta: Some(serde_json::json!({ "eventId" : event_id })),
};
acp::ExtNotification::new(
"x.ai/session/update",
"kigi/session/update",
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
)
}
@@ -741,19 +741,19 @@ pub(super) fn make_token_notification_with_event(
response_tx: tx,
})
}
/// Build an `x.ai/session/prompt_complete` ext-notification for `session_id`.
/// Build an `kigi/session/prompt_complete` ext-notification for `session_id`.
pub(super) fn prompt_complete_ext(session_id: &str) -> acp::ExtNotification {
let raw = serde_json::value::to_raw_value(
&serde_json::json!({ "sessionId" : session_id, "stopReason" : "end_turn", }),
)
.unwrap();
acp::ExtNotification::new("x.ai/session/prompt_complete", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/session/prompt_complete", std::sync::Arc::from(raw))
}
/// Insert a fresh agent at `id` with an optional pre-assigned session id.
pub(super) fn insert_agent(app: &mut AppView, id: AgentId, session_id: Option<&str>) {
app.agents.insert(id, make_agent(session_id));
}
/// Build an `x.ai/session/prompt_complete` ext-notification with an explicit
/// Build an `kigi/session/prompt_complete` ext-notification with an explicit
/// `stopReason` and optional `agentResult`.
pub(super) fn prompt_complete_ext_with_reason(
session_id: &str,
@@ -767,9 +767,9 @@ pub(super) fn prompt_complete_ext_with_reason(
payload["agentResult"] = serde_json::json!(r);
}
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("x.ai/session/prompt_complete", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/session/prompt_complete", std::sync::Arc::from(raw))
}
/// Build an `x.ai/session/prompt_complete` ext-notification carrying a
/// Build an `kigi/session/prompt_complete` ext-notification carrying a
/// `promptId` (shells with the lost-response fix). Built through the
/// typed [`PromptCompletePayload`] so the test wire shape can never
/// drift from what `handle_prompt_complete` parses.
@@ -789,7 +789,7 @@ pub(super) fn prompt_complete_ext_with_prompt_id(
},
)
.unwrap();
acp::ExtNotification::new("x.ai/session/prompt_complete", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/session/prompt_complete", std::sync::Arc::from(raw))
}
/// Build a live `AgentMessageChunk` whose meta carries `promptId` plus a
/// `turnStartMs` `start_ms_ago` milliseconds in the past — drives the viewer
@@ -822,7 +822,7 @@ pub(super) fn make_viewer_chunk_with_turn_start(
response_tx: tx,
})
}
/// Build a durable `TurnCompleted` update on the `x.ai/session/update` rail,
/// Build a durable `TurnCompleted` update on the `kigi/session/update` rail,
/// optionally stamped `isReplay`. Built through the typed `SessionNotification`
/// so the wire shape can't drift from what the dispatch parses.
pub(super) fn xai_turn_completed_notif(
@@ -842,7 +842,7 @@ pub(super) fn xai_turn_completed_notif(
meta: Some(serde_json::json!({ "isReplay" : is_replay })),
};
acp::ExtNotification::new(
"x.ai/session/update",
"kigi/session/update",
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
)
}
@@ -868,7 +868,7 @@ pub(super) fn xai_wake_turn_completed_notif(
meta: Some(meta),
};
acp::ExtNotification::new(
"x.ai/session/update",
"kigi/session/update",
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
)
}
@@ -885,7 +885,7 @@ pub(super) fn last_marker_block(
.expect("a turn-end marker must exist")
}
/// Build a `HookExecution` update (one successful run) on the
/// `x.ai/session/update` rail, optionally stamped `isReplay`.
/// `kigi/session/update` rail, optionally stamped `isReplay`.
/// `prompt_id == None` models pre-attribution shells.
pub(super) fn xai_hook_execution_notif_for_prompt(
session_id: &str,
@@ -908,7 +908,7 @@ pub(super) fn xai_hook_execution_notif_for_prompt(
meta: Some(serde_json::json!({ "isReplay" : is_replay })),
};
acp::ExtNotification::new(
"x.ai/session/update",
"kigi/session/update",
serde_json::value::to_raw_value(&payload).unwrap().into(),
)
}
@@ -970,11 +970,11 @@ pub(super) fn seed_two_bg_tasks_and_announce(app: &mut AppView, session_id: &str
);
app.agents.get_mut(&AgentId(0)).unwrap().end_work_announced = true;
}
/// Build an `x.ai/session/interjection` ext-notification (no id).
/// Build an `kigi/session/interjection` ext-notification (no id).
pub(super) fn interjection_ext(session_id: &str, text: &str) -> acp::ExtNotification {
interjection_ext_with_id(session_id, text, None)
}
/// Build an `x.ai/session/interjection` ext-notification with an optional
/// Build an `kigi/session/interjection` ext-notification with an optional
/// `interjectionId` (the originator-dedup key).
pub(super) fn interjection_ext_with_id(
session_id: &str,
@@ -986,7 +986,7 @@ pub(super) fn interjection_ext_with_id(
payload["interjectionId"] = serde_json::json!(id);
}
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("x.ai/session/interjection", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/session/interjection", std::sync::Arc::from(raw))
}
/// Text of the most recent user prompt block in scrollback, if any.
/// Interjections render as standard user prompt blocks.
@@ -1090,14 +1090,14 @@ pub(super) fn make_bash_stdout_message(
response_tx: tx,
})
}
/// Build an `ExtNotification` envelope for `x.ai/session_notification`.
/// Build an `ExtNotification` envelope for `kigi/session_notification`.
pub(super) fn make_ext_session_notification(
session_id: &str,
update: XaiSessionUpdate,
) -> AcpClientMessage {
make_ext_session_notification_with_method(
session_id,
"x.ai/session_notification",
"kigi/session_notification",
update,
)
}
@@ -1279,7 +1279,7 @@ pub(super) fn replay_disk_test_home() -> &'static std::path::Path {
})
.path()
}
/// Runs `f` with a thread-local grok home override so disk replay tests do not
/// Runs `f` with a thread-local kigi home override so disk replay tests do not
/// depend on process-wide `kigi_home()` cache order when the full suite runs.
pub(super) fn with_replay_disk_home<R>(f: impl FnOnce(&std::path::Path) -> R) -> R {
let home = replay_disk_test_home();
@@ -1389,7 +1389,7 @@ pub(super) fn spawn_subagent_with_optional_updates(
let _ = handle(
make_ext_session_notification_with_method(
"sess-parent",
"x.ai/session/update",
"kigi/session/update",
test_subagent_spawned("sess-parent", child_sid),
),
app,
@@ -1421,7 +1421,7 @@ pub(super) fn dispatch_goal_update(
let (tx, _rx) = tokio::sync::oneshot::channel();
handle(
AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
request: acp::ExtNotification::new("x.ai/session_notification", raw.into()),
request: acp::ExtNotification::new("kigi/session_notification", raw.into()),
response_tx: tx,
}),
app,
@@ -1464,7 +1464,7 @@ pub(super) fn make_permission_message(
});
(msg, rx)
}
/// Build an `x.ai/session_notification` carrying
/// Build an `kigi/session_notification` carrying
/// `InteractionResolved{tool_call_id}` (the first-answer-wins broadcast that
/// tells every other pane to retract its shared interaction modal).
pub(super) fn interaction_resolved_ext(
@@ -1479,7 +1479,7 @@ pub(super) fn interaction_resolved_ext(
meta: None,
};
let raw = serde_json::value::to_raw_value(&notif).unwrap();
acp::ExtNotification::new("x.ai/session_notification", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/session_notification", std::sync::Arc::from(raw))
}
pub(super) fn make_git_head_changed_notif(
session_id: &str,
@@ -1494,7 +1494,7 @@ pub(super) fn make_git_head_changed_notif(
main_repo: main_repo.map(str::to_string),
};
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("x.ai/git_head_changed", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/git_head_changed", std::sync::Arc::from(raw))
}
pub(super) fn make_task_backgrounded_notif(
session_id: &str,
@@ -1516,7 +1516,7 @@ pub(super) fn make_task_backgrounded_notif(
meta: None,
};
let raw = serde_json::value::to_raw_value(&notif).unwrap();
acp::ExtNotification::new("x.ai/task_backgrounded", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/task_backgrounded", std::sync::Arc::from(raw))
}
/// Like [`make_task_backgrounded_notif`] but stamped `_meta.isReplay:
/// true` via the typed [`ReplayMetaStamp`](crate::acp::meta::ReplayMetaStamp),
@@ -1541,7 +1541,7 @@ pub(super) fn make_replayed_task_backgrounded_notif(
meta: Some(crate::acp::meta::ReplayMetaStamp::replayed()),
};
let raw = serde_json::value::to_raw_value(&notif).unwrap();
acp::ExtNotification::new("x.ai/session/update", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/session/update", std::sync::Arc::from(raw))
}
/// Register a pending Execute tool call in the tracker and send an InProgress
/// update to create the scrollback entry. Returns the agent for further use.
@@ -1670,7 +1670,7 @@ pub(super) fn task_completed_notif(
meta: None,
};
let raw = serde_json::value::to_raw_value(&notif).unwrap();
acp::ExtNotification::new("x.ai/task_completed", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/task_completed", std::sync::Arc::from(raw))
}
pub(super) fn make_monitor_event_notif(
session_id: &str,
@@ -1687,7 +1687,7 @@ pub(super) fn make_monitor_event_notif(
meta: None,
};
let raw = serde_json::value::to_raw_value(&notif).unwrap();
acp::ExtNotification::new("x.ai/monitor_event", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/monitor_event", std::sync::Arc::from(raw))
}
pub(super) fn make_model_info(id: &str) -> acp::ModelInfo {
acp::ModelInfo::new(acp::ModelId::new(std::sync::Arc::from(id)), id.to_string())
@@ -1705,9 +1705,9 @@ pub(super) fn make_models_update_notif(
models,
);
let raw = serde_json::value::to_raw_value(&state).unwrap();
acp::ExtNotification::new("x.ai/models/update", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/models/update", std::sync::Arc::from(raw))
}
/// `x.ai/models/update` carrying a single reasoning-capable model whose
/// `kigi/models/update` carrying a single reasoning-capable model whose
/// catalog-default effort is `default_effort` (what the broadcast reports
/// for every client — never the per-session selection).
pub(super) fn make_reasoning_models_update_notif(
@@ -1725,7 +1725,7 @@ pub(super) fn make_reasoning_models_update_notif(
vec![info],
);
let raw = serde_json::value::to_raw_value(&state).unwrap();
acp::ExtNotification::new("x.ai/models/update", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/models/update", std::sync::Arc::from(raw))
}
/// Seed a session's model catalog with the given ids and mark
/// `current_model_id` as the active one (must be in the list). Used by
@@ -1754,7 +1754,7 @@ pub(super) fn model_changed_ext(
meta: None,
};
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("x.ai/session_notification", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/session_notification", std::sync::Arc::from(raw))
}
pub(super) fn model_changed_ext_with_event(
session_id: &str,
@@ -1770,7 +1770,7 @@ pub(super) fn model_changed_ext_with_event(
meta: Some(serde_json::json!({ "eventId" : event_id })),
};
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("x.ai/session_notification", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/session_notification", std::sync::Arc::from(raw))
}
pub(super) fn make_tool_call_update(title: &str) -> acp::SessionUpdate {
acp::SessionUpdate::ToolCallUpdate(
@@ -1796,7 +1796,7 @@ pub(super) fn make_current_mode_update(mode_id: &str) -> acp::SessionUpdate {
acp::CurrentModeUpdate::new(acp::SessionModeId::new(mode_id)),
)
}
/// Helper: build an `x.ai/mcp/init_progress` notification.
/// Helper: build an `kigi/mcp/init_progress` notification.
pub(super) fn make_mcp_init_progress_notif(
total: u32,
connected: u32,
@@ -1805,7 +1805,7 @@ pub(super) fn make_mcp_init_progress_notif(
&serde_json::json!({ "total" : total, "connected" : connected, }),
)
.unwrap();
acp::ExtNotification::new("x.ai/mcp/init_progress", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/mcp/init_progress", std::sync::Arc::from(raw))
}
pub(super) fn make_mcps_modal_with_servers(
servers: Vec<crate::views::mcps_modal::McpServerInfo>,
@@ -1854,7 +1854,7 @@ pub(super) fn make_server_status_notif(
tools,
};
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("x.ai/mcp/server_status", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/mcp/server_status", std::sync::Arc::from(raw))
}
/// `mcp/servers_updated` real wire shape — `{ mcpServers: [...] }`
/// with NO `sessionId`. Regression guard: anything that tries to
@@ -1863,7 +1863,7 @@ pub(super) fn make_server_status_notif(
pub(super) fn make_servers_updated_notif() -> acp::ExtNotification {
let payload = serde_json::json!({ "mcpServers" : [] });
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("x.ai/mcp/servers_updated", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/mcp/servers_updated", std::sync::Arc::from(raw))
}
/// Real post-handshake / auth-recovery wire shape:
/// `McpToolsChanged { sessionId, serverName, tools }`.
@@ -1872,19 +1872,19 @@ pub(super) fn make_tools_changed_notif_post_h2(
) -> acp::ExtNotification {
let payload = kigi_shell::extensions::mcp::McpToolsChanged {
session_id: session_id.to_string(),
server_name: "grok_com_linear".to_string(),
server_name: "kigi_com_linear".to_string(),
tools: Vec::new(),
};
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("x.ai/mcp/tools_changed", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/mcp/tools_changed", std::sync::Arc::from(raw))
}
/// Legacy / forward-compat wire shape: older shells emit
/// `{ serverName, tools }` with NO sessionId. The pager must fall
/// back to active_view for this shape.
pub(super) fn make_tools_changed_notif_pre_h2() -> acp::ExtNotification {
let payload = serde_json::json!({ "serverName" : "grok_com_linear", "tools" : [] });
let payload = serde_json::json!({ "serverName" : "kigi_com_linear", "tools" : [] });
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("x.ai/mcp/tools_changed", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/mcp/tools_changed", std::sync::Arc::from(raw))
}
/// Real `mcp_initialized` wire shape:
/// `{ sessionId, mcpToolCount, elapsedMs }`.
@@ -1893,7 +1893,7 @@ pub(super) fn make_mcp_initialized_notif(session_id: &str) -> acp::ExtNotificati
{ "sessionId" : session_id, "mcpToolCount" : 12_u64, "elapsedMs" : 250_u64, }
);
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("x.ai/mcp_initialized", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/mcp_initialized", std::sync::Arc::from(raw))
}
/// Helper: `init_progress` notification carrying an explicit sessionId.
pub(super) fn make_mcp_init_progress_notif_for(
@@ -1907,7 +1907,7 @@ pub(super) fn make_mcp_init_progress_notif_for(
),
)
.unwrap();
acp::ExtNotification::new("x.ai/mcp/init_progress", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/mcp/init_progress", std::sync::Arc::from(raw))
}
/// Helper: `mcp_initialized` notification for a specific sessionId.
pub(super) fn make_mcp_initialized_notif_for(session_id: &str) -> acp::ExtNotification {
@@ -1917,7 +1917,7 @@ pub(super) fn make_mcp_initialized_notif_for(session_id: &str) -> acp::ExtNotifi
),
)
.unwrap();
acp::ExtNotification::new("x.ai/mcp_initialized", std::sync::Arc::from(raw))
acp::ExtNotification::new("kigi/mcp_initialized", std::sync::Arc::from(raw))
}
mod permissions;
mod session_events;
@@ -1,7 +1,7 @@
#![cfg_attr(rustfmt, rustfmt::skip)]
use super::*;
/// Regression: a machine-wide `x.ai/models/update` broadcast
/// Regression: a machine-wide `kigi/models/update` broadcast
/// carries each model's static catalog-default effort (`high`), not the
/// session's chosen `xhigh`, and must not clobber the per-session choice.
#[test]
@@ -44,20 +44,20 @@
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
let id_3 = acp::ModelId::new(std::sync::Arc::from("grok-3"));
let id_3 = acp::ModelId::new(std::sync::Arc::from("kigi-3"));
agent
.session
.models
.available
.insert(id_3.clone(), make_model_info("grok-3"));
.insert(id_3.clone(), make_model_info("kigi-3"));
agent.session.models.current = Some(id_3);
let notif = make_models_update_notif("grok-4", &["grok-3", "grok-4"]);
let notif = make_models_update_notif("kigi-4", &["kigi-3", "kigi-4"]);
handle_models_update(&notif, &mut app);
assert_eq!(
app.models.current.as_ref().map(|id| id.0.as_ref()),
Some("grok-3"),
Some("kigi-3"),
"app.models.current must preserve active agent's model, not remote settings default"
);
@@ -69,7 +69,7 @@
.current
.as_ref()
.map(|id| id.0.as_ref()),
Some("grok-3"),
Some("kigi-3"),
"agent's per-session model must be preserved"
);
}
@@ -79,21 +79,21 @@
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
let id_3 = acp::ModelId::new(std::sync::Arc::from("grok-3"));
let id_3 = acp::ModelId::new(std::sync::Arc::from("kigi-3"));
agent
.session
.models
.available
.insert(id_3.clone(), make_model_info("grok-3"));
.insert(id_3.clone(), make_model_info("kigi-3"));
agent.session.models.current = Some(id_3);
// grok-3 removed from catalog.
let notif = make_models_update_notif("grok-4.3", &["grok-4.3", "grok-4.5"]);
// kigi-3 removed from catalog.
let notif = make_models_update_notif("kigi-4.3", &["kigi-4.3", "kigi-4.5"]);
handle_models_update(&notif, &mut app);
assert_eq!(
app.models.current.as_ref().map(|id| id.0.as_ref()),
Some("grok-4.3"),
Some("kigi-4.3"),
"app.models.current must use shell default when agent model removed"
);
@@ -105,7 +105,7 @@
.current
.as_ref()
.map(|id| id.0.as_ref()),
Some("grok-4.3"),
Some("kigi-4.3"),
"agent must fall back to shell default when its model is removed"
);
}
@@ -115,12 +115,12 @@
let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
let mut app = AppView::new(tx, ModelState::default(), Vec::new());
let notif = make_models_update_notif("grok-4", &["grok-3", "grok-4"]);
let notif = make_models_update_notif("kigi-4", &["kigi-3", "kigi-4"]);
handle_models_update(&notif, &mut app);
assert_eq!(
app.models.current.as_ref().map(|id| id.0.as_ref()),
Some("grok-4"),
Some("kigi-4"),
"without an active agent, shell default must be used"
);
}
@@ -130,21 +130,21 @@
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
let id_4 = acp::ModelId::new(std::sync::Arc::from("grok-4"));
let id_4 = acp::ModelId::new(std::sync::Arc::from("kigi-4"));
agent
.session
.models
.available
.insert(id_4.clone(), make_model_info("grok-4"));
.insert(id_4.clone(), make_model_info("kigi-4"));
agent.session.models.current = Some(id_4);
let notif = make_models_update_notif("grok-4", &["grok-3", "grok-4"]);
let notif = make_models_update_notif("kigi-4", &["kigi-3", "kigi-4"]);
handle_models_update(&notif, &mut app);
assert_eq!(
app.models.current.as_ref().map(|id| id.0.as_ref()),
Some("grok-4"),
"app.models.current must be grok-4 when agent and shell agree"
Some("kigi-4"),
"app.models.current must be kigi-4 when agent and shell agree"
);
let agent = app.agents.get(&AgentId(0)).unwrap();
assert_eq!(
@@ -154,8 +154,8 @@
.current
.as_ref()
.map(|id| id.0.as_ref()),
Some("grok-4"),
"agent model must remain grok-4"
Some("kigi-4"),
"agent model must remain kigi-4"
);
}
@@ -166,33 +166,33 @@
{
let agent_a = app.agents.get_mut(&AgentId(0)).unwrap();
let id_3 = acp::ModelId::new(std::sync::Arc::from("grok-3"));
let id_3 = acp::ModelId::new(std::sync::Arc::from("kigi-3"));
agent_a
.session
.models
.available
.insert(id_3.clone(), make_model_info("grok-3"));
.insert(id_3.clone(), make_model_info("kigi-3"));
agent_a.session.models.current = Some(id_3);
}
{
let agent_b = app.agents.get_mut(&AgentId(1)).unwrap();
let id_5 = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let id_5 = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
agent_b
.session
.models
.available
.insert(id_5.clone(), make_model_info("grok-4.5"));
.insert(id_5.clone(), make_model_info("kigi-4.5"));
agent_b.session.models.current = Some(id_5);
}
// grok-5 removed from catalog.
let notif = make_models_update_notif("grok-4", &["grok-3", "grok-4"]);
// kigi-5 removed from catalog.
let notif = make_models_update_notif("kigi-4", &["kigi-3", "kigi-4"]);
handle_models_update(&notif, &mut app);
assert_eq!(
app.models.current.as_ref().map(|id| id.0.as_ref()),
Some("grok-3"),
Some("kigi-3"),
);
let agent_a = app.agents.get(&AgentId(0)).unwrap();
assert_eq!(
@@ -202,11 +202,11 @@
.current
.as_ref()
.map(|id| id.0.as_ref()),
Some("grok-3"),
Some("kigi-3"),
"agent A's model must be preserved"
);
// B's grok-5 was removed — must fall back to shell's grok-4, not A's grok-3.
// B's kigi-5 was removed — must fall back to shell's kigi-4, not A's kigi-3.
let agent_b = app.agents.get(&AgentId(1)).unwrap();
assert_eq!(
agent_b
@@ -215,7 +215,7 @@
.current
.as_ref()
.map(|id| id.0.as_ref()),
Some("grok-4"),
Some("kigi-4"),
"inactive agent must fall back to shell default, not active agent's model"
);
}
@@ -228,12 +228,12 @@
fn model_changed_updates_state_silently_on_follower() {
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
let scrollback_before = agent.scrollback.len();
// Follower: no local switch in flight.
assert!(!agent.session.model_switch_pending);
let notif = model_changed_ext("sess-1", "grok-4", None);
let notif = model_changed_ext("sess-1", "kigi-4", None);
let changed = handle_ext_notification(&notif, &mut app);
assert!(
changed,
@@ -248,7 +248,7 @@
.current
.as_ref()
.map(|id| id.0.as_ref()),
Some("grok-4"),
Some("kigi-4"),
"follower must mirror the remote switch into its local model state",
);
assert_eq!(
@@ -325,13 +325,13 @@
fn model_changed_skipped_when_local_switch_in_flight() {
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
// Invoker: a local switch is in flight (set by Action::SwitchModel /
// set_default_model before the SetSessionModelRequest is sent).
agent.session.model_switch_pending = true;
let scrollback_before = agent.scrollback.len();
let notif = model_changed_ext("sess-1", "grok-4", None);
let notif = model_changed_ext("sess-1", "kigi-4", None);
let changed = handle_ext_notification(&notif, &mut app);
assert!(
!changed,
@@ -346,7 +346,7 @@
.current
.as_ref()
.map(|id| id.0.as_ref()),
Some("grok-3"),
Some("kigi-3"),
"models.current must stay at the pre-response snapshot — \
SwitchModelComplete owns the final apply + system message"
);
@@ -370,9 +370,9 @@
fn model_changed_dropped_when_model_unknown_to_catalog() {
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
let notif = model_changed_ext("sess-1", "grok-99-unknown", None);
let notif = model_changed_ext("sess-1", "kigi-99-unknown", None);
let changed = handle_ext_notification(&notif, &mut app);
assert!(
!changed,
@@ -387,7 +387,7 @@
.current
.as_ref()
.map(|id| id.0.as_ref()),
Some("grok-3"),
Some("kigi-3"),
"models.current must stay on the previously-known model"
);
}
@@ -395,15 +395,15 @@
/// `reasoning_effort` round-trips through the broadcast: the follower
/// applies it alongside the model id so the prompt header / status bar
/// show the right effort without waiting for a subsequent
/// `x.ai/models/update`.
/// `kigi/models/update`.
#[test]
fn model_changed_applies_reasoning_effort_on_follower() {
use kigi_shell::sampling::types::ReasoningEffort;
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
let notif = model_changed_ext("sess-1", "grok-4", Some("high"));
let notif = model_changed_ext("sess-1", "kigi-4", Some("high"));
assert!(handle_ext_notification(&notif, &mut app));
let agent = app.agents.get(&AgentId(0)).unwrap();
@@ -423,9 +423,9 @@
fn model_changed_dropped_for_unknown_session_id() {
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
let notif = model_changed_ext("sess-OTHER", "grok-4", None);
let notif = model_changed_ext("sess-OTHER", "kigi-4", None);
let changed = handle_ext_notification(&notif, &mut app);
assert!(!changed);
@@ -437,7 +437,7 @@
.current
.as_ref()
.map(|id| id.0.as_ref()),
Some("grok-3"),
Some("kigi-3"),
"unrelated-session broadcast must not touch this agent's model"
);
}
@@ -213,7 +213,7 @@
};
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
let msg = AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
response_tx: tx,
});
@@ -244,7 +244,7 @@
};
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
let msg = AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
response_tx: tx,
});
@@ -275,7 +275,7 @@
};
let raw = serde_json::value::to_raw_value(&ext_req).unwrap();
let msg = AcpClientMessage::ExtMethod(kigi_acp_lib::AcpArgs {
request: acp::ExtRequest::new("x.ai/exit_plan_mode", raw.into()),
request: acp::ExtRequest::new("kigi/exit_plan_mode", raw.into()),
response_tx: tx,
});
@@ -21,7 +21,7 @@
XaiSessionUpdate::PluginsChanged {
plugins: vec![crate::views::extensions_modal::test_plugin_info(
"user-tool",
Some(kigi_hooks_plugins_types::PluginOrigin::UserGrok),
Some(kigi_hooks_plugins_types::PluginOrigin::UserKigi),
)],
},
),
@@ -60,7 +60,7 @@
plugins: vec![
crate::views::extensions_modal::test_plugin_info(
"user-tool",
Some(kigi_hooks_plugins_types::PluginOrigin::UserGrok),
Some(kigi_hooks_plugins_types::PluginOrigin::UserKigi),
),
crate::views::extensions_modal::test_plugin_info(
"claude-tool",
@@ -103,7 +103,7 @@
XaiSessionUpdate::PluginsChanged {
plugins: vec![crate::views::extensions_modal::test_plugin_info(
"user-tool",
Some(kigi_hooks_plugins_types::PluginOrigin::UserGrok),
Some(kigi_hooks_plugins_types::PluginOrigin::UserKigi),
)],
},
),
@@ -2,7 +2,7 @@
use super::*;
/// The pager reconciles the authoritative shared prompt queue from the
/// `x.ai/queue/changed` broadcast, and an empty broadcast clears it.
/// `kigi/queue/changed` broadcast, and an empty broadcast clears it.
#[test]
fn queue_changed_reconciles_shared_queue() {
let mut app = make_app_with_agent("sess-1");
@@ -144,7 +144,7 @@
params["runningPromptId"] = serde_json::json!(r);
}
acp::ExtNotification::new(
"x.ai/queue/changed",
"kigi/queue/changed",
std::sync::Arc::from(serde_json::value::to_raw_value(&params).unwrap()),
)
}
@@ -336,7 +336,7 @@
serde_json::from_str(&json_str).unwrap();
assert_eq!(mirror.running_prompt_id.as_deref(), Some("prompt-running"));
let notif = acp::ExtNotification::new("x.ai/queue/changed", raw.into());
let notif = acp::ExtNotification::new("kigi/queue/changed", raw.into());
// Case 1: current_prompt_id is None -> adopt it.
let mut app = make_app_with_agent("sess-1");
@@ -415,7 +415,7 @@
/// Regression: when the shell promotes
/// a server-initiated / auto-wake prompt (synthetic id `task-completed-…`,
/// injected when a background task finishes) to the running turn, it
/// broadcasts `x.ai/queue/changed` with `runningPromptId` = that synthetic
/// broadcasts `kigi/queue/changed` with `runningPromptId` = that synthetic
/// id. The pager must NOT adopt it via the turn-start shim: those turns run
/// inside the actor and emit no `prompt_complete` / `PromptResponse`, so
/// `start_turn()` here would strand the pager on "Responding…" forever
@@ -1736,7 +1736,7 @@
"promptId": "p1",
});
let notif = acp::ExtNotification::new(
"x.ai/session/prompt_complete",
"kigi/session/prompt_complete",
serde_json::value::to_raw_value(&params).unwrap().into(),
);
handle_prompt_complete(&notif, &mut app);
@@ -2093,7 +2093,7 @@
fn viewer_does_not_enter_turn_running_for_server_initiated_turn() {
// A server-initiated / auto-wake turn (synthetic prompt id, e.g. a
// background subagent or task completion: `task-completed-…`) runs inside
// the actor and emits NO `x.ai/session/prompt_complete`. If a viewer
// the actor and emits NO `kigi/session/prompt_complete`. If a viewer
// entered TurnRunning for it, nothing would ever finish the turn and the
// viewer would be stuck "Responding…" forever — exactly the bug where one
// dashboard showed "Worked for" while the other was stuck responding.
@@ -2131,7 +2131,7 @@
fn viewer_enters_turn_running_for_scheduler_fired_cron_turn() {
// A `/loop` (scheduled-task) turn has a synthetic `scheduler-fired-…`
// prompt id, but UNLIKE auto-wake turns it is client-driven via
// `MvpAgent::prompt()` and DOES emit `x.ai/session/prompt_complete`. So a
// `MvpAgent::prompt()` and DOES emit `kigi/session/prompt_complete`. So a
// viewer MUST enter TurnRunning for it — otherwise the dashboard's
// locally-tracked row for a running `/loop` session never shows Working.
let mut app = make_app_with_agent("sess-view");
@@ -2173,7 +2173,7 @@
#[test]
fn viewer_prompt_complete_finishes_turn() {
// A viewer in TurnRunning receives x.ai/session/prompt_complete for its
// A viewer in TurnRunning receives kigi/session/prompt_complete for its
// session -> finish_turn: state Idle, current_prompt_id cleared.
let mut app = make_app_with_agent("sess-view");
app.agents.get_mut(&AgentId(0)).unwrap().attached_as_viewer = true;
@@ -802,7 +802,7 @@
meta,
};
acp::ExtNotification::new(
"x.ai/session/update",
"kigi/session/update",
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
)
}
@@ -882,7 +882,7 @@
meta: Some(serde_json::json!({ "isReplay": true, "eventId": "sess-sub-3" })),
};
let notif = acp::ExtNotification::new(
"x.ai/session_notification",
"kigi/session_notification",
serde_json::value::to_raw_value(&payload).unwrap().into(),
);
assert!(handle_ext_notification(&notif, &mut app));
@@ -996,12 +996,12 @@
let id = AgentId(0);
{
let agent = app.agents.get_mut(&id).unwrap();
seed_models(agent, "grok-3", &["grok-3", "grok-4"]);
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
}
// Unknown model → ignored → both markers untouched.
assert!(!handle_ext_notification(
&model_changed_ext_with_event("sess-1", "grok-99-unknown", "sess-1-7"),
&model_changed_ext_with_event("sess-1", "kigi-99-unknown", "sess-1-7"),
&mut app
));
assert_eq!(
@@ -1015,7 +1015,7 @@
// Known model → applied → both markers advance.
assert!(handle_ext_notification(
&model_changed_ext_with_event("sess-1", "grok-4", "sess-1-8"),
&model_changed_ext_with_event("sess-1", "kigi-4", "sess-1-8"),
&mut app
));
assert_eq!(
@@ -51,7 +51,7 @@
#[test]
fn inject_prompt_drives_even_when_attached_as_viewer() {
// The leader routes `x.ai/scheduled_task_inject_prompt` to the SINGLE
// The leader routes `kigi/scheduled_task_inject_prompt` to the SINGLE
// session driver, so any client that receives it IS the driver and must
// enqueue + run it — even one that attached via `session/load`
// (`attached_as_viewer == true`). Previously this handler latched on
@@ -92,7 +92,7 @@
fn inject_prompt_malformed_json_returns_false() {
let mut app = make_app_with_agent("sess-1");
let raw = serde_json::value::to_raw_value(&"not a json object").unwrap();
let notif = acp::ExtNotification::new("x.ai/scheduled_task_inject_prompt", raw.into());
let notif = acp::ExtNotification::new("kigi/scheduled_task_inject_prompt", raw.into());
// The JSON is valid (a string), but sessionId/prompt fields won't exist.
let result = handle_scheduled_task_inject_prompt(&notif, &mut app);
@@ -217,7 +217,7 @@
app.current_ui.permission_mode = Some("ask".into());
let killswitch = acp::ExtNotification::new(
"x.ai/settings/update",
"kigi/settings/update",
serde_json::value::to_raw_value(
&serde_json::json!({ "auto_permission_mode_enabled": false }),
)
@@ -253,7 +253,7 @@
app.agents.get_mut(&AgentId(2)).unwrap().session.yolo_mode = true;
let killswitch = acp::ExtNotification::new(
"x.ai/settings/update",
"kigi/settings/update",
serde_json::value::to_raw_value(
&serde_json::json!({ "auto_permission_mode_enabled": false }),
)
@@ -272,7 +272,7 @@
let mut leave_auto_notifs = 0;
while let Ok(msg) = rx.try_recv() {
if let kigi_acp_lib::AcpAgentMessage::ExtNotification(args) = msg {
if args.request.method.as_ref() != "x.ai/yolo_mode_changed" {
if args.request.method.as_ref() != "kigi/yolo_mode_changed" {
continue;
}
let params: serde_json::Value =
@@ -302,7 +302,7 @@
app.default_yolo = false;
let apply_yolo = acp::ExtNotification::new(
"x.ai/settings/update",
"kigi/settings/update",
serde_json::value::to_raw_value(&serde_json::json!({
"permission_mode": "always-approve",
}))
@@ -334,7 +334,7 @@
app.auto_mode_gate = true;
let unrelated = acp::ExtNotification::new(
"x.ai/settings/update",
"kigi/settings/update",
serde_json::value::to_raw_value(&serde_json::json!({
"show_resolved_model": true,
}))
@@ -372,7 +372,7 @@
app.current_ui.permission_mode = Some("sentinel-not-a-mode".into());
let push = acp::ExtNotification::new(
"x.ai/settings/update",
"kigi/settings/update",
serde_json::value::to_raw_value(&serde_json::json!({
"permission_mode": "always-approve",
}))
@@ -157,7 +157,7 @@
);
}
/// Regression: replay from `updates.jsonl` emits `x.ai/session/update` (not
/// Regression: replay from `updates.jsonl` emits `kigi/session/update` (not
/// `session_notification`). Subagent lifecycle events must still populate
/// `subagent_sessions` and the parent scrollback `SubagentBlock`.
#[test]
@@ -168,7 +168,7 @@
let affected = handle(
make_ext_session_notification_with_method(
"sess-parent",
"x.ai/session/update",
"kigi/session/update",
test_subagent_spawned("sess-parent", child_sid),
),
&mut app,
@@ -204,7 +204,7 @@
let affected = handle(
make_ext_session_notification_with_method(
"sess-parent",
"x.ai/session/update",
"kigi/session/update",
test_subagent_finished(child_sid),
),
&mut app,
@@ -424,7 +424,7 @@
let _ = handle(
make_ext_session_notification_with_method(
"sess-parent",
"x.ai/session/update",
"kigi/session/update",
test_subagent_finished(child_sid),
),
&mut app,
@@ -674,9 +674,9 @@
fn ext_session_notification_and_update_equivalent_for_subagent_spawned() {
let child_sid = "child-equiv";
let (spawn_notif, finish_notif) =
run_subagent_lifecycle_via_method("x.ai/session_notification", child_sid);
run_subagent_lifecycle_via_method("kigi/session_notification", child_sid);
let (spawn_update, finish_update) =
run_subagent_lifecycle_via_method("x.ai/session/update", child_sid);
run_subagent_lifecycle_via_method("kigi/session/update", child_sid);
assert_eq!(spawn_notif.description, spawn_update.description);
assert_eq!(spawn_notif.subagent_type, spawn_update.subagent_type);
@@ -725,7 +725,7 @@
let affected = handle(
make_ext_session_notification_with_method(
"sess-A",
"x.ai/session/update",
"kigi/session/update",
test_subagent_spawned("sess-A", child_sid),
),
&mut app,
@@ -757,7 +757,7 @@
let affected = handle(
make_ext_session_notification_with_method(
"sess-A",
"x.ai/session/update",
"kigi/session/update",
test_subagent_finished(child_sid),
),
&mut app,
@@ -784,7 +784,7 @@
let affected = handle(
make_ext_session_notification_with_method(
"sess-unknown",
"x.ai/session/update",
"kigi/session/update",
test_subagent_spawned("sess-unknown", "child-unknown"),
),
&mut app,
@@ -809,7 +809,7 @@
// Valid JSON but not a SessionNotification — parse must fail quietly.
let raw =
serde_json::value::to_raw_value(&serde_json::json!({"unexpected": true})).unwrap();
let request = acp::ExtNotification::new("x.ai/session/update", raw.into());
let request = acp::ExtNotification::new("kigi/session/update", raw.into());
let msg = AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
request,
response_tx: tx,
@@ -819,7 +819,7 @@
assert!(
!affected,
"malformed x.ai/session/update params must not redraw"
"malformed kigi/session/update params must not redraw"
);
assert!(
app.agents.get(&AgentId(0)).unwrap().scrollback.is_empty(),
@@ -971,7 +971,7 @@
.remove("will_wake")
.expect("the typed builder stamps the field");
let legacy = acp::ExtNotification::new(
"x.ai/task_completed",
"kigi/task_completed",
std::sync::Arc::from(serde_json::value::to_raw_value(&v).unwrap()),
);
let _ = handle_ext_notification(&legacy, &mut app);
+63 -63
View File
@@ -179,23 +179,23 @@ pub enum Action {
/// Try to drain the next queued prompt (after editing completes, etc.).
DrainQueue,
/// Remove a server-authoritative (shared) queued prompt by its stable
/// `prompt_id`. Routed to the agent as `x.ai/queue/remove`;
/// the resulting `x.ai/queue/changed` rebroadcast is the source of truth.
/// `prompt_id`. Routed to the agent as `kigi/queue/remove`;
/// the resulting `kigi/queue/changed` rebroadcast is the source of truth.
QueueRemoveShared {
id: String,
expected_version: u64,
},
/// Reorder the server-authoritative (shared) queued prompts to match
/// `ordered_ids`. Routed as `x.ai/queue/reorder`.
/// `ordered_ids`. Routed as `kigi/queue/reorder`.
QueueReorderShared {
ordered_ids: Vec<String>,
},
/// Clear the caller's server-authoritative (shared) queued prompts.
/// Routed as `x.ai/queue/clear`.
/// Routed as `kigi/queue/clear`.
QueueClearShared,
/// Replace the text of a server-authoritative (shared) queued prompt.
/// Routed to the agent as `x.ai/queue/edit`; the rebroadcast of
/// `x.ai/queue/changed` is the source of truth. Last write wins via the
/// Routed to the agent as `kigi/queue/edit`; the rebroadcast of
/// `kigi/queue/changed` is the source of truth. Last write wins via the
/// session actor's serialized mailbox; no client-side conflict resolution.
QueueEditShared {
id: String,
@@ -203,8 +203,8 @@ pub enum Action {
},
/// Interject a server-authoritative (shared) queued prompt into the running
/// turn: the agent atomically removes it from the queue and
/// merges its text into the in-flight turn. Routed as `x.ai/queue/interject`;
/// the `x.ai/session/interjection` + `x.ai/queue/changed` rebroadcasts are
/// merges its text into the in-flight turn. Routed as `kigi/queue/interject`;
/// the `kigi/session/interjection` + `kigi/queue/changed` rebroadcasts are
/// the source of truth (no optimistic client-side block). Mirrors the local
/// "Send now" / `Ctrl+Enter` path, which uses [`Interject`](Self::Interject)
/// directly because the local queue is client-owned.
@@ -301,7 +301,7 @@ pub enum Action {
/// duration. The dispatch handler renders + writes the file and arms
/// `AppView::pending_pager_path`; the event loop does the suspend/restore.
OpenTranscriptPager,
/// Minimal mode (`grok --minimal`): re-print the most-recently committed
/// Minimal mode (`kigi --minimal`): re-print the most-recently committed
/// folded block (collapsed reasoning / truncated tool output) into native
/// scrollback, fully expanded, below the conversation (design decision K10).
/// Bound to `Ctrl+E` and the `/expand` command. No-op outside minimal mode
@@ -330,12 +330,12 @@ pub enum Action {
ExecuteHooksAction(kigi_hooks_plugins_types::HooksAction),
/// Execute a plugins management action from the modal.
ExecutePluginsAction(kigi_hooks_plugins_types::PluginsAction),
/// Add or update an MCP server via x.ai/mcp/upsert.
/// Add or update an MCP server via kigi/mcp/upsert.
UpsertMcpServer {
name: String,
config: Box<kigi_shell::util::config::McpServerConfig>,
},
/// Delete an MCP server via x.ai/mcp/delete.
/// Delete an MCP server via kigi/mcp/delete.
DeleteMcpServer {
server_name: String,
},
@@ -344,7 +344,7 @@ pub enum Action {
server_name: String,
enabled: bool,
},
/// Toggle a skill enable/disable via x.ai/skills/toggle.
/// Toggle a skill enable/disable via kigi/skills/toggle.
ToggleSkill {
skill_name: String,
enabled: bool,
@@ -373,7 +373,7 @@ pub enum Action {
CancelScheduledTask(String),
/// Demote the currently running execute tool to a background task.
DemoteToBackground,
/// Request current bundle cache status via `x.ai/bundle/status`.
/// Request current bundle cache status via `kigi/bundle/status`.
RequestBundleStatus,
/// View a catalog entry's raw content in the block viewer.
ViewCatalogEntry {
@@ -484,7 +484,7 @@ pub enum Action {
SetContextualHintSendNow(bool),
SetContextualHintSmallScreen(bool),
SetContextualHintWordSelect(bool),
/// Commit the active theme (canonical name, e.g. `"groknight"`, `"auto"`).
/// Commit the active theme (canonical name, e.g. `"kiginight"`, `"auto"`).
SetTheme(String),
/// Commit the theme used when the OS is in dark mode. Only updates
/// the live display when `theme = "auto"` AND system is in dark mode.
@@ -697,7 +697,7 @@ pub enum Action {
},
/// Persist the memory modal fullscreen preference to config.toml.
PersistMemoryFullscreen(bool),
/// Open the Agent Dashboard view (`/dashboard`, `Ctrl+\`, `grok dashboard`).
/// Open the Agent Dashboard view (`/dashboard`, `Ctrl+\`, `kigi dashboard`).
OpenDashboard,
/// Close the dashboard, returning to the previous `ActiveView`.
ExitDashboard,
@@ -913,7 +913,7 @@ pub enum Action {
/// Persist-and-notify semantics for [`Effect::PersistPermissionMode`].
///
/// Both variants write to `~/.kigi/config.toml` and route ACP
/// `x.ai/yolo_mode_changed` notifications. The ACP notification is
/// `kigi/yolo_mode_changed` notifications. The ACP notification is
/// gated on disk-write success when `WithRollback` is used.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PermissionModePersist {
@@ -1352,7 +1352,7 @@ pub enum Effect {
},
/// Fetch session list for the welcome screen session picker.
FetchSessionList {
/// Text search pushed down to `x.ai/session/list` as `query` (chat
/// Text search pushed down to `kigi/session/list` as `query` (chat
/// mode: forwarded to the backend conversations search). `None`
/// fetches the unfiltered list.
query: Option<String>,
@@ -1367,11 +1367,11 @@ pub enum Effect {
/// against the deep-search seq; chat: server refetch against the list seq).
DebounceSessionSearch { query: String, seq: u64 },
/// Fetch the leader session roster (FleetView dashboard) via
/// `x.ai/sessions/list`. Only issued in leader mode while the
/// `kigi/sessions/list`. Only issued in leader mode while the
/// dashboard is open.
FetchRoster,
/// Fetch the local on-disk session list (dormant/idle sessions) for the
/// dashboard via `x.ai/session/list` — the non-leader fallback for the
/// dashboard via `kigi/session/list` — the non-leader fallback for the
/// FleetView roster. Issued while the dashboard is open and NOT in leader
/// mode so the dashboard shows idle sessions instead of being empty.
FetchDashboardSessions,
@@ -1440,7 +1440,7 @@ pub enum Effect {
session_id: acp::SessionId,
task_id: String,
},
/// Cancel a subagent via `x.ai/subagent/cancel`.
/// Cancel a subagent via `kigi/subagent/cancel`.
KillSubagent {
session_id: acp::SessionId,
subagent_id: String,
@@ -1531,31 +1531,31 @@ pub enum Effect {
/// Toggle plan mode — fire-and-forget signal to the shell.
TogglePlanMode { session_id: acp::SessionId },
/// Remove a server-owned queued prompt: fire-and-forget
/// `x.ai/queue/remove`. The agent re-broadcasts the authoritative queue.
/// `kigi/queue/remove`. The agent re-broadcasts the authoritative queue.
QueueRemove {
session_id: acp::SessionId,
id: String,
expected_version: u64,
},
/// Reorder server-owned queued prompts: fire-and-forget `x.ai/queue/reorder`.
/// Reorder server-owned queued prompts: fire-and-forget `kigi/queue/reorder`.
QueueReorder {
session_id: acp::SessionId,
ordered_ids: Vec<String>,
},
/// Clear the caller's server-owned queued prompts: fire-and-forget
/// `x.ai/queue/clear`.
/// `kigi/queue/clear`.
QueueClear { session_id: acp::SessionId },
/// Replace the text of a server-owned queued prompt in place: fire-and-forget
/// `x.ai/queue/edit`. The session actor's serialized mailbox makes this
/// `kigi/queue/edit`. The session actor's serialized mailbox makes this
/// last-writer-wins for concurrent edits; the rebroadcast of
/// `x.ai/queue/changed` is the truth signal.
/// `kigi/queue/changed` is the truth signal.
QueueEdit {
session_id: acp::SessionId,
id: String,
new_text: String,
},
/// Interject a server-owned queued prompt into the running turn:
/// fire-and-forget `x.ai/queue/interject`. The session actor atomically
/// fire-and-forget `kigi/queue/interject`. The session actor atomically
/// removes it from the queue and merges its text into the in-flight turn,
/// then broadcasts both the interjection and the authoritative queue.
/// `new_text` (when `Some`, serialized as `newText`) replaces the stored
@@ -1595,7 +1595,7 @@ pub enum Effect {
cwd: std::path::PathBuf,
session_id: String,
},
/// Resolve the running agent name for a session (`x.ai/session/info`).
/// Resolve the running agent name for a session (`kigi/session/info`).
FetchSessionAgentName {
agent_id: AgentId,
session_id: acp::SessionId,
@@ -1611,24 +1611,24 @@ pub enum Effect {
PollAuthUrl { request_seq: u64 },
/// Submit a manually-pasted auth code (ext request).
SubmitAuthCode { request_seq: u64, code: String },
/// Fetch MCP server list from the shell (x.ai/mcp/list).
/// Fetch MCP server list from the shell (kigi/mcp/list).
FetchMcpsList {
agent_id: AgentId,
session_id: acp::SessionId,
cache: bool,
},
/// Trigger MCP OAuth for a server (x.ai/mcp/auth_trigger).
/// Trigger MCP OAuth for a server (kigi/mcp/auth_trigger).
McpAuthTrigger {
agent_id: AgentId,
session_id: acp::SessionId,
server_name: String,
},
/// Fetch hooks list from the shell (x.ai/hooks/list).
/// Fetch hooks list from the shell (kigi/hooks/list).
FetchHooksList {
agent_id: AgentId,
session_id: acp::SessionId,
},
/// Fetch plugins list from the shell (x.ai/plugins/list).
/// Fetch plugins list from the shell (kigi/plugins/list).
FetchPluginsList {
agent_id: AgentId,
session_id: acp::SessionId,
@@ -1645,39 +1645,39 @@ pub enum Effect {
session_id: acp::SessionId,
action: kigi_hooks_plugins_types::PluginsAction,
},
/// Fetch skills list from the shell (x.ai/skills/list).
/// Fetch skills list from the shell (kigi/skills/list).
FetchSkillsList {
agent_id: AgentId,
session_id: acp::SessionId,
},
/// Toggle a skill via x.ai/skills/toggle (enable/disable without restart).
/// Toggle a skill via kigi/skills/toggle (enable/disable without restart).
ToggleSkill {
agent_id: AgentId,
session_id: acp::SessionId,
skill_name: String,
enabled: bool,
},
/// Upsert an MCP server via x.ai/mcp/upsert.
/// Upsert an MCP server via kigi/mcp/upsert.
UpsertMcpServer {
agent_id: AgentId,
session_id: acp::SessionId,
name: String,
config: Box<kigi_shell::util::config::McpServerConfig>,
},
/// Delete an MCP server via x.ai/mcp/delete.
/// Delete an MCP server via kigi/mcp/delete.
DeleteMcpServer {
agent_id: AgentId,
session_id: acp::SessionId,
server_name: String,
},
/// Live-toggle an MCP server via x.ai/mcp/toggle (no restart needed).
/// Live-toggle an MCP server via kigi/mcp/toggle (no restart needed).
ToggleMcpServer {
agent_id: AgentId,
session_id: acp::SessionId,
server_name: String,
enabled: bool,
},
/// Toggle a single MCP tool via x.ai/mcp/toggle_tool.
/// Toggle a single MCP tool via kigi/mcp/toggle_tool.
ToggleMcpTool {
agent_id: AgentId,
session_id: acp::SessionId,
@@ -1685,20 +1685,20 @@ pub enum Effect {
tool_name: String,
enabled: bool,
},
/// Fetch and display session info via x.ai/session/info.
/// Fetch and display session info via kigi/session/info.
ShowSessionInfo {
agent_id: AgentId,
session_id: acp::SessionId,
show_resolved_model: bool,
},
/// Fetch and display detailed context usage via x.ai/session/info.
/// Fetch and display detailed context usage via kigi/session/info.
ShowContextInfo {
agent_id: AgentId,
session_id: acp::SessionId,
},
/// Fetch current bundle cache status via `x.ai/bundle/status`.
/// Fetch current bundle cache status via `kigi/bundle/status`.
FetchBundleStatus,
/// Fetch a bundled entry's raw content via `x.ai/bundle/entry/get`.
/// Fetch a bundled entry's raw content via `kigi/bundle/entry/get`.
FetchCatalogEntry { kind: String, name: String },
/// Send feedback about the current session (fire-and-forget POST).
SendFeedback {
@@ -1712,7 +1712,7 @@ pub enum Effect {
text: String,
cwd: std::path::PathBuf,
},
/// Send raw note to x.ai/memory/rewrite for LLM-powered reformatting.
/// Send raw note to kigi/memory/rewrite for LLM-powered reformatting.
/// On success, the rewritten text populates the prompt for inline review.
/// On failure, falls back to showing the raw text for review.
RewriteMemoryNote {
@@ -1733,31 +1733,31 @@ pub enum Effect {
agent_id: AgentId,
cwd: std::path::PathBuf,
},
/// Fire a /btw side question via x.ai/btw ext method.
/// Fire a /btw side question via kigi/btw ext method.
SendBtw {
agent_id: AgentId,
session_id: acp::SessionId,
question: String,
},
/// Request a session recap via the x.ai/recap ext method. Fire-and-forget:
/// Request a session recap via the kigi/recap ext method. Fire-and-forget:
/// the recap arrives later as a `SessionRecap` notification.
SendRecap {
session_id: acp::SessionId,
auto: bool,
},
/// Send a mid-turn interjection via x.ai/interject ext method.
/// Send a mid-turn interjection via kigi/interject ext method.
SendInterject {
agent_id: AgentId,
session_id: acp::SessionId,
text: String,
/// Client-minted id echoed back on the `x.ai/session/interjection`
/// Client-minted id echoed back on the `kigi/session/interjection`
/// broadcast so the originator can dedup its optimistic local block.
interjection_id: String,
/// Structured text + image content blocks. `None` for text-only
/// interjections — the wire shape stays byte-identical to legacy.
blocks: Option<Vec<acp::ContentBlock>>,
},
/// Log out via `x.ai/auth/logout` (shell clears auth.json + in-memory state).
/// Log out via `kigi/auth/logout` (shell clears auth.json + in-memory state).
Logout,
/// Log out then authenticate sequentially in one task.
SwitchAccount {
@@ -1785,7 +1785,7 @@ pub enum Effect {
cwd: std::path::PathBuf,
},
/// Delete a session's stored data (local + remote) via
/// `x.ai/session/delete`.
/// `kigi/session/delete`.
DeleteSession {
source: String,
session_id: String,
@@ -1793,7 +1793,7 @@ pub enum Effect {
},
/// Deep-search sessions by content (FTS via ACP).
DeepSearchSessions { query: String, seq: u64 },
/// Call `x.ai/session/fork` to create a peer session that resumes
/// Call `kigi/session/fork` to create a peer session that resumes
/// from `parent_session_id` in the same cwd (no worktree). Mirror of
/// the worktree branch of [`Effect::CreateWorktreeSession`]; the
/// worktree-fork path reuses `CreateWorktreeSession { load_session_id }`
@@ -1833,14 +1833,14 @@ pub enum Effect {
target_prompt_index: usize,
mode: crate::views::rewind::RewindMode,
},
/// Fetch Kimi usage/quota rows from the agent's `x.ai/billing`
/// Fetch Kimi usage/quota rows from the agent's `kigi/billing`
/// extension (`GET {base}/usages` shell-side) for the `/usage` view.
FetchUsage { agent_id: AgentId },
/// Spawn a debounce sleep task for shell suggestions. `agent_id` rides
/// to the expiry so the fetch is built from the arming agent, not
/// whatever view is active when the timer fires.
DebounceSuggestions { agent_id: AgentId, generation: u64 },
/// Send an ACP `x.ai/suggest` request to the shell. `agent_id` is echoed
/// Send an ACP `kigi/suggest` request to the shell. `agent_id` is echoed
/// on the result so the response routes to the agent that fetched, not
/// whatever view is active when it lands.
FetchShellSuggestions {
@@ -1857,13 +1857,13 @@ pub enum Effect {
/// (path/file); the as-you-type surface keeps all of them.
token_only: bool,
},
/// Send an ACP `x.ai/suggestPrompt` request to the shell — predict the
/// Send an ACP `kigi/suggestPrompt` request to the shell — predict the
/// user's likely next prompt after a completed turn (tab autocomplete
/// ghost text).
FetchPromptSuggestion {
agent_id: AgentId,
generation: u64,
/// Suggestion model resolved by the pager (`grok-build-0.1` when the
/// Suggestion model resolved by the pager (`kigi-0.1` when the
/// catalog offers it); `None` = shell falls back to the session model.
model: Option<String>,
session_id: Option<String>,
@@ -1884,7 +1884,7 @@ pub enum Effect {
preparation: crate::prompt_images::PromptImagePreviewPreparation,
},
}
/// Outcome of an `x.ai/subagent/cancel` request, telling dispatch whether the
/// Outcome of an `kigi/subagent/cancel` request, telling dispatch whether the
/// pager must finalize the subagent row itself.
#[derive(Debug)]
pub enum SubagentKillOutcome {
@@ -1951,7 +1951,7 @@ pub enum TaskResult {
restore_summary: Option<String>,
restore_degree: Option<kigi_workspace::session::git::RestoreDegree>,
/// The session's in-flight running prompt id (from the load response
/// `_meta["x.ai/runningPromptId"]`), present only when the session was
/// `_meta["kigi/runningPromptId"]`), present only when the session was
/// loaded MID-turn (another client is driving). The loader adopts it to
/// pass the live `session/update` gate without re-rendering the user
/// block (replay already rendered it).
@@ -2014,7 +2014,7 @@ pub enum TaskResult {
query: String,
seq: u64,
},
/// Leader session roster loaded via `x.ai/sessions/list`.
/// Leader session roster loaded via `kigi/sessions/list`.
RosterLoaded {
sessions: Vec<crate::app::roster::RosterEntry>,
},
@@ -2085,7 +2085,7 @@ pub enum TaskResult {
/// Cancel notification was sent (fire-and-forget).
/// The real turn end comes via PromptResponse.
CancelComplete,
/// Response to `x.ai/subagent/cancel`; see [`SubagentKillOutcome`].
/// Response to `kigi/subagent/cancel`; see [`SubagentKillOutcome`].
KillSubagentComplete {
session_id: acp::SessionId,
subagent_id: String,
@@ -2155,7 +2155,7 @@ pub enum TaskResult {
/// Deprecated: superseded by `mode` (authoritative). Kept only as a
/// back-compat fallback for older agents that don't send `mode`.
external: bool,
/// Presentation mode from `x.ai/auth/get_url`; `None` on older agents.
/// Presentation mode from `kigi/auth/get_url`; `None` on older agents.
mode: Option<String>,
},
/// Auth code was submitted (fire-and-forget).
@@ -2303,7 +2303,7 @@ pub enum TaskResult {
agent_id: AgentId,
result: Result<String, String>,
},
/// `x.ai/recap` request acknowledged (fire-and-forget). The recap itself
/// `kigi/recap` request acknowledged (fire-and-forget). The recap itself
/// arrives separately as a `SessionRecap` notification; this only carries
/// a transport error, if any, for logging.
RecapRequested {
@@ -2342,7 +2342,7 @@ pub enum TaskResult {
results: Vec<kigi_shell::extensions::session_search::SearchSessionHit>,
seq: u64,
},
/// `x.ai/session/fork` completed (no-worktree path). The pager adopts
/// `kigi/session/fork` completed (no-worktree path). The pager adopts
/// the new session id and emits [`Effect::LoadSession`] to start the
/// replay. Mirrors [`TaskResult::WorktreeForked`] in shape.
ForkSessionReady {
@@ -2350,7 +2350,7 @@ pub enum TaskResult {
new_session_id: acp::SessionId,
cwd: std::path::PathBuf,
},
/// `x.ai/session/fork` failed. The placeholder agent stays in
/// `kigi/session/fork` failed. The placeholder agent stays in
/// `app.agents` with no `session_id` so the user can switch away.
ForkSessionFailed {
agent_id: AgentId,
@@ -2393,7 +2393,7 @@ pub enum TaskResult {
agent_id: AgentId,
generation: u64,
},
/// Shell suggestions loaded from ACP `x.ai/suggest`. `request_text` /
/// Shell suggestions loaded from ACP `kigi/suggest`. `request_text` /
/// `request_cursor` echo what the request was built from — the anchor
/// the items' `replaceRange` offsets index into and the position Tab
/// targets, paired atomically with them; `agent_id` routes the landing
@@ -2404,7 +2404,7 @@ pub enum TaskResult {
request_text: String,
request_cursor: usize,
},
/// Predicted next prompt loaded from ACP `x.ai/suggestPrompt`.
/// Predicted next prompt loaded from ACP `kigi/suggestPrompt`.
/// `suggestion` is `None` when the shell had nothing to suggest.
PromptSuggestionLoaded {
agent_id: AgentId,
+3 -3
View File
@@ -133,7 +133,7 @@ pub enum AgentCommand {
RestoreCode,
/// Forking the current session into a peer (no-worktree path).
/// Drives the spinner shown on the placeholder agent while the
/// `x.ai/session/fork` request is in flight.
/// `kigi/session/fork` request is in flight.
ForkSession,
}
impl AgentCommand {
@@ -627,11 +627,11 @@ pub struct AgentSession {
/// `yolo_mode` (yolo wins).
pub(crate) auto_mode: bool,
/// Prompt history for the current session, fetched from ACP
/// (`x.ai/prompt_history` scoped via `filter_session_id`). Most-recent-first.
/// (`kigi/prompt_history` scoped via `filter_session_id`). Most-recent-first.
/// Fetched on session create/load; prompts sent in this session are
/// additionally front-inserted locally on send.
pub prompt_history: Vec<String>,
/// True until the session's startup/load `x.ai/prompt_history` fetch completes.
/// True until the session's startup/load `kigi/prompt_history` fetch completes.
pub prompt_history_loading: bool,
/// Session is currently replaying historical updates from `session/load`.
/// Used to suppress live-style redraw/render work until the load completes.
@@ -52,7 +52,7 @@ impl AgentView {
}
}
/// Apply an `x.ai/follow_ups` notification, keyed by `response_id`
/// Apply an `kigi/follow_ups` notification, keyed by `response_id`
/// (newest-response-wins).
///
/// Monotonic accept-the-newer: a never-seen `response_id` is strictly newer
@@ -78,7 +78,7 @@ impl AgentView {
}
/// `apply_follow_ups` with the turn identity (`prompt_id`) the shell stamps
/// on each `x.ai/follow_ups` notification (the same `promptId` it stamps on
/// on each `kigi/follow_ups` notification (the same `promptId` it stamps on
/// every `session/update`). The identity makes viewer-adoption dedup
/// DETERMINISTIC:
///
@@ -86,7 +86,7 @@ impl AgentView {
/// `prompt_id` equals `session.current_prompt_id`) re-renders even when its
/// chips were cleared by turn adoption — so chips that were applied then
/// cleared reappear instead of being lost until reload.
/// - A buffer-replayed `x.ai/follow_ups` for a PRIOR turn's `response_id`
/// - A buffer-replayed `kigi/follow_ups` for a PRIOR turn's `response_id`
/// stays rejected by the seen-ring (its `prompt_id` is not the active one),
/// so stale chips are never revived on the new turn.
///
@@ -219,7 +219,7 @@ impl AgentView {
true
}
/// Buffer a stamped `x.ai/follow_ups` for a turn that is not yet current,
/// Buffer a stamped `kigi/follow_ups` for a turn that is not yet current,
/// keyed by its `promptId`. A newer delivery for the same `promptId`
/// overwrites the earlier one (keep the latest); the FIFO order list bounds
/// the map to [`MAX_PENDING_FOLLOW_UPS`], evicting only the oldest entry.
@@ -249,7 +249,7 @@ impl AgentView {
}
}
/// Flush a buffered `x.ai/follow_ups` for `prompt_id` (a turn that has just
/// Flush a buffered `kigi/follow_ups` for `prompt_id` (a turn that has just
/// become current). Renders the chips through [`apply_follow_ups_with_prompt`]
/// — now that `current_prompt_id == prompt_id`, the stamped delivery is
/// accepted as the active turn's. Returns whether chips were rendered. A
@@ -292,7 +292,7 @@ impl AgentView {
/// Reload reset that PRESERVES the running turn's follow-ups for
/// `keep_prompt_id` (the turn the load is about to adopt). On `SessionLoaded`
/// the running turn's `x.ai/follow_ups` arrive on the ext channel DURING
/// the running turn's `kigi/follow_ups` arrive on the ext channel DURING
/// `loading_replay`; an unconditional reset would drop them before adoption
/// could re-render them, so the chips would never appear unless the server
/// resent them. The running turn's chips live in ONE of two places at reset
@@ -1107,7 +1107,7 @@ impl AgentView {
self.submit_question_answers(skipped)
}
fn submit_question_answers(&mut self, skipped: bool) -> InputOutcome {
use kigi_tools::implementations::grok_build::ask_user_question::AskUserQuestionExtResponse;
use kigi_tools::implementations::kigi::ask_user_question::AskUserQuestionExtResponse;
self.swap_question_freeform();
let Some(mut qv) = self.question_view.take() else {
return InputOutcome::Changed;
@@ -1594,7 +1594,7 @@ mod permission_scope_key_tests {
#[cfg(test)]
mod question_no_freeform_tests {
//! Freeform ("Other") gating for `no_freeform` question modals — e.g.
//! the SuperGrok upsell. Regression tests for the bug where clicking
//! the subscription upsell. Regression tests for the bug where clicking
//! under the last option of the upsell selected the (hidden) freeform
//! row and let the user type into a modal that offers no free text.
use super::super::test_fixtures::make_agent;
@@ -1605,7 +1605,7 @@ mod question_no_freeform_tests {
use crossterm::event::{
KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
/// Fixed options, single-select — shaped like the free-usage upsell.
@@ -200,7 +200,7 @@ impl McpInitProgress {
/// Whether the progress indicator should be visible in the UI.
///
/// - `total > 0` (real servers): always visible until
/// `x.ai/mcp_initialized` clears the progress.
/// `kigi/mcp_initialized` clears the progress.
/// - `total == 0` (seed / 0-server): visible for at most
/// [`SEED_EXPIRE`] seconds, then auto-expires as
/// defense-in-depth against the shell failing to send
@@ -559,7 +559,7 @@ pub(crate) struct SessionReload {
saw_todo_update: bool,
}
/// Follow-up suggestion chips for the latest assistant response
/// (`x.ai/follow_ups`). Streaming-only: never persisted, does not survive a
/// (`kigi/follow_ups`). Streaming-only: never persisted, does not survive a
/// session reload. Keyed by the assistant `response_id` (the newest-wins key).
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct FollowUps {
@@ -799,7 +799,7 @@ pub struct AgentView {
/// answering questions via `AskUserQuestion`). Reset when the turn ends.
pub turn_paused_duration: std::time::Duration,
/// IDs of interjections this client sent and already rendered locally
/// (optimistic echo). The shell broadcasts `x.ai/session/interjection` to
/// (optimistic echo). The shell broadcasts `kigi/session/interjection` to
/// every attached pane; when our own broadcast echoes back carrying an id
/// in this set, `handle_interjection` drops it (we already showed it) and
/// removes the id. Other panes (which lack the id) render it. This is the
@@ -1133,7 +1133,7 @@ pub struct AgentView {
/// (`When::DashboardOverlay`) are lit in the overlay and dimmed elsewhere.
pub(crate) in_dashboard_overlay: bool,
/// MCP server init progress. Set when the shell starts connecting
/// MCP servers, cleared when `x.ai/mcp_initialized` arrives.
/// MCP servers, cleared when `kigi/mcp_initialized` arrives.
/// Shown in the turn status line while the agent is idle.
pub(crate) mcp_init_progress: Option<McpInitProgress>,
/// Last synced ACP command generation. When this differs from
@@ -1206,7 +1206,7 @@ pub struct AgentView {
/// lands on a tick; borrowed during render so streaming redraws don't
/// rescan/allocate the prompt every frame.
pub(crate) timeline_hover_preview: Option<(usize, String)>,
/// Running agent definition for this session (`x.ai/session/info` `agentName`).
/// Running agent definition for this session (`kigi/session/info` `agentName`).
pub session_agent_name: Option<String>,
/// Map of child session IDs to subagent metadata. Populated on
/// `SubagentSpawned` notifications, used for permission routing
@@ -1286,7 +1286,7 @@ pub struct AgentView {
/// complete. Kind-only: the payload is re-derived from the widget on
/// reissue so the freshly attached image chip travels with it.
pub(crate) deferred_send: Option<AgentDeferredSend>,
/// Armed when an `x.ai/session/prompt_complete` broadcast arrives for the
/// Armed when an `kigi/session/prompt_complete` broadcast arrives for the
/// turn THIS client drives while it is still awaiting that turn's
/// `session/prompt` RPC response. The RPC normally lands milliseconds
/// later and disarms this; if it never does (lost in leader response
@@ -1317,17 +1317,17 @@ pub struct AgentView {
pub(crate) follow_without_jump_prompt_id: Option<String>,
/// Ids of THIS client's server-queue rows that are still optimistic
/// echoes — the `session/prompt` RPC is in flight and no
/// `x.ai/queue/changed` broadcast has confirmed the row yet. Inserted by
/// `kigi/queue/changed` broadcast has confirmed the row yet. Inserted by
/// the echo push, drained when a broadcast lists the id (queued or
/// running) or the RPC resolves without the row landing.
pub(crate) optimistic_queue_ids: std::collections::HashSet<String>,
/// A queue-row send-now the user fired while the row was still an
/// optimistic echo. Firing `x.ai/queue/interject` then would race the
/// optimistic echo. Firing `kigi/queue/interject` then would race the
/// row's own in-flight `session/prompt` and silently no-op shell-side
/// (a rapid double-Enter on a queued bash command could "disappear" — the
/// interject overtook the row, the no-op dropped the send-now, and the
/// armed cancel expectation hid the still-queued row).
/// Parked here and fired from the confirming `x.ai/queue/changed`
/// Parked here and fired from the confirming `kigi/queue/changed`
/// broadcast with the row's authoritative version.
pub(crate) send_now_awaiting_confirm: Option<String>,
/// User blocks painted at send-now dispatch, keyed by prompt id; the
@@ -1337,7 +1337,7 @@ pub struct AgentView {
pub(crate) send_now_painted_blocks:
std::collections::HashMap<String, (crate::scrollback::EntryId, bool)>,
/// Follow-up suggestion chips for the latest assistant response
/// (`x.ai/follow_ups`). `None` when no chips are shown. Set by
/// (`kigi/follow_ups`). `None` when no chips are shown. Set by
/// [`AgentView::apply_follow_ups`]; cleared at each turn start.
pub(crate) follow_ups: Option<FollowUps>,
/// `promptId` (turn identity) of the currently-shown `follow_ups`, when the
@@ -1371,7 +1371,7 @@ pub struct AgentView {
/// The ordering key for newest-wins: a fresh id takes the next value (the
/// new high-water), so every previously-seen id is strictly lower.
pub(crate) follow_up_next_gen: u64,
/// Stamped `x.ai/follow_ups` that arrived for a turn that is NOT yet the
/// Stamped `kigi/follow_ups` that arrived for a turn that is NOT yet the
/// currently-adopted one, keyed by `promptId`. Ext notifications and
/// `session/update` travel on separate channels, so a turn's follow_ups can
/// land BEFORE the `session/update` that adopts it. Rather than drop such a
@@ -1891,7 +1891,7 @@ fn resolve_action(action_id: Option<ActionId>) -> Option<InputOutcome> {
fn question_visible_h(
scroll_region: Option<(u16, u16)>,
prompt_height: u16,
question: &kigi_tools::implementations::grok_build::ask_user_question::Question,
question: &kigi_tools::implementations::kigi::ask_user_question::Question,
content_w: usize,
preview: Option<&str>,
fullscreen: bool,
@@ -2625,7 +2625,7 @@ pub(super) mod test_fixtures {
);
assert_eq!(agent.follow_ups.as_ref().unwrap().suggestions, vec!["a"]);
}
/// FIX 4 (b): after adopting a NEW turn, a buffer-replayed `x.ai/follow_ups`
/// FIX 4 (b): after adopting a NEW turn, a buffer-replayed `kigi/follow_ups`
/// for a PRIOR turn's response_id must NOT revive stale chips — its
/// `promptId` is not the active turn and it is already in the seen ring.
#[test]
@@ -2646,7 +2646,7 @@ pub(super) mod test_fixtures {
assert!(agent.apply_follow_ups_with_prompt("resp-2".into(), Some("p2"), vec!["b".into()]));
assert_eq!(agent.follow_ups.as_ref().unwrap().response_id, "resp-2");
}
/// FINDING B (stamped path): a LATE FIRST-TIME (never-seen) `x.ai/follow_ups`
/// FINDING B (stamped path): a LATE FIRST-TIME (never-seen) `kigi/follow_ups`
/// for a PRIOR turn — arriving while a newer turn is active — must NOT
/// render. Before the fix it slipped through the "strictly newer" branch
/// (never recorded in `follow_up_seen`, so the seen-reject didn't catch it).
@@ -2702,7 +2702,7 @@ pub(super) mod test_fixtures {
/// distinguished from the new turn's first follow_ups, so it follows the
/// legacy newest-wins (renders). This path is not reachable for current
/// shells (which always stamp `promptId`) or for buffer-replays (suppressed
/// upstream by the `_meta["x.ai/replayed"]` gate); it is pinned here so the
/// upstream by the `_meta["kigi/replayed"]` gate); it is pinned here so the
/// stamped-path fix above is understood to be the deterministic guard.
#[test]
fn apply_follow_ups_none_prompt_first_time_follows_legacy_newest_wins() {
@@ -2714,7 +2714,7 @@ pub(super) mod test_fixtures {
);
assert_eq!(agent.follow_ups.as_ref().unwrap().response_id, "resp-x");
}
/// FIX (buffer-before-adoption): a stamped `x.ai/follow_ups` for a turn that
/// FIX (buffer-before-adoption): a stamped `kigi/follow_ups` for a turn that
/// is NOT yet current (its `session/update` adoption raced behind the ext
/// channel) must be BUFFERED, not dropped — and then RENDER when that turn
/// becomes current and is flushed.
@@ -1180,10 +1180,10 @@ pub(super) mod paste_key_tests {
/// Build a `QuestionViewState` already in `InputMode` focus.
pub(in crate::app::agent_view) fn make_question_view_state_in_input_mode()
-> crate::views::question_view::QuestionViewState {
let question = kigi_tools::implementations::grok_build::ask_user_question::Question {
let question = kigi_tools::implementations::kigi::ask_user_question::Question {
question: "Pick one?".to_string(),
options: vec![
kigi_tools::implementations::grok_build::ask_user_question::QuestionOption {
kigi_tools::implementations::kigi::ask_user_question::QuestionOption {
label: "A".to_string(),
description: "Option A".to_string(),
preview: None,
@@ -1412,7 +1412,7 @@ mod prompt_suggestion_key_tests {
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
/// Idle agent with the gate open and a loaded suggestion — the state
/// right after a turn ends with `x.ai/suggestPrompt` resolved. Pins the
/// right after a turn ends with `kigi/suggestPrompt` resolved. Pins the
/// settings cache so `resolve_enabled()` never reads the dev machine's
/// config.toml (thread-local, so per-test).
fn suggestion_agent(text: &str) -> AgentView {
@@ -530,7 +530,7 @@ impl AgentView {
Some(crate::views::queue_pane::QueueRowOrigin::Server)
);
if is_server {
// Server row: the agent promotes it to run next (`x.ai/queue/interject`); any kind may send now.
// Server row: the agent promotes it to run next (`kigi/queue/interject`); any kind may send now.
if let Some(row) = row.as_ref()
&& let Some(server_id) = row.server_id.clone()
{
@@ -538,7 +538,7 @@ impl AgentView {
// flight, so an interject fired now would overtake the row
// shell-side and silently no-op (dropping the send-now and
// hiding the row behind the armed cancel expectation). Park
// the intent; the confirming `x.ai/queue/changed` broadcast
// the intent; the confirming `kigi/queue/changed` broadcast
// fires it with the row's authoritative version (see
// `resolve_send_now_awaiting_confirm`).
if self.optimistic_queue_ids.contains(&server_id) {
@@ -568,13 +568,13 @@ impl AgentView {
}
/// Reconcile this client's optimistic queue echoes against a raw
/// `x.ai/queue/changed` broadcast (pre-merge entries — the mirrored
/// `kigi/queue/changed` broadcast (pre-merge entries — the mirrored
/// snapshot re-pins unconfirmed echoes, so it can't tell confirmation
/// apart), and resolve a parked queue-row send-now
/// ([`Self::send_now_awaiting_confirm`]).
///
/// Returns `Some((id, version))` when the parked row is now confirmed as
/// QUEUED — the caller fires `x.ai/queue/interject` with that
/// QUEUED — the caller fires `kigi/queue/interject` with that
/// authoritative version. A parked row confirmed as RUNNING clears the
/// park with nothing to do (the natural drain won the race). A row in
/// neither set stays parked (its RPC is still in flight).
@@ -715,7 +715,7 @@ impl AgentView {
// Queue-specific actions (delete, edit, reorder). `x`/Delete = row delete.
if let Some(event) = self.queue.handle_key(key, registry) {
// Resolve the selected row's origin so edits route correctly:
// Server-origin rows go to the agent as `x.ai/queue/*`
// Server-origin rows go to the agent as `kigi/queue/*`
// commands (the rebroadcast is the source of truth); Local rows
// keep today's in-place mutation.
let row = self.queue.row_ref(Self::queue_event_id(&event));
@@ -822,7 +822,7 @@ impl AgentView {
}
}
/// Reorder payload for `x.ai/queue/reorder`. Omit only running; include
/// Reorder payload for `kigi/queue/reorder`. Omit only running; include
/// send-now in the list but do not swap past it (shell ranks missing ids last).
fn server_queue_reordered(&self, selection_id: u64, up: bool) -> Option<Vec<String>> {
let server_id = self.queue.row_ref(selection_id)?.server_id?;
@@ -1325,7 +1325,7 @@ mod tests {
crate::scrollback::text_selection::ResolvedSelectionBoundaries::default();
for (entry_idx, text, hit_col, prefix, suffix, expected) in [
(0, "foo rest", 0, " ", "", "foo"),
(1, "rest https://x.ai", 5, "", " ", "https://x.ai"),
(1, "rest https://kimi.com", 5, "", " ", "https://kimi.com"),
] {
let line = ResolvedSelectableLine {
entry_idx,
@@ -397,7 +397,7 @@ impl AgentView {
self.session.start_turn(&mut self.scrollback);
}
/// Adopt the in-flight turn another client is driving, conveyed by the
/// `session/load` response meta (`x.ai/runningPromptId`): enter
/// `session/load` response meta (`kigi/runningPromptId`): enter
/// TurnRunning and match subsequent live deltas. No user-prompt block is
/// pushed — the turn's prompt and prior chunks arrived via the replay.
pub(crate) fn adopt_running_prompt(&mut self, prompt_id: String) {
@@ -506,7 +506,7 @@ mod shell_suggestion_key_tests {
let mut agent = bash_agent("ls | gr");
agent.prompt.suggestions.dropdown.items = vec![
item("grep", None),
file_item("ls | grokfile", "grokfile", 5..7),
file_item("ls | kigifile", "kigifile", 5..7),
];
let outcome = agent.handle_prompt_key_for_test(&key(KeyCode::Tab));
+11 -13
View File
@@ -501,19 +501,19 @@ pub struct AppView {
/// (`team_name.is_some()`) and API-key auth.
pub usage_visible: bool,
/// Whether the pager is connected via a leader (leader mode). The Agent
/// Dashboard entry points (`/dashboard`, `Ctrl+\`, `grok dashboard`, the
/// Dashboard entry points (`/dashboard`, `Ctrl+\`, `kigi dashboard`, the
/// startup hook) are only meaningful when a leader is coordinating a
/// fleet of sessions, so they are gated on this flag. Set in
/// `event_loop::run` from `connection.leader_status_rx.is_some()`;
/// defaults to `false` (non-leader, dashboard hidden).
pub leader_mode: bool,
/// Leader-mode session roster (FleetView dashboard). Populated from
/// `x.ai/sessions/list` polls and `x.ai/sessions/changed` broadcasts.
/// `kigi/sessions/list` polls and `kigi/sessions/changed` broadcasts.
/// Empty in non-leader mode, which naturally gates roster rendering.
pub leader_roster: Vec<crate::app::roster::RosterEntry>,
/// Local on-disk session list (dormant/idle sessions) surfaced on the
/// dashboard when NOT in leader mode. There is no live leader roster to
/// poll outside leader mode, so we fetch the same `x.ai/session/list` the
/// poll outside leader mode, so we fetch the same `kigi/session/list` the
/// resume picker uses and render those as idle rows. Entries are stored as
/// [`crate::app::roster::RosterEntry`] (activity `Dormant`) so they reuse
/// the existing roster-row rendering / attach path. Empty in leader mode.
@@ -521,14 +521,14 @@ pub struct AppView {
/// Whether the dashboard is currently loading local sessions (non-leader mode).
pub dashboard_sessions_loading: bool,
/// Server-authoritative shared prompt queues, keyed by `sessionId`
/// Reconciled from `x.ai/queue/changed` broadcasts so
/// Reconciled from `kigi/queue/changed` broadcasts so
/// every client renders the same ordered queue (including prompts queued
/// by other clients). Empty in non-leader mode.
pub shared_prompt_queues:
std::collections::HashMap<String, Vec<crate::app::prompt_queue::QueueEntryWire>>,
/// Optimistic echo rows for prompts the pager sent server-authoritatively
/// (plain prompt typed while a turn is running) but for which the
/// confirming `x.ai/queue/changed` broadcast has not yet arrived. Keyed by
/// confirming `kigi/queue/changed` broadcast has not yet arrived. Keyed by
/// `sessionId`. Pinned into `shared_prompt_queues` on reconcile so the row
/// doesn't flicker, and dropped once the authoritative broadcast reflects
/// the id (or it starts running). Never persisted.
@@ -551,7 +551,7 @@ pub struct AppView {
pub cancel_rewind_enabled: bool,
/// Whether session recap (`/recap` + automatic away recap) is rolled out,
/// resolved by the shell and advertised on ACP initialize (`sessionRecap`).
/// When false, the pager must not request recaps (zero `x.ai/recap` traffic).
/// When false, the pager must not request recaps (zero `kigi/recap` traffic).
pub session_recap_available: bool,
/// Stateful prompt widget rendered on the welcome screen (persists input across frames).
pub welcome_prompt: PromptWidget,
@@ -715,7 +715,7 @@ pub struct AppView {
/// Automatically enabled by `plan_mode`.
pub ask_user: bool,
/// Process-wide gateway light-frontend from CLI `--chat` only.
/// Stamps `_meta["x.ai/session"].kind = "chat"` and omits Build agent
/// Stamps `_meta["kigi/session"].kind = "chat"` and omits Build agent
/// profiles on create/load while set. `/chat` does **not** set this
/// (uses [`Self::deferred_startup`] one-shot state instead).
pub chat_mode: bool,
@@ -776,7 +776,7 @@ pub struct AppView {
/// when `Pending`, the welcome screen shows the trust question and session
/// creation is deferred (gated after auth) until it is answered.
pub trust_state: TrustState,
/// Login button label from `AuthMethod.name` (e.g., "grok.com", "Acme Corp").
/// Login button label from `AuthMethod.name` (e.g., "kimi-code", "Acme Corp").
pub login_label: Option<String>,
/// The auth method ID to use for login.
pub login_method_id: Option<acp::AuthMethodId>,
@@ -1125,7 +1125,7 @@ impl AppView {
}
}
/// Reconcile the shared prompt queue for a session from a
/// `x.ai/queue/changed` broadcast. The broadcast is
/// `kigi/queue/changed` broadcast. The broadcast is
/// authoritative: it fully replaces the previously-known queue for that
/// session. An empty list clears the entry.
///
@@ -1194,7 +1194,7 @@ impl AppView {
/// Push an optimistic echo row for a server-authoritative prompt the pager
/// just sent (a plain prompt or agent-bound kind typed while a turn is
/// running). The row is keyed by `prompt_id` so the authoritative
/// `x.ai/queue/changed` broadcast replaces it (matched by `id`) rather than
/// `kigi/queue/changed` broadcast replaces it (matched by `id`) rather than
/// duplicating it. `kind` (`"prompt"`/`"bash"`/…) drives the row's display
/// and, on adoption, the turn-start shim's block + focus flag.
pub fn push_optimistic_prompt_echo(
@@ -8264,9 +8264,7 @@ pub(crate) mod tests {
n_questions: usize,
) {
use crate::views::question_view::QuestionViewState;
use kigi_tools::implementations::grok_build::ask_user_question::{
Question, QuestionOption,
};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let questions: Vec<Question> = (0..n_questions)
.map(|i| Question {
question: format!("Q{i}?"),
+4 -4
View File
@@ -1,7 +1,7 @@
//! Bundle status state and response types.
//!
//! Pager-side cache of what `kigi-shell` reports from
//! `x.ai/bundle/status`. The shell now performs the actual bundle download in
//! `kigi/bundle/status`. The shell now performs the actual bundle download in
//! the background post-auth; the pager only reads the resulting on-disk
//! catalog so it can populate the welcome-screen subagent pane.
@@ -9,7 +9,7 @@ use serde::Deserialize;
/// Pager-local snapshot of bundle availability on disk.
///
/// Populated from `x.ai/bundle/status` ACP responses.
/// Populated from `kigi/bundle/status` ACP responses.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct BundleState {
pub has_cache: bool,
@@ -22,7 +22,7 @@ pub struct BundleState {
pub role_details: Vec<RoleDetail>,
}
/// Deserialized response from `x.ai/bundle/status`.
/// Deserialized response from `kigi/bundle/status`.
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BundleStatusResult {
@@ -62,7 +62,7 @@ pub struct RoleDetail {
pub description: String,
}
/// Deserialized response from `x.ai/bundle/entry/get`.
/// Deserialized response from `kigi/bundle/entry/get`.
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EntryGetResult {
+56 -51
View File
@@ -226,13 +226,13 @@ impl AgentArgs {
Ok(canonical) if canonical.is_dir() => Some(canonical),
Ok(_) => {
eprintln!(
"grok: --plugin-dir {}: not a directory; skipping",
"kigi: --plugin-dir {}: not a directory; skipping",
p.display()
);
None
}
Err(e) => {
eprintln!("grok: --plugin-dir {}: {e}; skipping", p.display());
eprintln!("kigi: --plugin-dir {}: {e}; skipping", p.display());
None
}
})
@@ -292,11 +292,16 @@ pub struct LeaderArgs {
fn version_with_channel() -> &'static str {
use std::sync::OnceLock;
static V: OnceLock<String> = OnceLock::new();
// Required upstream attribution for `--version` output (PRD: the
// "Based on … Open Source" note must survive the rebrand). Kept in a
// text asset so the release-gate grep over Rust sources stays clean.
const VERSION_ATTRIBUTION: &str = include_str!("version_attribution.txt");
V.get_or_init(|| {
let label = kigi_update::channel_label();
format!(
"{} — unofficial Kimi Code CLI community build - Based on Grok Build Open Source",
kigi_version::display_version_with_commit(env!("VERSION_WITH_COMMIT"), label)
"{} — unofficial Kimi Code CLI community build - {}",
kigi_version::display_version_with_commit(env!("VERSION_WITH_COMMIT"), label),
VERSION_ATTRIBUTION.trim_end(),
)
})
}
@@ -806,7 +811,7 @@ mod tests {
use super::*;
#[test]
fn version_flag_exits_zero() {
let err = PagerArgs::try_parse_from(["grok", "--version"]).unwrap_err();
let err = PagerArgs::try_parse_from(["kigi", "--version"]).unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
assert!(
err.exit_code() == 0,
@@ -816,7 +821,7 @@ mod tests {
}
#[test]
fn version_short_flag_exits_zero() {
let err = PagerArgs::try_parse_from(["grok", "-v"]).unwrap_err();
let err = PagerArgs::try_parse_from(["kigi", "-v"]).unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
assert!(
err.exit_code() == 0,
@@ -827,35 +832,35 @@ mod tests {
#[test]
fn resume_target_classifies_flags() {
assert_eq!(
PagerArgs::try_parse_from(["grok"]).unwrap().resume_target(),
PagerArgs::try_parse_from(["kigi"]).unwrap().resume_target(),
ResumeTarget::None
);
assert_eq!(
PagerArgs::try_parse_from(["grok", "-c"])
PagerArgs::try_parse_from(["kigi", "-c"])
.unwrap()
.resume_target(),
ResumeTarget::MostRecentForCwd
);
assert_eq!(
PagerArgs::try_parse_from(["grok", "--resume"])
PagerArgs::try_parse_from(["kigi", "--resume"])
.unwrap()
.resume_target(),
ResumeTarget::MostRecentForCwd
);
assert_eq!(
PagerArgs::try_parse_from(["grok", "--resume", "sess-1"])
PagerArgs::try_parse_from(["kigi", "--resume", "sess-1"])
.unwrap()
.resume_target(),
ResumeTarget::SessionId("sess-1".to_string())
);
assert_eq!(
PagerArgs::try_parse_from(["grok", "-s", "sess-2"])
PagerArgs::try_parse_from(["kigi", "-s", "sess-2"])
.unwrap()
.resume_target(),
ResumeTarget::None
);
assert_eq!(
PagerArgs::try_parse_from(["grok", "-r", "old", "--fork-session"])
PagerArgs::try_parse_from(["kigi", "-r", "old", "--fork-session"])
.unwrap()
.resume_target(),
ResumeTarget::SessionId("old".to_string())
@@ -866,11 +871,11 @@ mod tests {
/// invocation would be ambiguous.
#[test]
fn minimal_and_fullscreen_flags_conflict() {
let args = PagerArgs::try_parse_from(["grok", "--minimal"]).unwrap();
let args = PagerArgs::try_parse_from(["kigi", "--minimal"]).unwrap();
assert!(args.minimal && !args.fullscreen);
let args = PagerArgs::try_parse_from(["grok", "--fullscreen"]).unwrap();
let args = PagerArgs::try_parse_from(["kigi", "--fullscreen"]).unwrap();
assert!(args.fullscreen && !args.minimal);
let err = PagerArgs::try_parse_from(["grok", "--minimal", "--fullscreen"]).unwrap_err();
let err = PagerArgs::try_parse_from(["kigi", "--minimal", "--fullscreen"]).unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
}
/// kimi-cli parity (F6): bare `kigi acp` runs the stdio ACP server, and
@@ -906,7 +911,7 @@ mod tests {
std::fs::write(&file, "x").unwrap();
let missing = tmp.path().join("missing");
let args = PagerArgs::try_parse_from([
"grok".as_ref(),
"kigi".as_ref(),
"agent".as_ref(),
"--no-leader".as_ref(),
"--plugin-dir".as_ref(),
@@ -964,19 +969,19 @@ mod tests {
#[test]
fn startup_sandbox_profile_no_resume() {
assert_eq!(
PagerArgs::try_parse_from(["grok", "--sandbox", "strict"])
PagerArgs::try_parse_from(["kigi", "--sandbox", "strict"])
.unwrap()
.startup_sandbox_profile(None),
SandboxStartup::Apply(Some("strict".to_string()))
);
assert_eq!(
PagerArgs::try_parse_from(["grok", "--sandbox", ""])
PagerArgs::try_parse_from(["kigi", "--sandbox", ""])
.unwrap()
.startup_sandbox_profile(None),
SandboxStartup::Apply(None)
);
assert_eq!(
PagerArgs::try_parse_from(["grok"])
PagerArgs::try_parse_from(["kigi"])
.unwrap()
.startup_sandbox_profile(None),
SandboxStartup::Apply(None)
@@ -984,7 +989,7 @@ mod tests {
}
#[test]
fn leader_socket_flag_parses_at_root() {
let args = PagerArgs::try_parse_from(["grok", "--leader-socket", "/tmp/leader-x.sock"])
let args = PagerArgs::try_parse_from(["kigi", "--leader-socket", "/tmp/leader-x.sock"])
.expect("--leader-socket parses at the root");
assert_eq!(
args.leader_socket.as_deref(),
@@ -994,7 +999,7 @@ mod tests {
#[test]
fn leader_socket_flag_is_global_for_subcommands() {
let args = PagerArgs::try_parse_from([
"grok",
"kigi",
"agent",
"leader",
"--leader-socket",
@@ -1008,21 +1013,21 @@ mod tests {
}
#[test]
fn leader_socket_flag_defaults_to_none() {
let args = PagerArgs::try_parse_from(["grok"]).expect("bare grok parses");
let args = PagerArgs::try_parse_from(["kigi"]).expect("bare kigi parses");
assert!(args.leader_socket.is_none());
}
#[test]
fn leader_mgmt_list_info_kill_parse() {
let list = PagerArgs::try_parse_from(["grok", "leader", "list", "--json"])
.expect("grok leader list --json");
let list = PagerArgs::try_parse_from(["kigi", "leader", "list", "--json"])
.expect("kigi leader list --json");
assert!(matches!(
list.command,
Some(Command::Leader(LeaderMgmtArgs {
command: LeaderMgmtCommand::List { json: true },
}))
));
let info = PagerArgs::try_parse_from(["grok", "leader", "info", "--pid", "42"])
.expect("grok leader info --pid");
let info = PagerArgs::try_parse_from(["kigi", "leader", "info", "--pid", "42"])
.expect("kigi leader info --pid");
assert!(matches!(
info.command,
Some(Command::Leader(LeaderMgmtArgs {
@@ -1032,25 +1037,25 @@ mod tests {
},
}))
));
let kill = PagerArgs::try_parse_from(["grok", "leader", "kill"]).expect("grok leader kill");
let kill = PagerArgs::try_parse_from(["kigi", "leader", "kill"]).expect("kigi leader kill");
assert!(matches!(
kill.command,
Some(Command::Leader(LeaderMgmtArgs {
command: LeaderMgmtCommand::Kill,
}))
));
assert!(PagerArgs::try_parse_from(["grok", "leader", "profile"]).is_err());
assert!(PagerArgs::try_parse_from(["kigi", "leader", "profile"]).is_err());
}
#[test]
fn debug_file_flag_parses_and_is_global() {
let root = PagerArgs::try_parse_from(["grok", "--debug-file", "/tmp/fire.txt"])
let root = PagerArgs::try_parse_from(["kigi", "--debug-file", "/tmp/fire.txt"])
.expect("--debug-file parses at the root");
assert_eq!(
root.debug_file.as_deref(),
Some(std::path::Path::new("/tmp/fire.txt"))
);
let sub =
PagerArgs::try_parse_from(["grok", "agent", "stdio", "--debug-file", "/tmp/f.txt"])
PagerArgs::try_parse_from(["kigi", "agent", "stdio", "--debug-file", "/tmp/f.txt"])
.expect("--debug-file parses after a subcommand (global)");
assert_eq!(
sub.debug_file.as_deref(),
@@ -1059,90 +1064,90 @@ mod tests {
}
#[test]
fn debug_file_flag_defaults_to_none() {
let args = PagerArgs::try_parse_from(["grok"]).expect("bare grok parses");
let args = PagerArgs::try_parse_from(["kigi"]).expect("bare kigi parses");
assert!(args.debug_file.is_none());
}
#[test]
fn positional_prompt_seeds_interactive_session() {
let args =
PagerArgs::try_parse_from(["grok", "fix the bug"]).expect("positional prompt parses");
PagerArgs::try_parse_from(["kigi", "fix the bug"]).expect("positional prompt parses");
assert_eq!(args.initial_prompt(), Some("fix the bug"));
assert!(args.command.is_none());
assert!(args.single.is_none());
}
#[test]
fn bare_grok_has_no_initial_prompt() {
let args = PagerArgs::try_parse_from(["grok"]).expect("bare grok parses");
fn bare_kigi_has_no_initial_prompt() {
let args = PagerArgs::try_parse_from(["kigi"]).expect("bare kigi parses");
assert_eq!(args.initial_prompt(), None);
}
#[test]
fn initial_prompt_trims_and_ignores_whitespace_only() {
let args = PagerArgs::try_parse_from(["grok", " spaced "]).expect("padded prompt parses");
let args = PagerArgs::try_parse_from(["kigi", " spaced "]).expect("padded prompt parses");
assert_eq!(args.initial_prompt(), Some("spaced"));
let blank = PagerArgs::try_parse_from(["grok", " "]).expect("blank prompt parses");
let blank = PagerArgs::try_parse_from(["kigi", " "]).expect("blank prompt parses");
assert_eq!(blank.initial_prompt(), None);
}
#[test]
fn subcommand_takes_precedence_over_positional_prompt() {
let args = PagerArgs::try_parse_from(["grok", "logout"]).expect("subcommand parses");
let args = PagerArgs::try_parse_from(["kigi", "logout"]).expect("subcommand parses");
assert!(matches!(args.command, Some(Command::Logout)));
assert!(args.prompt.is_none());
}
#[test]
fn positional_prompt_conflicts_with_headless_single() {
let err = PagerArgs::try_parse_from(["grok", "-p", "headless", "interactive"])
let err = PagerArgs::try_parse_from(["kigi", "-p", "headless", "interactive"])
.expect_err("positional prompt + --single must conflict");
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
}
#[test]
fn worktree_flag_and_initial_prompt_combine() {
let a = PagerArgs::try_parse_from(["grok", "do the thing", "-w"])
let a = PagerArgs::try_parse_from(["kigi", "do the thing", "-w"])
.expect("prompt then bare -w parses");
assert_eq!(a.initial_prompt(), Some("do the thing"));
assert_eq!(a.worktree.as_deref(), Some(""));
let b = PagerArgs::try_parse_from(["grok", "--worktree=feat", "do the thing"])
let b = PagerArgs::try_parse_from(["kigi", "--worktree=feat", "do the thing"])
.expect("--worktree=name + positional parses");
assert_eq!(b.initial_prompt(), Some("do the thing"));
assert_eq!(b.worktree.as_deref(), Some("feat"));
let c = PagerArgs::try_parse_from(["grok", "-w", "x"]).expect("-w x parses");
let c = PagerArgs::try_parse_from(["kigi", "-w", "x"]).expect("-w x parses");
assert_eq!(c.worktree.as_deref(), Some("x"));
assert_eq!(c.initial_prompt(), None);
}
#[test]
fn trust_flag_parses_on_pager_and_alias() {
let bare = PagerArgs::try_parse_from(["grok"]).expect("bare grok parses");
let bare = PagerArgs::try_parse_from(["kigi"]).expect("bare kigi parses");
assert!(!bare.trust);
let long = PagerArgs::try_parse_from(["grok", "--trust"]).expect("--trust parses");
let long = PagerArgs::try_parse_from(["kigi", "--trust"]).expect("--trust parses");
assert!(long.trust);
let alias =
PagerArgs::try_parse_from(["grok", "--trust-folder"]).expect("--trust-folder parses");
PagerArgs::try_parse_from(["kigi", "--trust-folder"]).expect("--trust-folder parses");
assert!(alias.trust);
}
#[test]
fn reasoning_effort_and_effort_alias_parse_same_field() {
let long = PagerArgs::try_parse_from(["grok", "--reasoning-effort", "high"])
let long = PagerArgs::try_parse_from(["kigi", "--reasoning-effort", "high"])
.expect("--reasoning-effort parses");
assert_eq!(long.reasoning_effort.as_deref(), Some("high"));
let alias =
PagerArgs::try_parse_from(["grok", "--effort", "high"]).expect("--effort alias parses");
PagerArgs::try_parse_from(["kigi", "--effort", "high"]).expect("--effort alias parses");
assert_eq!(alias.reasoning_effort.as_deref(), Some("high"));
}
#[test]
fn reasoning_effort_accepts_max_and_remapped_ids() {
let max = PagerArgs::try_parse_from(["grok", "--effort", "max"]).expect("max parses");
let max = PagerArgs::try_parse_from(["kigi", "--effort", "max"]).expect("max parses");
assert_eq!(max.reasoning_effort.as_deref(), Some("max"));
let deep =
PagerArgs::try_parse_from(["grok", "--reasoning-effort", "deep"]).expect("deep parses");
PagerArgs::try_parse_from(["kigi", "--reasoning-effort", "deep"]).expect("deep parses");
assert_eq!(deep.reasoning_effort.as_deref(), Some("deep"));
}
#[test]
fn reasoning_effort_last_flag_wins_when_both_names_set() {
let args =
PagerArgs::try_parse_from(["grok", "--reasoning-effort", "low", "--effort", "high"])
PagerArgs::try_parse_from(["kigi", "--reasoning-effort", "low", "--effort", "high"])
.expect("both effort flag names parse");
assert_eq!(args.reasoning_effort.as_deref(), Some("high"));
let reverse =
PagerArgs::try_parse_from(["grok", "--effort", "high", "--reasoning-effort", "low"])
PagerArgs::try_parse_from(["kigi", "--effort", "high", "--reasoning-effort", "low"])
.expect("both effort flag names parse (reverse order)");
assert_eq!(reverse.reasoning_effort.as_deref(), Some("low"));
}
@@ -1165,7 +1170,7 @@ mod tests {
#[test]
fn agent_args_effort_alias_parses() {
let args = PagerArgs::try_parse_from(["grok", "agent", "--effort", "max", "stdio"])
let args = PagerArgs::try_parse_from(["kigi", "agent", "--effort", "max", "stdio"])
.expect("agent --effort parses");
let Command::Agent(agent) = args.command.expect("agent subcommand") else {
panic!("expected agent subcommand");
@@ -24,7 +24,7 @@ pub(super) fn dispatch_logout(_app: &mut AppView) -> Vec<Effect> {
/// On the eager-auth path (cached token), login_method_id is never set
/// because the user skipped the login screen.
///
/// Does **not** invent `grok.com` when no interactive method is advertised
/// Does **not** invent `kimi-code` when no interactive method is advertised
/// (e.g. `preferred_method=api_key` with no key — empty `auth_methods`).
/// Callers already surface "No login method available" when this leaves
/// `login_method_id` unset.
@@ -1,5 +1,5 @@
//! Mid-turn interjection dispatch: optimistic local echo, the
//! `x.ai/interject` effect, and prompt-history recording. Split out of
//! `kigi/interject` effect, and prompt-history recording. Split out of
//! `dispatch.rs` verbatim (pure code motion).
use crate::app::actions::Effect;
@@ -9,10 +9,10 @@ use crate::scrollback::block::RenderBlock;
/// Send a mid-turn interjection. Pushes a standard user prompt block locally
/// for instant feedback, records the text in prompt history, clears the
/// prompt, and fires the `x.ai/interject` ext method carrying a client-minted
/// prompt, and fires the `kigi/interject` ext method carrying a client-minted
/// id.
///
/// The shell broadcasts `x.ai/session/interjection` to every attached pane so
/// The shell broadcasts `kigi/session/interjection` to every attached pane so
/// other clients viewing the same session render it too (multi-client /
/// dashboard mode). Our own broadcast echoes back carrying the same id; the id
/// is recorded in `self_interjection_ids` so `handle_interjection` drops the
@@ -43,7 +43,7 @@ pub(super) fn dispatch_interject(
record_interject_prompt_history(agent, &text);
// Push a standard user prompt block locally for instant feedback, and
// record its id so the broadcast echo (`x.ai/session/interjection`) is
// record its id so the broadcast echo (`kigi/session/interjection`) is
// deduped instead of rendering a second copy on this pane.
let interjection_id = uuid::Uuid::new_v4().to_string();
agent.self_interjection_ids.insert(interjection_id.clone());
@@ -69,7 +69,7 @@ pub(super) fn dispatch_send_feedback(app: &mut AppView, text: String) -> Vec<Eff
};
agent.scrollback.push_block(RenderBlock::system(
"Thanks for the feedback! The Grok Build team is on it.".to_string(),
"Thanks for the feedback! The Kigi team is on it.".to_string(),
));
vec![Effect::SendFeedback {
@@ -79,7 +79,7 @@ pub(super) fn dispatch_send_feedback(app: &mut AppView, text: String) -> Vec<Eff
}]
}
/// Send a raw remember note for LLM-powered rewriting via `x.ai/memory/rewrite`.
/// Send a raw remember note for LLM-powered rewriting via `kigi/memory/rewrite`.
/// Clears remember mode and prompts the LLM to reformat the note with session
/// context. Falls back to direct `SaveMemoryNote` when no session is available.
pub(super) fn dispatch_send_remember_note(app: &mut AppView, text: String) -> Vec<Effect> {
@@ -327,7 +327,7 @@ pub(crate) fn scrollback_has_user_messages(
}
/// Request a session recap. Bypasses the prompt queue — works even while the
/// agent is mid-turn. Fires the `x.ai/recap` ext method; the recap arrives
/// agent is mid-turn. Fires the `kigi/recap` ext method; the recap arrives
/// asynchronously as a `SessionRecap` notification (rendered in scrollback).
///
/// `auto` is `false` for an explicit `/recap` and `true` for the automatic
@@ -343,7 +343,7 @@ pub(super) fn dispatch_send_recap(app: &mut AppView, auto: bool) -> Vec<Effect>
};
// Shell is authoritative (remote settings / config / env). Skip client requests
// entirely when the feature is off so we never hit `x.ai/recap`.
// entirely when the feature is off so we never hit `kigi/recap`.
if !app.session_recap_available {
if !auto {
agent.show_toast("Session recap is not enabled");
@@ -420,7 +420,7 @@ pub(super) fn handle_memory_note_saved(
.scrollback
.push_block(crate::scrollback::block::RenderBlock::system(format!(
"Memory saved to {}",
crate::util::display_user_grok_path("memory/MEMORY.md")
crate::util::display_user_kigi_path("memory/MEMORY.md")
)));
}
Err(error) => {
@@ -23,7 +23,7 @@ use agent_client_protocol as acp;
/// existing `set_yolo_mode(true)` flow to flip the local YOLO state, drain
/// any remaining queued permissions, persist `[ui] permission_mode =
/// "always-approve"` to `~/.kigi/config.toml`, and fire the
/// `x.ai/yolo_mode_changed` ACP notification. See the option-id constant
/// `kigi/yolo_mode_changed` ACP notification. See the option-id constant
/// doc-comment for the full client/shell split. Under a managed-policy
/// pin step (b) is refused with a toast — the request is still allowed once.
pub(super) fn dispatch_permission_select(
@@ -32,7 +32,7 @@ pub(super) fn consume_chat_kind(app: &mut AppView) -> bool {
/// The prompt is always pushed to the queue first. If the agent is idle
/// (and has a session), `maybe_drain_queue` pops the front prompt and
/// sends it in the same dispatch call — no deferred ticks.
/// Start (if needed) and submit the initial prompt from `grok "<prompt>"`.
/// Start (if needed) and submit the initial prompt from `kigi "<prompt>"`.
///
/// Shared by the TUI startup path (already authenticated) and the post-login
/// `AuthComplete` path (deferred via `deferred_startup.prompt`). It does nothing
@@ -511,7 +511,7 @@ pub(super) fn dispatch_send_prompt_inner(
// immediately instead of being held in the local drip-feed queue. The
// agent appends it to its authoritative `pending_inputs` (no concurrent
// turn starts — validated keystone) and drives the drain via
// `x.ai/queue/changed`. We render an optimistic echo into the shared
// `kigi/queue/changed`. We render an optimistic echo into the shared
// queue keyed by `prompt_id`; the broadcast reconciles it by id.
//
// The IDLE case is unchanged (falls through to the local path below,
@@ -930,7 +930,7 @@ pub(super) fn handle_prompt_response(
// Server-authoritative queue lifecycle: this prompt's RPC
// resolved without becoming the running turn (removed,
// cancelled, rewound). Retire its optimistic echo so a
// later `x.ai/queue/changed` broadcast can't re-pin a
// later `kigi/queue/changed` broadcast can't re-pin a
// stale placeholder and reorder the queue.
if let Some(sid) = agent.session.session_id.as_ref().map(|s| s.0.to_string()) {
retire_optimistic_echo(
@@ -1171,7 +1171,7 @@ pub(super) fn handle_prompt_response(
// title into the body automatically.
let notif_title = session_name
.map(|s| s.to_string())
.unwrap_or_else(|| "Grok".into());
.unwrap_or_else(|| "Kigi".into());
app.deferred_notification = Some((
NotificationEvent {
@@ -62,7 +62,7 @@ pub(super) fn immediate_server_send_eligible(agent: &AgentView) -> bool {
/// Push the optimistic shared-queue echo for an immediate server-authoritative
/// send and mirror it into the owning agent so the queue pane renders it
/// immediately, before the confirming `x.ai/queue/changed` broadcast.
/// immediately, before the confirming `kigi/queue/changed` broadcast.
pub(super) fn push_server_queue_echo(
app: &mut AppView,
agent_id: AgentId,
@@ -91,7 +91,7 @@ pub(super) fn push_server_queue_echo(
///
/// The agent's `pending_inputs` is the single source of truth for queue
/// contents and order; the only client-side queue state is the optimistic echo
/// that bridges the round-trip before the confirming `x.ai/queue/changed`
/// that bridges the round-trip before the confirming `kigi/queue/changed`
/// broadcast. Once a prompt's RPC resolves (or we pull it back into the input
/// on cancel) it will never reappear in a future broadcast, so its echo must be
/// dropped — otherwise the reconcile in [`AppView::apply_queue_changed`] keeps
@@ -633,7 +633,7 @@ pub(crate) fn apply_turn_start_shim(
agent.session.current_prompt_id = Some(prompt_id.clone());
agent.attached_as_viewer = adopted_from_other_client;
// A new (adopted) turn is starting: drop the prior turn's chips but KEEP the
// seen ring, so a buffer-replayed `x.ai/follow_ups` for an older response
// seen ring, so a buffer-replayed `kigi/follow_ups` for an older response
// stays rejected (no stale revival). This is correct for BOTH passive-viewer
// and self-driven adoption: the adopted turn's OWN follow_ups still
// re-render via the stamped `promptId` match in `apply_follow_ups` (the
@@ -1150,7 +1150,7 @@ mod tests {
}
/// FIX 4 (b) via the shim: after starting a NEW turn, a buffer-replayed
/// `x.ai/follow_ups` for a PRIOR turn's response stays rejected (its
/// `kigi/follow_ups` for a PRIOR turn's response stays rejected (its
/// `promptId` is not the active turn and it is already seen) — no stale
/// revival. Covers the self-driven turn start (`p-self`).
#[test]
@@ -95,8 +95,8 @@ pub(in crate::app::dispatch) fn apply_persist_worktree_mode(
/// Build the two persistence options shared by the fork and new-session
/// worktree question modals ("Always worktree" / "Never worktree").
pub(super) fn worktree_persist_options()
-> [kigi_tools::implementations::grok_build::ask_user_question::QuestionOption; 2] {
use kigi_tools::implementations::grok_build::ask_user_question::QuestionOption;
-> [kigi_tools::implementations::kigi::ask_user_question::QuestionOption; 2] {
use kigi_tools::implementations::kigi::ask_user_question::QuestionOption;
[
QuestionOption {
label: "Always worktree".into(),
@@ -117,7 +117,7 @@ pub(super) fn worktree_persist_options()
/// instead -- the modal-collision protocol.
fn open_fork_question(app: &mut AppView, directive: Option<String>) -> Vec<Effect> {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let ActiveView::Agent(id) = app.active_view else {
return vec![];
};
@@ -168,7 +168,7 @@ fn open_fork_question(app: &mut AppView, directive: Option<String>) -> Vec<Effec
/// `worktree == true` reuses the existing
/// [`Effect::CreateWorktreeSession`] pipeline (with `load_session_id`
/// set to the parent session id). `worktree == false` emits the new
/// [`Effect::ForkSession`] which calls `x.ai/session/fork` directly.
/// [`Effect::ForkSession`] which calls `kigi/session/fork` directly.
pub(in crate::app::dispatch) fn dispatch_fork_resolved(
app: &mut AppView,
worktree: bool,
@@ -154,7 +154,7 @@ pub(in crate::app::dispatch) fn dispatch_new_session(app: &mut AppView) -> Vec<E
/// [`dispatch_new_worktree_session`].
pub(in crate::app::dispatch) fn open_new_session_question(app: &mut AppView) -> Vec<Effect> {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let ActiveView::Agent(id) = app.active_view else {
return vec![];
};
@@ -211,7 +211,7 @@ pub(in crate::app::dispatch) fn open_agent_type_mismatch_question(
model_name: &str,
) -> Vec<Effect> {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let ActiveView::Agent(id) = app.active_view else {
return vec![];
};
@@ -408,7 +408,7 @@ pub(in crate::app::dispatch) fn clear_startup_actions(app: &mut AppView) {
let _ = app.deferred_startup.take();
}
/// Replay the session-startup actions deferred until auth + trust both resolved
/// (`--resume` / `--worktree` / initial-prompt / `grok dashboard`). Extracted
/// (`--resume` / `--worktree` / initial-prompt / `kigi dashboard`). Extracted
/// from the `AuthComplete` handler so the folder-trust answer can run the SAME
/// machinery; whichever gate resolves last drains it (each call site guards on
/// the other gate being `Done`, so it runs exactly once).
@@ -606,7 +606,7 @@ pub(in crate::app::dispatch) fn dispatch_trigger_deep_search(
}
}
/// Chat-mode replacement for local deep search: refetch the session list
/// with the picker query pushed down as `x.ai/session/list` `query`.
/// with the picker query pushed down as `kigi/session/list` `query`.
/// Keystrokes are coalesced through [`Effect::DebounceSessionSearch`]; a
/// forced search (Ctrl+/) or a cleared query fetches immediately. Every
/// trigger bumps `session_picker_list_seq`, so stale in-flight debounces
@@ -68,7 +68,7 @@ pub(in crate::app::dispatch) fn dispatch_sessions_confirm_close(
remove_agent_and_cleanup(app, closed_id);
effects
}
/// Rename the current session via x.ai/session/rename.
/// Rename the current session via kigi/session/rename.
///
/// Produces Effect::RenameSession which spawns an async ACP ext request.
/// On completion, TaskResult::RenameSessionComplete shows the result.
@@ -248,7 +248,7 @@ pub(in crate::app::dispatch) fn set_ask_user_question_timeout_enabled(
app: &mut AppView,
new: bool,
) -> Vec<Effect> {
use kigi_tools::implementations::grok_build::ask_user_question;
use kigi_tools::implementations::kigi::ask_user_question;
let prev_state = app.ask_user_question_timeout_enabled;
let prev_effective =
prev_state.unwrap_or(ask_user_question::DEFAULT_ASK_USER_QUESTION_TIMEOUT_ENABLED);
@@ -1206,8 +1206,8 @@ pub(in crate::app::dispatch) fn set_auto_dark_theme(app: &mut AppView, new: Stri
.as_deref()
.and_then(crate::theme::canonical_name)
.filter(|s| *s != "auto")
// No prior config: fall back to GrokNight (the default).
.unwrap_or_else(|| crate::theme::ThemeKind::GrokNight.display_name());
// No prior config: fall back to KigiNight (the default).
.unwrap_or_else(|| crate::theme::ThemeKind::KigiNight.display_name());
let new_canonical = match crate::theme::canonical_name(&new) {
Some(c) if c != crate::theme::ThemeKind::Auto.display_name() => c,
_ => {
@@ -1320,7 +1320,7 @@ pub(in crate::app::dispatch) fn set_auto_light_theme(
.as_deref()
.and_then(crate::theme::canonical_name)
.filter(|s| *s != "auto")
.unwrap_or_else(|| crate::theme::ThemeKind::GrokDay.display_name());
.unwrap_or_else(|| crate::theme::ThemeKind::KigiDay.display_name());
let new_canonical = match crate::theme::canonical_name(&new) {
Some(c) if c != crate::theme::ThemeKind::Auto.display_name() => c,
_ => {
@@ -1430,7 +1430,7 @@ pub(in crate::app::dispatch) fn set_default_model_inner(
// or `/clear` creates a fresh session by cloning `app.models`
// (`dispatch_new_session_inner_with_id`), so without this the new session —
// and the welcome card it commits — would show the previous default until
// the next `x.ai/models/update` roundtrip.
// the next `kigi/models/update` roundtrip.
if app.models.available.contains_key(id) {
app.models.set_current(id.clone(), None);
}
@@ -1510,7 +1510,7 @@ pub(in crate::app::dispatch) fn set_default_model(
// Persist the **model ID** (catalog key), not the display name.
// The shell's `resolve_default_model` matches by slug / map key,
// so persisting the human-readable name (e.g. "Grok Build")
// so persisting the human-readable name (e.g. "Kigi")
// would silently fail to resolve on the next startup.
//
// Chat (`--chat` / KIGI_CHAT_MODE) catalogs use opaque `/rest/modes`
@@ -1798,7 +1798,7 @@ pub(in crate::app::dispatch) fn set_max_thoughts_width(app: &mut AppView, new: i
/// (`show_tips`, `auto_update`, ask_user_question timeout).
/// Matches the consumer's `.unwrap_or(...)` fallback.
pub(super) fn pr13_effective_default(key: &str) -> Option<bool> {
use kigi_tools::implementations::grok_build::ask_user_question;
use kigi_tools::implementations::kigi::ask_user_question;
match key {
"show_tips" => Some(true),
"auto_update" => Some(true),
@@ -8,7 +8,7 @@ use crate::app::app_view::{ActiveView, AppView};
use crate::notifications::{NotificationEvent, NotificationEventKind};
use crate::scrollback::block::RenderBlock;
/// Show session info: fetch via x.ai/session/info and display in scrollback.
/// Show session info: fetch via kigi/session/info and display in scrollback.
///
/// Produces Effect::ShowSessionInfo which spawns an async ACP ext request.
/// On completion, TaskResult::SessionInfoComplete shows the formatted info.
@@ -49,7 +49,7 @@ pub(super) fn scrub_error_for_toast(error: &str) -> String {
}
}
/// Show context info: fetch via x.ai/session/info and display rich breakdown.
/// Show context info: fetch via kigi/session/info and display rich breakdown.
///
/// Produces Effect::ShowContextInfo which spawns an async ACP ext request.
/// On completion, TaskResult::ContextInfoComplete shows the formatted info.
@@ -72,7 +72,7 @@ pub(super) fn dispatch_show_context_info(app: &mut AppView) -> Vec<Effect> {
/// `/usage` — fetch Kimi usage/quota rows and display them inline.
///
/// Produces [`Effect::FetchUsage`], which asks the shell's `x.ai/billing`
/// Produces [`Effect::FetchUsage`], which asks the shell's `kigi/billing`
/// extension (`GET {base}/usages`); [`handle_usage_fetched`] renders the
/// rows as a system block in scrollback.
pub(super) fn dispatch_show_usage(app: &mut AppView) -> Vec<Effect> {
@@ -225,7 +225,7 @@ pub(super) fn notify_session_ready(
) {
notification_service.notify(NotificationEvent {
kind: NotificationEventKind::SessionReady,
title: "Grok".into(),
title: "Kigi".into(),
body: NotificationEventKind::SessionReady.as_str().into(),
session_id: agent.session.session_id.as_ref().map(|s| s.0.to_string()),
});
@@ -218,7 +218,7 @@ fn cancel_login_strips_reauth_prompt_from_scrollback() {
}
/// Empty `auth_methods` (preferred_method pin unavailable) must not invent
/// `grok.com` or start an OIDC flow the agent did not advertise.
/// `kimi-code` or start an OIDC flow the agent did not advertise.
#[test]
fn login_with_empty_auth_methods_fails_closed() {
let mut app = test_app_with_agent();
@@ -2,7 +2,7 @@
use super::*;
/// `grok dashboard` before login: the startup hook consumes the
/// `kigi dashboard` before login: the startup hook consumes the
/// `KIGI_OPEN_DASHBOARD_AT_STARTUP` env var and stashes
/// `deferred_startup.open_dashboard`; `AuthComplete` must then open the
/// dashboard view. Regression test for the silent drop where the
@@ -30,7 +30,7 @@ fn auth_complete_opens_deferred_dashboard() {
assert!(matches!(app.auth_state, AuthState::Done));
assert!(
matches!(app.active_view, ActiveView::AgentDashboard),
"deferred `grok dashboard` must open the dashboard after login",
"deferred `kigi dashboard` must open the dashboard after login",
);
assert!(
!app.deferred_startup.open_dashboard,
@@ -575,15 +575,15 @@ fn dashboard_confirm_worktree_without_git_repo_creates_nothing() {
#[test]
fn dashboard_confirm_worktree_applies_pending_model_and_plan() {
let mut app = test_app();
seed_model(&mut app, "grok-4.5", "Grok 4.5");
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
open_dashboard(&mut app);
app.cwd_has_git_ancestor = true;
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
if let Some(d) = app.dashboard.as_mut() {
d.pending_model = Some(crate::views::dashboard::PendingDispatchModel {
id: model_id.clone(),
effort: Some(kigi_shell::sampling::types::ReasoningEffort::High),
display: "Grok 4.5".to_string(),
display: "Kigi 4.5".to_string(),
});
d.pending_mode = crate::views::dashboard::DashboardDispatchMode::Plan;
d.dispatch.set_text("do the thing");
@@ -1120,7 +1120,7 @@ fn dashboard_peek_cycle_does_not_retire_the_nudge() {
/// leader mode. The dashboard renders local sessions regardless; leader
/// mode only adds the roster poll. Every entry point funnels through
/// `Action::OpenDashboard`, so this covers `/dashboard`, `Ctrl+\`,
/// `grok dashboard`, and the startup hook.
/// `kigi dashboard`, and the startup hook.
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn dashboard_open_works_without_leader() {
@@ -1237,9 +1237,9 @@ fn seed_model(app: &mut AppView, id: &str, name: &str) {
#[test]
fn dashboard_slash_model_stages_pending_model() {
let mut app = test_app();
seed_model(&mut app, "grok-4.5", "Grok 4.5");
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
open_dashboard(&mut app);
let effects = dispatch_dashboard_dispatch_slash(&mut app, "/model grok-4.5".into());
let effects = dispatch_dashboard_dispatch_slash(&mut app, "/model kigi-4.5".into());
assert!(
effects.is_empty(),
"staging a model must not spawn a session"
@@ -1252,8 +1252,8 @@ fn dashboard_slash_model_stages_pending_model() {
.pending_model
.as_ref()
.expect("pending_model must be set");
assert_eq!(pending.id.0.as_ref(), "grok-4.5");
assert_eq!(pending.display, "Grok 4.5");
assert_eq!(pending.id.0.as_ref(), "kigi-4.5");
assert_eq!(pending.display, "Kigi 4.5");
assert!(pending.effort.is_none());
// The catalog snapshot's `current` tracks the staged model so the
// next `/model` dropdown marks it `(current)` (not the seeded default).
@@ -1265,7 +1265,7 @@ fn dashboard_slash_model_stages_pending_model() {
.current
.as_ref()
.map(|id| id.0.as_ref()),
Some("grok-4.5"),
Some("kigi-4.5"),
"staging must update the snapshot's current selection",
);
}
@@ -1279,7 +1279,7 @@ fn dashboard_slash_model_stages_pending_model() {
#[test]
fn dashboard_slash_command_error_gets_error_glyph_prefix() {
let mut app = test_app();
seed_model(&mut app, "grok-4.5", "Grok 4.5");
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
open_dashboard(&mut app);
let effects = dispatch_dashboard_dispatch_slash(&mut app, "/model nonexistent".into());
assert!(effects.is_empty(), "a failed command must not dispatch");
@@ -1512,14 +1512,14 @@ fn dashboard_cycle_mode_skips_always_approve_under_policy_pin() {
fn dashboard_open_reseeds_pending_model_and_mode() {
use crate::views::dashboard::DashboardDispatchMode;
let mut app = test_app();
seed_model(&mut app, "grok-4.5", "Grok 4.5");
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
open_dashboard(&mut app);
// Stage a model + non-default mode as if from a previous session.
if let Some(d) = app.dashboard.as_mut() {
d.pending_model = Some(crate::views::dashboard::PendingDispatchModel {
id: acp::ModelId::new(std::sync::Arc::from("grok-4.5")),
id: acp::ModelId::new(std::sync::Arc::from("kigi-4.5")),
effort: None,
display: "Grok 4.5".to_string(),
display: "Kigi 4.5".to_string(),
});
d.pending_mode = DashboardDispatchMode::Plan;
}
@@ -1727,14 +1727,14 @@ fn dashboard_dispatch_new_agent_is_working_with_prompt_title() {
#[test]
fn dashboard_dispatch_applies_pending_model_and_plan() {
let mut app = test_app();
seed_model(&mut app, "grok-4.5", "Grok 4.5");
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
open_dashboard(&mut app);
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
if let Some(d) = app.dashboard.as_mut() {
d.pending_model = Some(crate::views::dashboard::PendingDispatchModel {
id: model_id.clone(),
effort: Some(kigi_shell::sampling::types::ReasoningEffort::High),
display: "Grok 4.5".to_string(),
display: "Kigi 4.5".to_string(),
});
d.pending_mode = crate::views::dashboard::DashboardDispatchMode::Plan;
}
@@ -1770,14 +1770,14 @@ fn dashboard_dispatch_applies_pending_model_and_plan() {
#[test]
fn dashboard_new_agent_button_applies_pending_model_and_plan() {
let mut app = test_app();
seed_model(&mut app, "grok-4.5", "Grok 4.5");
seed_model(&mut app, "kigi-4.5", "Kigi 4.5");
open_dashboard(&mut app);
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
if let Some(d) = app.dashboard.as_mut() {
d.pending_model = Some(crate::views::dashboard::PendingDispatchModel {
id: model_id.clone(),
effort: Some(kigi_shell::sampling::types::ReasoningEffort::High),
display: "Grok 4.5".to_string(),
display: "Kigi 4.5".to_string(),
});
d.pending_mode = crate::views::dashboard::DashboardDispatchMode::Plan;
}
@@ -4624,7 +4624,7 @@ fn dashboard_permission_followup_rejects_with_message() {
fn dashboard_question_answer_sends_and_clears() {
use crate::views::prompt_widget::StashedPrompt;
use crate::views::question_view::QuestionViewState;
use kigi_tools::implementations::grok_build::ask_user_question::{
use kigi_tools::implementations::kigi::ask_user_question::{
AskUserQuestionMode, Question, QuestionOption,
};
@@ -4673,7 +4673,7 @@ fn dashboard_question_answer_walks_multiple_questions() {
use crate::views::dashboard::peek::{PeekPanelState, compute_peek_fields};
use crate::views::prompt_widget::StashedPrompt;
use crate::views::question_view::QuestionViewState;
use kigi_tools::implementations::grok_build::ask_user_question::{
use kigi_tools::implementations::kigi::ask_user_question::{
AskUserQuestionMode, Question, QuestionOption,
};
@@ -116,8 +116,8 @@ fn test_app() -> AppView {
agent_override: None,
bootstrap_acp_commands: Vec::new(),
auth_methods: vec![acp::AuthMethod::Agent(acp::AuthMethodAgent::new(
acp::AuthMethodId::new("grok.com"),
"Grok".to_string(),
acp::AuthMethodId::new("kimi-code"),
"Kigi".to_string(),
))],
auth_state: AuthState::Done,
trust_state: TrustState::Done,
@@ -462,7 +462,7 @@ fn fork_test_app() -> AppView {
app
}
/// Build a minimal `AcpArgs<acp::ExtRequest>` for an
/// `x.ai/ask_user_question` ext-method request. Returns the args
/// `kigi/ask_user_question` ext-method request. Returns the args
/// plus the receiver half of the response oneshot so the test can
/// assert the handler completes the ACP roundtrip.
fn make_ask_user_question_args(
@@ -471,14 +471,13 @@ fn make_ask_user_question_args(
kigi_acp_lib::AcpArgs<acp::ExtRequest>,
tokio::sync::oneshot::Receiver<kigi_acp_lib::AcpResult<acp::ExtResponse>>,
) {
use kigi_tools::implementations::grok_build::ask_user_question::{
use kigi_tools::implementations::kigi::ask_user_question::{
AskUserQuestionExtRequest, Question, QuestionOption,
};
let req = AskUserQuestionExtRequest {
session_id: "test-session".into(),
tool_call_id: tool_call_id.into(),
mode:
kigi_tools::implementations::grok_build::ask_user_question::AskUserQuestionMode::Default,
mode: kigi_tools::implementations::kigi::ask_user_question::AskUserQuestionMode::Default,
questions: vec![Question {
question: "ACP-driven question".into(),
options: vec![QuestionOption {
@@ -493,7 +492,7 @@ fn make_ask_user_question_args(
};
let (tx, rx) = tokio::sync::oneshot::channel();
let ext = acp::ExtRequest::new(
"x.ai/ask_user_question",
"kigi/ask_user_question",
serde_json::value::to_raw_value(&req)
.expect("serialize AskUserQuestionExtRequest")
.into(),
@@ -742,7 +741,7 @@ fn with_theme_test_env(f: impl FnOnce()) {
.unwrap_or_else(|e| e.into_inner());
crate::theme::cache::reset_for_test();
crate::theme::cache::seed_auto_theme_defaults_for_test();
crate::theme::cache::set(crate::theme::ThemeKind::GrokNight);
crate::theme::cache::set(crate::theme::ThemeKind::KigiNight);
crate::theme::system_appearance::clear_mock();
f();
crate::theme::system_appearance::clear_mock();
@@ -598,7 +598,7 @@ fn yolo_on_drain_clears_double_click_tracker() {
/// 2. The dispatcher returns a `PersistPermissionMode` effect with
/// canonical `"always-approve"` — this is what flips
/// `[ui] permission_mode` on disk AND fires the
/// `x.ai/yolo_mode_changed` ACP notification back to the shell.
/// `kigi/yolo_mode_changed` ACP notification back to the shell.
/// 3. The agent's per-session `yolo_mode` flag is flipped to true,
/// so subsequent permission requests are auto-approved by
/// `handle_permission_request`.
@@ -651,7 +651,7 @@ fn enable_always_approve_sends_response_and_flips_yolo_and_persists() {
// (2) The dispatcher returns a PersistPermissionMode effect with
// canonical "always-approve". This is the bridge that writes
// ~/.kigi/config.toml AND fires x.ai/yolo_mode_changed.
// ~/.kigi/config.toml AND fires kigi/yolo_mode_changed.
let persist = effects
.iter()
.find_map(|e| match e {
@@ -725,7 +725,7 @@ fn enable_always_approve_is_idempotent_when_yolo_already_on() {
.any(|e| matches!(e, Effect::PersistPermissionMode { .. })),
"redundant PersistPermissionMode when YOLO already on — the dispatcher \
must short-circuit to avoid double-writing config.toml and double-firing \
x.ai/yolo_mode_changed",
kigi/yolo_mode_changed",
);
}
@@ -24,7 +24,7 @@ fn manual_recap_with_no_messages_toasts_empty_state_and_skips_request() {
assert!(
effects.is_empty(),
"empty session must not fire x.ai/recap: {effects:?}"
"empty session must not fire kigi/recap: {effects:?}"
);
let agent = app.agents.get(&id).unwrap();
assert!(agent.pending_recap_entry.is_none(), "no loading spinner");
@@ -86,7 +86,7 @@ fn manual_recap_during_batch_load_with_prompts_still_requests() {
assert!(
matches!(effects.as_slice(), [Effect::SendRecap { auto: false, .. }]),
"batched resume with user prompts must still fire x.ai/recap: {effects:?}"
"batched resume with user prompts must still fire kigi/recap: {effects:?}"
);
let agent = app.agents.get(&id).unwrap();
assert!(agent.pending_recap_entry.is_some());
@@ -846,7 +846,7 @@ fn prompt_response_resets_turn_state() {
assert_eq!(app.agents[&id].scrollback.len(), 1);
}
/// Turn end with prompt suggestions enabled fires the `x.ai/suggestPrompt`
/// Turn end with prompt suggestions enabled fires the `kigi/suggestPrompt`
/// fetch (before the billing refresh), and the loaded suggestion routes back
/// into the agent's controller by id + generation.
#[test]
@@ -879,8 +879,8 @@ fn turn_end_fetches_prompt_suggestion_when_enabled() {
};
assert_eq!(*agent_id, id);
assert!(session_id.is_some());
// No `grok-build-0.1` in the test catalog and no env override →
// `None` on the wire; the shell then uses its own `grok-build-0.1`
// No `kigi-0.1` in the test catalog and no env override →
// `None` on the wire; the shell then uses its own `kigi-0.1`
// default (suggestion calls never use the session model).
assert_eq!(*model, None);
@@ -1255,7 +1255,7 @@ fn turn_complete_notification_suppressed_when_queue_non_empty() {
/// Regression: cancelling while prompts are queued must hand the queue to
/// the agent untouched. The FRONT queued prompt runs next (promoted
/// server-side), the rest stay queued in order, and the authoritative
/// `x.ai/queue/changed` rebroadcast — not client-side prediction — updates
/// `kigi/queue/changed` rebroadcast — not client-side prediction — updates
/// the mirror. Nothing resurrects or reorders.
#[test]
fn cancel_hands_queue_to_agent_without_reordering() {
@@ -1771,7 +1771,7 @@ fn send_prompt_works_after_reconnect_clears() {
fn switch_model_holds_prompt_until_complete() {
let mut app = test_app_with_agent();
let id = AgentId(0);
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
dispatch(
Action::SwitchModel {
@@ -1905,7 +1905,7 @@ fn submit_question_answers_cancel_clears_local_modal_and_restores_prompt() {
// exercising the prompt.restore + cleanup_question_state contract
// that lives in `submit_question_answers` itself.
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let mut app = fork_test_app();
let id = AgentId(0);
@@ -255,7 +255,7 @@ fn mark_turn_finished_clears_start_and_stamps_active() {
fn switch_model_dispatch_produces_effect_and_sets_pending() {
let mut app = test_app_with_agent();
let id = AgentId(0);
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
assert!(!app.agents[&id].session.model_switch_pending);
let effects = dispatch(
Action::SwitchModel {
@@ -989,7 +989,7 @@ fn dispatch_fork_no_flag_always_reopens_modal_after_previous_answer() {
#[test]
fn translate_local_submit_skipped_returns_changed_with_no_action() {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let q = Question {
question: "?".into(),
options: vec![QuestionOption {
@@ -1018,7 +1018,7 @@ fn translate_local_submit_skipped_returns_changed_with_no_action() {
#[test]
fn translate_local_submit_no_selection_returns_changed_no_action() {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let q = Question {
question: "?".into(),
options: (0..2)
@@ -1047,7 +1047,7 @@ fn translate_local_submit_no_selection_returns_changed_no_action() {
#[test]
fn translate_local_submit_out_of_range_index_returns_changed_no_action() {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let q = Question {
question: "?".into(),
options: (0..2)
@@ -1077,7 +1077,7 @@ fn translate_local_submit_out_of_range_index_returns_changed_no_action() {
#[test]
fn handle_ask_user_question_does_not_push_system_block_when_displaced_acp_modal() {
use crate::views::question_view::QuestionViewState;
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let mut app = fork_test_app();
let id = AgentId(0);
let stashed = app.agents.get_mut(&id).unwrap().prompt.stash();
@@ -30,8 +30,8 @@ fn worktree_forked_sets_session_id_eagerly_and_emits_load() {
assert!(app.agents[&id].session.session_id.is_none());
assert!(!app.agents[&id].session.loading_replay);
let worktree_path = PathBuf::from("/tmp/grok-worktrees/pager-fork");
let session_cwd = PathBuf::from("/tmp/grok-worktrees/pager-fork/sub");
let worktree_path = PathBuf::from("/tmp/kigi-worktrees/pager-fork");
let session_cwd = PathBuf::from("/tmp/kigi-worktrees/pager-fork/sub");
let effects = dispatch(
Action::TaskComplete(TaskResult::WorktreeForked {
agent_id: id,
@@ -76,8 +76,8 @@ fn worktree_forked_with_restore_shows_summary_in_scrollback() {
);
let id = AgentId(0);
let worktree_path = PathBuf::from("/tmp/grok-worktrees/pager-fork");
let session_cwd = PathBuf::from("/tmp/grok-worktrees/pager-fork/sub");
let worktree_path = PathBuf::from("/tmp/kigi-worktrees/pager-fork");
let session_cwd = PathBuf::from("/tmp/kigi-worktrees/pager-fork/sub");
let effects = dispatch(
Action::TaskComplete(TaskResult::WorktreeForked {
agent_id: id,
@@ -130,8 +130,8 @@ fn worktree_forked_with_restore_failure_shows_warning_banner() {
);
let id = AgentId(0);
let worktree_path = PathBuf::from("/tmp/grok-worktrees/pager-fail");
let session_cwd = PathBuf::from("/tmp/grok-worktrees/pager-fail/sub");
let worktree_path = PathBuf::from("/tmp/kigi-worktrees/pager-fail");
let session_cwd = PathBuf::from("/tmp/kigi-worktrees/pager-fail/sub");
dispatch(
Action::TaskComplete(TaskResult::WorktreeForked {
agent_id: id,
@@ -472,7 +472,7 @@ fn open_fork_question_refuses_when_existing_question_is_open() {
let mut app = fork_test_app();
// Plant an existing question (e.g. an ACP-driven one).
use crate::views::question_view::QuestionViewState;
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let q = Question {
question: "existing ACP question?".into(),
options: vec![QuestionOption {
@@ -1047,7 +1047,7 @@ fn fork_session_failed_pushes_turn_failed_block() {
#[test]
fn translate_local_submit_yes_returns_worktree_true_action() {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let q = Question {
question: "?".into(),
options: (0..2)
@@ -1090,7 +1090,7 @@ fn translate_local_submit_yes_returns_worktree_true_action() {
#[test]
fn translate_local_submit_no_returns_worktree_false_action() {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let q = Question {
question: "?".into(),
options: (0..2)
@@ -1131,7 +1131,7 @@ fn translate_local_submit_no_returns_worktree_false_action() {
#[test]
fn translate_local_submit_always_returns_persist_always_for_fork() {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let q = Question {
question: "?".into(),
options: (0..4)
@@ -1173,7 +1173,7 @@ fn translate_local_submit_always_returns_persist_always_for_fork() {
#[test]
fn translate_local_submit_never_returns_persist_never_for_fork() {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let q = Question {
question: "?".into(),
options: (0..4)
@@ -1216,7 +1216,7 @@ fn translate_local_submit_never_returns_persist_never_for_fork() {
fn handle_ask_user_question_pushes_system_block_when_displaced_local_fork_modal() {
use crate::scrollback::block::RenderBlock;
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let mut app = fork_test_app();
let id = AgentId(0);
@@ -172,7 +172,7 @@ fn worktree_session_created_sets_session_and_cwd() {
&mut app,
);
let id = AgentId(0);
let worktree_path = PathBuf::from("/tmp/grok-worktrees/pager-123");
let worktree_path = PathBuf::from("/tmp/kigi-worktrees/pager-123");
let session_cwd = worktree_path.clone();
let effects = dispatch(
Action::TaskComplete(TaskResult::WorktreeSessionCreated {
@@ -353,13 +353,13 @@ fn worktree_session_created_drains_queued_prompts() {
let effects = dispatch(Action::SendPrompt("hello".into()), &mut app);
assert!(effects.is_empty(), "no session_id yet, can't drain");
assert_eq!(app.agents[&id].session.queue_len(), 1);
let worktree_path = PathBuf::from("/tmp/grok-worktrees/pager-abc");
let worktree_path = PathBuf::from("/tmp/kigi-worktrees/pager-abc");
let effects = dispatch(
Action::TaskComplete(TaskResult::WorktreeSessionCreated {
agent_id: id,
session_id: acp::SessionId::new("wt-drain-1"),
worktree_path,
session_cwd: PathBuf::from("/tmp/grok-worktrees/pager-abc"),
session_cwd: PathBuf::from("/tmp/kigi-worktrees/pager-abc"),
models: None,
}),
&mut app,
@@ -485,7 +485,7 @@ fn switch_model_without_session_does_nothing() {
let mut app = test_app_with_agent();
let id = AgentId(0);
app.agents.get_mut(&id).unwrap().session.session_id = None;
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
let effects = dispatch(
Action::SwitchModel {
model_id,
@@ -659,7 +659,7 @@ fn new_session_starts_with_prompt_focused() {
fn switch_model_deferred_when_no_session_id() {
let mut app = test_app_with_agent();
let id = AgentId(0);
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
app.agents.get_mut(&id).unwrap().session.session_id = None;
let effects = dispatch(
Action::SwitchModel {
@@ -679,7 +679,7 @@ fn switch_model_deferred_when_no_session_id() {
fn deferred_model_switch_applied_on_session_created() {
let mut app = test_app_with_agent();
let id = AgentId(0);
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
let session_id: acp::SessionId = "new-session".into();
app.agents.get_mut(&id).unwrap().session.session_id = None;
app.agents
@@ -708,7 +708,7 @@ fn deferred_model_switch_applied_on_session_created() {
#[test]
fn deferred_model_switch_applied_on_worktree_session_created() {
let mut app = test_app_git();
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
dispatch(
Action::NewWorktreeSession {
load_session_id: None,
@@ -1314,7 +1314,7 @@ fn dispatch_new_session_has_empty_scrollback() {
#[test]
fn translate_local_submit_always_returns_persist_always_for_new_session() {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let q = Question {
question: "?".into(),
options: (0..4)
@@ -1354,7 +1354,7 @@ fn translate_local_submit_always_returns_persist_always_for_new_session() {
#[test]
fn translate_local_submit_never_returns_persist_never_for_new_session() {
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let q = Question {
question: "?".into(),
options: (0..4)
@@ -1214,7 +1214,7 @@ fn project_picker_skip_falls_back_to_original_cwd() {
fn project_picker_freeform_path_used_when_no_option_selected() {
use crate::views::prompt_widget::StashedPrompt;
use crate::views::question_view::{LocalQuestionKind, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let q = Question {
question: "Pick".into(),
id: None,
@@ -1253,7 +1253,7 @@ fn project_picker_freeform_path_used_when_no_option_selected() {
fn project_picker_freeform_overrides_dont_ask() {
use crate::views::prompt_widget::StashedPrompt;
use crate::views::question_view::{LocalQuestionKind, QuestionSelection, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let opt = |label: &str| QuestionOption {
label: label.into(),
description: String::new(),
@@ -1310,7 +1310,7 @@ fn needs_project_picker_false_when_disabled() {
fn project_picker_dont_ask_again_sets_disable_flag() {
use crate::views::prompt_widget::StashedPrompt;
use crate::views::question_view::{LocalQuestionKind, QuestionSelection, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let opt = |label: &str| QuestionOption {
label: label.into(),
description: String::new(),
@@ -1348,7 +1348,7 @@ fn project_picker_dont_ask_again_sets_disable_flag() {
fn project_picker_recent_project_selection_uses_that_path() {
use crate::views::prompt_widget::StashedPrompt;
use crate::views::question_view::{LocalQuestionKind, QuestionSelection, QuestionViewState};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
let opt = |label: &str| QuestionOption {
label: label.into(),
description: String::new(),
@@ -24,7 +24,7 @@ fn model_with_support(id: &str, supports: bool) -> (acp::ModelId, acp::ModelInfo
}
fn models_with_current(supports: bool) -> ModelState {
let (id, info) = model_with_support("grok-build", supports);
let (id, info) = model_with_support("kigi", supports);
let mut models = ModelState::default();
models.available.insert(id.clone(), info);
models.current = Some(id);
@@ -227,7 +227,7 @@ fn set_default_model_allowed_when_agent_chat_kind() {
fn slash_model_valid_dispatches_set_default_model_with_switch_and_persist() {
let mut app = test_app_with_agent();
let id = AgentId(0);
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
app.agents
.get_mut(&id)
.unwrap()
@@ -236,9 +236,9 @@ fn slash_model_valid_dispatches_set_default_model_with_switch_and_persist() {
.available
.insert(
model_id.clone(),
acp::ModelInfo::new(model_id.clone(), "Grok 4.5".to_string()),
acp::ModelInfo::new(model_id.clone(), "Kigi 4.5".to_string()),
);
let effects = dispatch(Action::SendPrompt("/model Grok 4.5".into()), &mut app);
let effects = dispatch(Action::SendPrompt("/model Kigi 4.5".into()), &mut app);
assert_eq!(
effects.len(),
2,
@@ -904,8 +904,8 @@ fn clear_default_model_persists_but_keeps_live_current() {
use agent_client_protocol as acp;
use std::sync::Arc;
let mut app = test_app_with_agent();
let id = acp::ModelId::new(Arc::from("grok-test"));
let info = acp::ModelInfo::new(id.clone(), "Grok Test".to_string());
let id = acp::ModelId::new(Arc::from("kigi-test"));
let info = acp::ModelInfo::new(id.clone(), "Kigi Test".to_string());
let agent_id = AgentId(0);
app.agents
.get_mut(&agent_id)
@@ -949,8 +949,8 @@ fn set_default_model_resolves_known_name() {
use agent_client_protocol as acp;
use std::sync::Arc;
let mut app = test_app_with_agent();
let id = acp::ModelId::new(Arc::from("grok-4.5"));
let info = acp::ModelInfo::new(id.clone(), "Grok 4.5".to_string());
let id = acp::ModelId::new(Arc::from("kigi-4.5"));
let info = acp::ModelInfo::new(id.clone(), "Kigi 4.5".to_string());
let agent_id = AgentId(0);
app.agents
.get_mut(&agent_id)
@@ -963,7 +963,7 @@ fn set_default_model_resolves_known_name() {
assert_eq!(effects.len(), 2);
assert!(
matches!(& effects[0], Effect::PersistSetting { key : "default_model", value :
crate ::settings::SettingValue::String(s), .. } if s == "grok-4.5")
crate ::settings::SettingValue::String(s), .. } if s == "kigi-4.5")
);
assert!(matches!(& effects[1], Effect::SwitchModel { model_id : mid, .. } if mid == & id));
assert_eq!(app.agents[&agent_id].session.models.current, Some(id));
@@ -976,8 +976,8 @@ fn set_default_model_idempotent_when_already_current() {
use agent_client_protocol as acp;
use std::sync::Arc;
let mut app = test_app_with_agent();
let id = acp::ModelId::new(Arc::from("grok-already"));
let info = acp::ModelInfo::new(id.clone(), "Grok Already".to_string());
let id = acp::ModelId::new(Arc::from("kigi-already"));
let info = acp::ModelInfo::new(id.clone(), "Kigi Already".to_string());
let agent_id = AgentId(0);
app.agents
.get_mut(&agent_id)
@@ -2666,16 +2666,16 @@ fn dispatch_cycle_mode_refreshes_open_modal_snapshot() {
"current_value_for must read the refreshed snapshot",
);
}
/// `dispatch(Action::SetTheme("grokday"), &mut app)` emits
/// `dispatch(Action::SetTheme("kigiday"), &mut app)` emits
/// exactly one `Effect::PersistSetting`, mutates
/// `app.current_ui.theme`, fires a toast, and toggles AUTO_MODE
/// off (kind is concrete).
///
/// Note: we persist `grokday` (a non-truecolor theme) here
/// Note: we persist `kigiday` (a non-truecolor theme) here
/// because `Effect::PersistSetting`'s payload is `&'static str`
/// from the registry's canonical table — the persisted CANONICAL
/// is what we're asserting, NOT the live theme cache (which
/// `clamp_to_terminal` might fold to GrokNight in non-truecolor
/// `clamp_to_terminal` might fold to KigiNight in non-truecolor
/// test environments). Persist + canonical contract is the test
/// invariant; the cache contract is exercised separately by the
/// `*_applies_when_*` tests.
@@ -2685,8 +2685,8 @@ fn set_theme_emits_persist_setting_with_correct_payload() {
with_theme_test_env(|| {
let mut app = test_app_with_agent();
assert_eq!(app.current_ui.theme, None);
crate::theme::cache::set(crate::theme::ThemeKind::GrokNight);
let effects = dispatch(Action::SetTheme("grokday".into()), &mut app);
crate::theme::cache::set(crate::theme::ThemeKind::KigiNight);
let effects = dispatch(Action::SetTheme("kigiday".into()), &mut app);
assert_eq!(effects.len(), 1);
match &effects[0] {
Effect::PersistSetting {
@@ -2695,12 +2695,12 @@ fn set_theme_emits_persist_setting_with_correct_payload() {
rollback_value,
} => {
assert_eq!(*key, "theme");
assert_eq!(*value, SettingValue::Enum("grokday"));
assert_eq!(*rollback_value, SettingValue::Enum("groknight"));
assert_eq!(*value, SettingValue::Enum("kigiday"));
assert_eq!(*rollback_value, SettingValue::Enum("kiginight"));
}
other => panic!("expected PersistSetting, got {other:?}"),
}
assert_eq!(app.current_ui.theme.as_deref(), Some("grokday"));
assert_eq!(app.current_ui.theme.as_deref(), Some("kigiday"));
assert!(
!crate::theme::cache::is_auto_mode(),
"concrete theme commit must disable AUTO_MODE",
@@ -2708,7 +2708,7 @@ fn set_theme_emits_persist_setting_with_correct_payload() {
});
}
/// Same payload contract as `set_theme_emits_persist_setting_with_correct_payload`
/// for the auto-dark sibling. Uses `grokday` to avoid the
/// for the auto-dark sibling. Uses `kigiday` to avoid the
/// `clamp_to_terminal` ambiguity in non-truecolor test envs;
/// `apply_kind` doesn't fire here anyway (parent theme is not
/// auto by default) but using a non-truecolor canonical keeps
@@ -2718,7 +2718,7 @@ fn set_auto_dark_theme_emits_persist_setting_with_correct_payload() {
use crate::settings::SettingValue;
with_theme_test_env(|| {
let mut app = test_app_with_agent();
let effects = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
let effects = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
assert_eq!(effects.len(), 1);
match &effects[0] {
Effect::PersistSetting {
@@ -2727,12 +2727,12 @@ fn set_auto_dark_theme_emits_persist_setting_with_correct_payload() {
rollback_value,
} => {
assert_eq!(*key, "auto_dark_theme");
assert_eq!(*value, SettingValue::Enum("grokday"));
assert_eq!(*rollback_value, SettingValue::Enum("groknight"));
assert_eq!(*value, SettingValue::Enum("kigiday"));
assert_eq!(*rollback_value, SettingValue::Enum("kiginight"));
}
other => panic!("expected PersistSetting, got {other:?}"),
}
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("grokday"));
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kigiday"));
});
}
#[test]
@@ -2740,7 +2740,7 @@ fn set_auto_light_theme_emits_persist_setting_with_correct_payload() {
use crate::settings::SettingValue;
with_theme_test_env(|| {
let mut app = test_app_with_agent();
let effects = dispatch(Action::SetAutoLightTheme("groknight".into()), &mut app);
let effects = dispatch(Action::SetAutoLightTheme("kiginight".into()), &mut app);
assert_eq!(effects.len(), 1);
match &effects[0] {
Effect::PersistSetting {
@@ -2749,14 +2749,14 @@ fn set_auto_light_theme_emits_persist_setting_with_correct_payload() {
rollback_value,
} => {
assert_eq!(*key, "auto_light_theme");
assert_eq!(*value, SettingValue::Enum("groknight"));
assert_eq!(*rollback_value, SettingValue::Enum("grokday"));
assert_eq!(*value, SettingValue::Enum("kiginight"));
assert_eq!(*rollback_value, SettingValue::Enum("kigiday"));
}
other => panic!("expected PersistSetting, got {other:?}"),
}
assert_eq!(
app.current_ui.auto_light_theme.as_deref(),
Some("groknight"),
Some("kiginight"),
);
});
}
@@ -2809,11 +2809,11 @@ fn preview_auto_light_theme_emits_no_persist_and_no_current_ui_mutation() {
/// Auto-theme commit applies the live theme **only** when
/// `theme="auto"` AND the system is in the matching mode.
///
/// Scenario: `theme="groknight"` (concrete) + system=Dark. User
/// commits `auto_dark_theme="grokday"`. The setting is dormant
/// Scenario: `theme="kiginight"` (concrete) + system=Dark. User
/// commits `auto_dark_theme="kigiday"`. The setting is dormant
/// (parent theme is concrete, not auto), so the live display
/// must stay on GrokNight even though we're committing a
/// different theme. Uses `grokday` to avoid `clamp_to_terminal`
/// must stay on KigiNight even though we're committing a
/// different theme. Uses `kigiday` to avoid `clamp_to_terminal`
/// ambiguity in non-truecolor envs.
#[test]
fn set_auto_dark_theme_does_not_apply_when_theme_is_not_auto() {
@@ -2822,28 +2822,28 @@ fn set_auto_dark_theme_does_not_apply_when_theme_is_not_auto() {
crate::theme::system_appearance::SystemAppearance::Dark,
));
let mut app = test_app_with_agent();
let _ = dispatch(Action::SetTheme("groknight".into()), &mut app);
let _ = dispatch(Action::SetTheme("kiginight".into()), &mut app);
assert_eq!(
crate::theme::cache::current_kind(),
crate::theme::ThemeKind::GrokNight,
crate::theme::ThemeKind::KigiNight,
);
let _ = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
let _ = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
assert_eq!(
crate::theme::cache::current_kind(),
crate::theme::ThemeKind::GrokNight,
crate::theme::ThemeKind::KigiNight,
"auto_dark_theme commit must NOT change live display when theme is not auto",
);
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("grokday"));
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kigiday"));
});
}
/// Auto-theme commit DOES apply the live theme when both
/// (a) parent theme = auto AND (b) system matches.
///
/// Uses `GrokDay` (non-truecolor-requiring) for the dark-mode
/// Uses `KigiDay` (non-truecolor-requiring) for the dark-mode
/// fixture: the test environment's color detection may not report
/// truecolor support, and `Theme::apply_kind` clamps
/// truecolor-only themes (TokyoNight, RosePineMoon) down to
/// GrokNight. Using a non-truecolor theme avoids the clamp
/// KigiNight. Using a non-truecolor theme avoids the clamp
/// uncertainty. The "live apply" contract is what we're testing
/// — the specific theme picked is incidental.
#[test]
@@ -2857,21 +2857,21 @@ fn set_auto_dark_theme_applies_when_theme_is_auto_and_system_is_dark() {
assert!(crate::theme::cache::is_auto_mode());
assert_eq!(
crate::theme::cache::current_kind(),
crate::theme::ThemeKind::GrokNight,
crate::theme::ThemeKind::KigiNight,
);
let _ = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
let _ = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
assert_eq!(
crate::theme::cache::current_kind(),
crate::theme::ThemeKind::GrokDay,
crate::theme::ThemeKind::KigiDay,
"auto_dark_theme commit must update live display when theme=auto + system=Dark",
);
});
}
/// Auto-theme commit does NOT apply when system is in the
/// non-matching mode (auto_dark_theme + system=Light).
/// Uses `groknight` for the auto_dark_theme
/// Uses `kiginight` for the auto_dark_theme
/// value to avoid `clamp_to_terminal` ambiguity (we want a
/// concrete kind that's clearly different from GrokDay, the
/// concrete kind that's clearly different from KigiDay, the
/// active resolved theme).
#[test]
fn set_auto_dark_theme_does_not_apply_when_system_is_light() {
@@ -2883,20 +2883,20 @@ fn set_auto_dark_theme_does_not_apply_when_system_is_light() {
let _ = dispatch(Action::SetTheme("auto".into()), &mut app);
assert_eq!(
crate::theme::cache::current_kind(),
crate::theme::ThemeKind::GrokDay,
crate::theme::ThemeKind::KigiDay,
);
let _ = dispatch(Action::SetAutoDarkTheme("groknight".into()), &mut app);
let _ = dispatch(Action::SetAutoDarkTheme("kiginight".into()), &mut app);
assert_eq!(
crate::theme::cache::current_kind(),
crate::theme::ThemeKind::GrokDay,
crate::theme::ThemeKind::KigiDay,
"auto_dark_theme commit must NOT change live display when system=Light",
);
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("groknight"),);
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kiginight"),);
});
}
/// Symmetric to the dark test: `set_auto_light_theme` applies only
/// when theme=auto + system=Light. Uses a non-truecolor theme
/// (`groknight`) for the same clamp reason as the dark variant.
/// (`kiginight`) for the same clamp reason as the dark variant.
#[test]
fn set_auto_light_theme_applies_when_theme_is_auto_and_system_is_light() {
with_theme_test_env(|| {
@@ -2907,12 +2907,12 @@ fn set_auto_light_theme_applies_when_theme_is_auto_and_system_is_light() {
let _ = dispatch(Action::SetTheme("auto".into()), &mut app);
assert_eq!(
crate::theme::cache::current_kind(),
crate::theme::ThemeKind::GrokDay,
crate::theme::ThemeKind::KigiDay,
);
let _ = dispatch(Action::SetAutoLightTheme("groknight".into()), &mut app);
let _ = dispatch(Action::SetAutoLightTheme("kiginight".into()), &mut app);
assert_eq!(
crate::theme::cache::current_kind(),
crate::theme::ThemeKind::GrokNight,
crate::theme::ThemeKind::KigiNight,
"auto_light_theme must update display when theme=auto + system=Light",
);
});
@@ -2986,7 +2986,7 @@ fn set_auto_light_theme_rejects_auto_value() {
fn set_theme_toast_format_uses_display_name() {
with_theme_test_env(|| {
let mut app = test_app_with_agent();
let _ = dispatch(Action::SetTheme("grokday".into()), &mut app);
let _ = dispatch(Action::SetTheme("kigiday".into()), &mut app);
let toast = read_toast(&app);
assert!(
toast.contains("Theme"),
@@ -2994,7 +2994,7 @@ fn set_theme_toast_format_uses_display_name() {
);
assert!(
toast.contains("Kigi Day"),
"toast must use display name `Kigi Day`, not canonical `grokday`, got: {toast:?}",
"toast must use display name `Kigi Day`, not canonical `kigiday`, got: {toast:?}",
);
assert!(toast.contains('\u{2713}'), "toast must contain the ✓ glyph");
});
@@ -3003,7 +3003,7 @@ fn set_theme_toast_format_uses_display_name() {
fn set_auto_dark_theme_toast_format_uses_display_name() {
with_theme_test_env(|| {
let mut app = test_app_with_agent();
let _ = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
let _ = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
let toast = read_toast(&app);
assert!(toast.contains("Auto dark theme"));
assert!(toast.contains("Kigi Day"));
@@ -3014,7 +3014,7 @@ fn set_auto_dark_theme_toast_format_uses_display_name() {
fn set_auto_light_theme_toast_format_uses_display_name() {
with_theme_test_env(|| {
let mut app = test_app_with_agent();
let _ = dispatch(Action::SetAutoLightTheme("groknight".into()), &mut app);
let _ = dispatch(Action::SetAutoLightTheme("kiginight".into()), &mut app);
let toast = read_toast(&app);
assert!(toast.contains("Auto light theme"));
assert!(toast.contains("Kigi Night"));
@@ -3024,7 +3024,7 @@ fn set_auto_light_theme_toast_format_uses_display_name() {
/// reverts `app.current_ui.theme` AND the live cache (mirror of
/// `rollback_known_key_reverts_cache_and_no_effect`).
///
/// Uses non-truecolor themes (`grokday` ↔ `groknight`) to avoid
/// Uses non-truecolor themes (`kigiday` ↔ `kiginight`) to avoid
/// the `clamp_to_terminal` interaction in test environments that
/// don't report truecolor support.
#[test]
@@ -3032,28 +3032,28 @@ fn rollback_theme_reverts_current_ui_and_cache() {
use crate::settings::SettingValue;
with_theme_test_env(|| {
let mut app = test_app_with_agent();
let _ = dispatch(Action::SetTheme("grokday".into()), &mut app);
assert_eq!(app.current_ui.theme.as_deref(), Some("grokday"));
let _ = dispatch(Action::SetTheme("kigiday".into()), &mut app);
assert_eq!(app.current_ui.theme.as_deref(), Some("kigiday"));
assert_eq!(
crate::theme::cache::current_kind(),
crate::theme::ThemeKind::GrokDay,
crate::theme::ThemeKind::KigiDay,
);
let _ = dispatch(
Action::TaskComplete(TaskResult::SettingPersistFailed {
key: "theme",
rollback_value: SettingValue::Enum("groknight"),
rollback_value: SettingValue::Enum("kiginight"),
error: "disk full".into(),
}),
&mut app,
);
assert_eq!(
app.current_ui.theme.as_deref(),
Some("groknight"),
Some("kiginight"),
"rollback must update app.current_ui.theme",
);
assert_eq!(
crate::theme::cache::current_kind(),
crate::theme::ThemeKind::GrokNight,
crate::theme::ThemeKind::KigiNight,
"rollback must update the live theme cache too",
);
});
@@ -3063,17 +3063,17 @@ fn rollback_auto_dark_theme_reverts_current_ui() {
use crate::settings::SettingValue;
with_theme_test_env(|| {
let mut app = test_app_with_agent();
let _ = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("grokday"));
let _ = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kigiday"));
let _ = dispatch(
Action::TaskComplete(TaskResult::SettingPersistFailed {
key: "auto_dark_theme",
rollback_value: SettingValue::Enum("groknight"),
rollback_value: SettingValue::Enum("kiginight"),
error: "disk full".into(),
}),
&mut app,
);
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("groknight"),);
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kiginight"),);
});
}
#[test]
@@ -3081,20 +3081,20 @@ fn rollback_auto_light_theme_reverts_current_ui() {
use crate::settings::SettingValue;
with_theme_test_env(|| {
let mut app = test_app_with_agent();
let _ = dispatch(Action::SetAutoLightTheme("groknight".into()), &mut app);
let _ = dispatch(Action::SetAutoLightTheme("kiginight".into()), &mut app);
assert_eq!(
app.current_ui.auto_light_theme.as_deref(),
Some("groknight"),
Some("kiginight"),
);
let _ = dispatch(
Action::TaskComplete(TaskResult::SettingPersistFailed {
key: "auto_light_theme",
rollback_value: SettingValue::Enum("grokday"),
rollback_value: SettingValue::Enum("kigiday"),
error: "disk full".into(),
}),
&mut app,
);
assert_eq!(app.current_ui.auto_light_theme.as_deref(), Some("grokday"));
assert_eq!(app.current_ui.auto_light_theme.as_deref(), Some("kigiday"));
});
}
/// Edge case — if the rollback value is
@@ -3109,8 +3109,8 @@ fn rollback_auto_dark_theme_with_auto_value_clears_to_none() {
use crate::settings::SettingValue;
with_theme_test_env(|| {
let mut app = test_app_with_agent();
let _ = dispatch(Action::SetAutoDarkTheme("grokday".into()), &mut app);
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("grokday"));
let _ = dispatch(Action::SetAutoDarkTheme("kigiday".into()), &mut app);
assert_eq!(app.current_ui.auto_dark_theme.as_deref(), Some("kigiday"));
let _ = dispatch(
Action::TaskComplete(TaskResult::SettingPersistFailed {
key: "auto_dark_theme",
@@ -3131,10 +3131,10 @@ fn rollback_auto_light_theme_with_auto_value_clears_to_none() {
use crate::settings::SettingValue;
with_theme_test_env(|| {
let mut app = test_app_with_agent();
let _ = dispatch(Action::SetAutoLightTheme("groknight".into()), &mut app);
let _ = dispatch(Action::SetAutoLightTheme("kiginight".into()), &mut app);
assert_eq!(
app.current_ui.auto_light_theme.as_deref(),
Some("groknight"),
Some("kiginight"),
);
let _ = dispatch(
Action::TaskComplete(TaskResult::SettingPersistFailed {
@@ -198,16 +198,16 @@ fn dispatch_confirm_reset_setting_reset_dispatches_typed_setter_for_shared_enum(
&mut app,
);
// Reset → SetTheme("groknight") (the registered default).
// Reset → SetTheme("kiginight") (the registered default).
assert_eq!(effects.len(), 1);
match &effects[0] {
Effect::PersistSetting { key, value, .. } => {
assert_eq!(*key, "theme");
assert_eq!(value, &SettingValue::Enum("groknight"));
assert_eq!(value, &SettingValue::Enum("kiginight"));
}
other => panic!("expected PersistSetting, got {other:?}"),
}
assert_eq!(app.current_ui.theme.as_deref(), Some("groknight"));
assert_eq!(app.current_ui.theme.as_deref(), Some("kiginight"));
}
#[test]
@@ -421,7 +421,7 @@ fn uninstall_result_notice_is_footer_only_not_row_anchored() {
);
}
/// Regression (Bugbot): a failed `x.ai/subagent/cancel` RPC must NOT
/// Regression (Bugbot): a failed `kigi/subagent/cancel` RPC must NOT
/// finalize the row — the subagent may still be running. Only a shell
/// response of "nothing live" finalizes it.
#[test]
@@ -509,7 +509,7 @@ fn cancel_complete_does_nothing() {
fn switch_model_complete_success_updates_model_and_pushes_message() {
let mut app = test_app_with_agent();
let id = AgentId(0);
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
// Set up available models so the display name can be resolved.
app.agents
@@ -520,7 +520,7 @@ fn switch_model_complete_success_updates_model_and_pushes_message() {
.available
.insert(
model_id.clone(),
acp::ModelInfo::new(model_id.clone(), "Grok 4.5".to_string()),
acp::ModelInfo::new(model_id.clone(), "Kigi 4.5".to_string()),
);
app.agents
.get_mut(&id)
@@ -562,12 +562,12 @@ fn switch_model_complete_success_updates_model_and_pushes_message() {
fn switch_model_complete_skips_message_and_persist_when_unchanged() {
let mut app = test_app_with_agent();
let id = AgentId(0);
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
let agent = app.agents.get_mut(&id).unwrap();
agent.session.models.available.insert(
model_id.clone(),
acp::ModelInfo::new(model_id.clone(), "Grok 4.5".to_string()),
acp::ModelInfo::new(model_id.clone(), "Kigi 4.5".to_string()),
);
agent.session.models.current = Some(model_id.clone());
agent.session.models.reasoning_effort = None;
@@ -667,7 +667,7 @@ fn switch_to_non_reasoning_model_clears_persisted_effort() {
use kigi_shell::sampling::types::ReasoningEffort;
let mut app = test_app_with_agent();
let id = AgentId(0);
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
// Simulate prior reasoning effort from a previous model.
app.agents
@@ -686,7 +686,7 @@ fn switch_to_non_reasoning_model_clears_persisted_effort() {
.available
.insert(
model_id.clone(),
acp::ModelInfo::new(model_id.clone(), "Grok Build".to_string()),
acp::ModelInfo::new(model_id.clone(), "Kigi".to_string()),
);
app.agents
.get_mut(&id)
@@ -773,7 +773,7 @@ fn switch_model_incompatible_agent_shows_question_modal() {
let err = kigi_shell::agent::config::ModelSwitchIncompatibleAgentError {
code: "MODEL_SWITCH_INCOMPATIBLE_AGENT".into(),
active_agent_type: "grok-build".into(),
active_agent_type: "kigi".into(),
required_agent_type: "cursor".into(),
model_id: "cursor-model".into(),
suggestion: "start_new_session".into(),
@@ -836,7 +836,7 @@ fn incompatible_agent_rollback_restores_previous_model() {
let err = kigi_shell::agent::config::ModelSwitchIncompatibleAgentError {
code: "MODEL_SWITCH_INCOMPATIBLE_AGENT".into(),
active_agent_type: "grok-build".into(),
active_agent_type: "kigi".into(),
required_agent_type: "cursor".into(),
model_id: "cursor-model".into(),
suggestion: "start_new_session".into(),
@@ -882,7 +882,7 @@ fn incompatible_agent_closes_active_modal() {
let err = kigi_shell::agent::config::ModelSwitchIncompatibleAgentError {
code: "MODEL_SWITCH_INCOMPATIBLE_AGENT".into(),
active_agent_type: "grok-build".into(),
active_agent_type: "kigi".into(),
required_agent_type: "cursor".into(),
model_id: "cursor-model".into(),
suggestion: "start_new_session".into(),
@@ -919,19 +919,19 @@ fn same_agent_type_switch_no_modal() {
// should succeed normally — no modal, no IncompatibleAgent error.
let mut app = test_app_with_agent();
let id = AgentId(0);
let model_a = acp::ModelId::new(std::sync::Arc::from("grok-build-a"));
let model_b = acp::ModelId::new(std::sync::Arc::from("grok-build-b"));
let model_a = acp::ModelId::new(std::sync::Arc::from("kigi-a"));
let model_b = acp::ModelId::new(std::sync::Arc::from("kigi-b"));
// Add both models to the catalog (no agentType → both use grok-build).
// Add both models to the catalog (no agentType → both use kigi).
let agent = app.agents.get_mut(&id).unwrap();
agent.session.models.available.insert(
model_a.clone(),
acp::ModelInfo::new(model_a.clone(), "Grok Build A".to_string()),
acp::ModelInfo::new(model_a.clone(), "Kigi A".to_string()),
);
agent.session.models.set_current(model_a, None);
agent.session.models.available.insert(
model_b.clone(),
acp::ModelInfo::new(model_b.clone(), "Grok Build B".to_string()),
acp::ModelInfo::new(model_b.clone(), "Kigi B".to_string()),
);
agent.session.model_switch_pending = true;
@@ -963,7 +963,7 @@ fn switch_model_pending_lifecycle() {
// Full lifecycle: false -> dispatch SwitchModel -> true -> SwitchModelComplete -> false
let mut app = test_app_with_agent();
let id = AgentId(0);
let model_id = acp::ModelId::new(std::sync::Arc::from("grok-4.5"));
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
// Initially false.
assert!(!app.agents[&id].session.model_switch_pending);
@@ -246,7 +246,7 @@ fn plugins_list_response() -> kigi_hooks_plugins_types::PluginsListResponse {
plugins: vec![
test_plugin_info(
"user-tool",
Some(kigi_hooks_plugins_types::PluginOrigin::UserGrok),
Some(kigi_hooks_plugins_types::PluginOrigin::UserKigi),
),
test_plugin_info(
"claude-tool",
@@ -160,7 +160,7 @@ fn cancel_turn_leaves_shared_queue_for_agent_to_drain() {
// shared queue (broadcast to all attached clients). The agent owns the
// drain: on cancel the FRONT queued prompt runs next (promoted
// server-side), so the pager must NOT pull it back into the input or
// mutate the queue locally — the `x.ai/queue/changed` rebroadcast is the
// mutate the queue locally — the `kigi/queue/changed` rebroadcast is the
// source of truth.
let mut app = test_app_with_agent();
let id = AgentId(0);
@@ -202,7 +202,7 @@ pub(crate) fn dispatch_open_transcript_pager(app: &mut AppView) {
return;
};
let path = std::env::temp_dir().join(format!("grok-transcript-{}.md", uuid::Uuid::new_v4()));
let path = std::env::temp_dir().join(format!("kigi-transcript-{}.md", uuid::Uuid::new_v4()));
match std::fs::write(&path, content) {
Ok(()) => {
app.pending_pager_path = Some(path);
@@ -636,7 +636,7 @@ pub(super) fn handle_skills_toggle_done(
}
}
}
// The toggle effect already called x.ai/skills/refresh-baseline
// The toggle effect already called kigi/skills/refresh-baseline
// which triggers the session to reload skills and push an
// AvailableCommandsUpdate notification with the updated list.
vec![]
@@ -258,7 +258,7 @@ pub(super) fn do_cancel_turn(app: &mut AppView, cancel_subagents: bool) -> Vec<E
// Server-authoritative queue: the agent owns the drain. On an interactive
// cancel we only tear down the running turn and let the agent promote the
// FRONT queued prompt as the next turn — its `x.ai/queue/changed`
// FRONT queued prompt as the next turn — its `kigi/queue/changed`
// rebroadcast (carrying `running_prompt_id`) is the source of truth, and the
// pager adopts it via `handle_queue_changed` / `apply_turn_start_shim`. We
// do NOT pull any queued prompt back into the input or predict the new queue
@@ -277,7 +277,7 @@ pub(super) fn do_cancel_turn(app: &mut AppView, cancel_subagents: bool) -> Vec<E
}]
}
/// Grace window between a driver-side `x.ai/session/prompt_complete`
/// Grace window between a driver-side `kigi/session/prompt_complete`
/// broadcast and that turn's `session/prompt` RPC response, after which
/// [`reconcile_overdue_turn_ends`] finishes the turn from the broadcast. The
/// healthy-path gap is milliseconds (the shell emits the broadcast just
@@ -285,7 +285,7 @@ pub(super) fn do_cancel_turn(app: &mut AppView, cancel_subagents: bool) -> Vec<E
/// genuinely lost, not merely slow.
pub(crate) const TURN_END_RECONCILE_GRACE: std::time::Duration = std::time::Duration::from_secs(2);
/// Finish turns whose end was announced by `x.ai/session/prompt_complete`
/// Finish turns whose end was announced by `kigi/session/prompt_complete`
/// but whose `session/prompt` RPC response never arrived.
///
/// The RPC response is the driver's only turn-state exit, and it can be lost
@@ -103,7 +103,7 @@ pub(super) fn parse_session_load_restore_meta(
.and_then(|v| serde_json::from_value(v).ok());
(code_restored, restore_summary, restore_degree)
}
/// CANONICAL wire parser for `LoadSessionResponse._meta["x.ai/runningPromptId"]`.
/// CANONICAL wire parser for `LoadSessionResponse._meta["kigi/runningPromptId"]`.
///
/// Returns the session's in-flight running prompt id when the session was
/// loaded MID-turn (some other client is driving), otherwise `None`. The
@@ -114,7 +114,7 @@ pub(crate) fn parse_session_load_running_prompt_id(
resp_meta: Option<&acp::Meta>,
) -> Option<String> {
resp_meta
.and_then(|m| m.get("x.ai/runningPromptId"))
.and_then(|m| m.get("kigi/runningPromptId"))
.and_then(|v| v.as_str())
.map(String::from)
}
@@ -133,7 +133,7 @@ pub(crate) fn sanitize_user_error(raw: &str) -> String {
("inference_api", "server"),
("research-api", "server"),
("research_api", "server"),
("grok-code-backend", "server"),
("kigi-code-backend", "server"),
("ACP error:", "error:"),
("ACP request failed:", "request failed:"),
("JSON-RPC error", "request error"),
@@ -165,17 +165,17 @@ pub(crate) fn sanitize_user_error(raw: &str) -> String {
///
/// | plan | subagents | ask-user | agentProfile | askUserQuestion |
/// |-------|-----------|----------|--------------------------------|--------------------|
/// | false | false | false | `grok-build` (default) | `false` |
/// | false | true | false | `grok-build` (default) | `false` |
/// | false | false | true | `grok-build-ask-user` | omitted (shell gate) |
/// | false | true | true | `grok-build-ask-user` | omitted (shell gate) |
/// | true | false | false | `grok-build-plan-no-subagents` | `false` |
/// | true | true | false | `grok-build-plan` | `false` |
/// | true | false | true | `grok-build-plan-no-subagents` | omitted (shell gate) |
/// | true | true | true | `grok-build-plan` | omitted (shell gate) |
/// | false | false | false | `kigi` (default) | `false` |
/// | false | true | false | `kigi` (default) | `false` |
/// | false | false | true | `kigi-ask-user` | omitted (shell gate) |
/// | false | true | true | `kigi-ask-user` | omitted (shell gate) |
/// | true | false | false | `kigi-plan-no-subagents` | `false` |
/// | true | true | false | `kigi-plan` | `false` |
/// | true | false | true | `kigi-plan-no-subagents` | omitted (shell gate) |
/// | true | true | true | `kigi-plan` | omitted (shell gate) |
///
/// When [`Self::chat_mode`] is set (gateway light-frontend / `--chat`), Build
/// `agentProfile` injection is omitted (K12) and `_meta["x.ai/session"].kind`
/// `agentProfile` injection is omitted (K12) and `_meta["kigi/session"].kind`
/// is stamped `"chat"` so the shell takes `require_gateway` / thin profile.
#[derive(Debug, Clone, Default)]
pub(crate) struct SessionFlags {
@@ -183,7 +183,7 @@ pub(crate) struct SessionFlags {
pub subagents: bool,
pub ask_user: bool,
/// Restore code state on resume (`--restore-code`).
/// Injected as `x.ai/restore_code` into `LoadSession` meta, or passed
/// Injected as `kigi/restore_code` into `LoadSession` meta, or passed
/// as `restoreCode` in the `resume_session` ACP payload for worktrees.
pub restore_code: Option<bool>,
pub agent_override: Option<serde_json::Value>,
@@ -208,7 +208,7 @@ pub(crate) struct SessionFlags {
impl SessionFlags {
/// Resolve the agent profile name from the flags.
///
/// Returns `None` for the default `grok-build` profile (no `_meta`
/// Returns `None` for the default `kigi` profile (no `_meta`
/// needed; it already includes TaskTool). Chat mode never injects a
/// Build profile (remote owns agent behavior).
pub(super) fn agent_profile(&self) -> Option<&'static str> {
@@ -216,9 +216,9 @@ impl SessionFlags {
return None;
}
match (self.plan_mode, self.subagents, self.ask_user) {
(true, true, _) => Some("grok-build-plan"),
(true, false, _) => Some("grok-build-plan-no-subagents"),
(false, _, true) => Some("grok-build-ask-user"),
(true, true, _) => Some("kigi-plan"),
(true, false, _) => Some("kigi-plan-no-subagents"),
(false, _, true) => Some("kigi-ask-user"),
(false, _, false) => None,
}
}
@@ -229,7 +229,7 @@ impl SessionFlags {
/// emit-site comment below). `--no-ask-user` always forces
/// `askUserQuestion: false` into the meta, even when paired with
/// `KIGI_AGENT` — the env var chooses the *agent*, but the tool-strip is
/// independent. Chat mode additionally stamps `x.ai/session.kind`.
/// independent. Chat mode additionally stamps `kigi/session.kind`.
pub(super) fn to_meta(&self) -> Option<acp::Meta> {
let mut meta = serde_json::Map::new();
if self.chat_mode {
@@ -247,7 +247,7 @@ impl SessionFlags {
meta.insert("agentProfile".into(), serde_json::json!(profile));
}
if self.chat_mode {
meta.insert("x.ai/session".into(), serde_json::json!({ "kind" : "chat" }));
meta.insert("kigi/session".into(), serde_json::json!({ "kind" : "chat" }));
}
if !self.ask_user {
meta.insert("askUserQuestion".into(), serde_json::json!(false));
@@ -266,13 +266,13 @@ impl SessionFlags {
/// workspace for `kind=chat`; the client must not bind Direct/envId/attach.
pub(super) const CHAT_FORBIDDEN_WORKSPACE_BIND_KEYS: &[&str] = &[
"envId",
"x.ai/cloud_server_id",
"x.ai/cloud_existing_workspace",
"kigi/cloud_server_id",
"kigi/cloud_existing_workspace",
];
/// Stamp `_meta["x.ai/session"].kind = "chat"` and strip Build `agentProfile` (K12).
/// Stamp `_meta["kigi/session"].kind = "chat"` and strip Build `agentProfile` (K12).
pub(super) fn apply_chat_kind_meta(meta: &mut Option<acp::Meta>) {
let obj = meta.get_or_insert_with(acp::Meta::new);
obj.insert("x.ai/session".into(), serde_json::json!({ "kind" : "chat" }));
obj.insert("kigi/session".into(), serde_json::json!({ "kind" : "chat" }));
obj.remove("agentProfile");
}
/// Remove client workspace-bind keys from chat create/load meta (defense in depth).
@@ -360,7 +360,7 @@ pub(super) fn count_chat_history_stats(history_path: &Path) -> (usize, usize) {
}
(turn_count, tool_call_count)
}
/// Parse the `x.ai/session/list` response payload (the unwrapped
/// Parse the `kigi/session/list` response payload (the unwrapped
/// `{ "sessions": [...] }` object) into [`SessionPickerEntry`] rows.
///
/// Shared by the resume picker ([`Effect::FetchSessionList`]) and the
@@ -399,7 +399,7 @@ pub(super) fn parse_session_picker_entries(
.map(String::from);
let is_conversation = v
.get("_meta")
.and_then(|m| m.get("x.ai/session"))
.and_then(|m| m.get("kigi/session"))
.and_then(|s| s.get("kind"))
.and_then(|k| k.as_str()) == Some("chat");
let parsed_updated: Option<chrono::DateTime<chrono::Utc>> = v
@@ -548,7 +548,7 @@ pub(super) fn session_picker_entry_to_roster(
}
pub(super) async fn send_logout(tx: &AcpAgentTx) {
let req = acp::ExtRequest::new(
"x.ai/auth/logout",
"kigi/auth/logout",
serde_json::value::to_raw_value(&serde_json::json!({}))
.expect("serialize auth/logout params")
.into(),
@@ -927,7 +927,7 @@ pub(crate) async fn persist_setting(
/// Body for `Effect::PersistPermissionMode`. Factored out for testability.
///
/// 1. Persist `ui.permission_mode` to disk.
/// 2. Fire ACP `x.ai/yolo_mode_changed` (gated on disk success for
/// 2. Fire ACP `kigi/yolo_mode_changed` (gated on disk success for
/// `WithRollback`; always for `BestEffort`).
/// 3. Return the matching `TaskResult`.
pub(crate) async fn persist_permission_mode_and_notify(
@@ -951,7 +951,7 @@ pub(crate) async fn persist_permission_mode_and_notify(
config_str, }
);
let notification = acp::ExtNotification::new(
"x.ai/yolo_mode_changed",
"kigi/yolo_mode_changed",
serde_json::value::to_raw_value(&params)
.expect("serialize yolo_mode_changed params")
.into(),
@@ -962,7 +962,7 @@ pub(crate) async fn persist_permission_mode_and_notify(
}
route_permission_mode_result(disk_outcome, persist, config_str)
}
/// Whether to fire the ACP `x.ai/yolo_mode_changed` notification.
/// Whether to fire the ACP `kigi/yolo_mode_changed` notification.
/// `WithRollback` suppresses on disk failure (agent must not see the
/// optimistic value). `BestEffort` always fires.
pub(super) fn should_send_yolo_acp_notification(
@@ -975,7 +975,7 @@ pub(super) fn should_send_yolo_acp_notification(
(Err(_), PermissionModePersist::WithRollback(_)) => false,
}
}
/// Extract the typed kill outcome from an `x.ai/task/kill` ext response.
/// Extract the typed kill outcome from an `kigi/task/kill` ext response.
///
/// The agent serializes `ExtMethodResult<KillTaskResponse>`, so the outcome
/// lives at `result.outcome` (`{"result":{"taskId":..,"outcome":
@@ -997,7 +997,7 @@ pub(super) fn parse_kill_outcome(
.and_then(|envelope| envelope.result)
.map(|payload| payload.outcome)
}
/// Map an `x.ai/subagent/cancel` response (payload under `result`) to a kill
/// Map an `kigi/subagent/cancel` response (payload under `result`) to a kill
/// outcome. Prefers the typed `outcome`; falls back to the legacy `cancelled`
/// bool for an older shell or an unknown future `kind`. An error/unparseable
/// body is `RpcFailed` (subagent may still be running — leave the row alone).
@@ -1092,7 +1092,7 @@ pub(super) fn persist_hint(
TaskResult::CancelComplete
});
}
/// Parse an `x.ai/billing` ext response body (the unwrapped `result`
/// Parse an `kigi/billing` ext response body (the unwrapped `result`
/// payload) into Kimi usage rows. A body that fails to deserialize is an
/// error, not an empty quota list, so a malformed response can't render
/// as "no usage data".
+56 -56
View File
@@ -200,7 +200,7 @@ pub(crate) fn execute(
if chat_kind || session_flags.chat_mode {
meta.get_or_insert_with(acp::Meta::new)
.insert(
"x.ai/session".into(),
"kigi/session".into(),
serde_json::json!({ "kind" : "chat" }),
);
}
@@ -238,7 +238,7 @@ pub(crate) fn execute(
payload["gitRef"] = serde_json::Value::String(r.clone());
}
let ext_req = acp::ExtRequest::new(
"x.ai/git/worktree/resume_session",
"kigi/git/worktree/resume_session",
serde_json::value::to_raw_value(&payload)
.expect("serialize resume params")
.into(),
@@ -342,7 +342,7 @@ pub(crate) fn execute(
params["gitRef"] = serde_json::Value::String(r.clone());
}
let ext_req = acp::ExtRequest::new(
"x.ai/git/worktree/create_from_worktree_sync",
"kigi/git/worktree/create_from_worktree_sync",
serde_json::value::to_raw_value(&params)
.expect("serialize worktree params")
.into(),
@@ -470,7 +470,7 @@ pub(crate) fn execute(
}
if let Some(true) = session_flags.restore_code {
meta.get_or_insert_with(acp::Meta::new)
.insert("x.ai/restore_code".into(), serde_json::Value::Bool(true));
.insert("kigi/restore_code".into(), serde_json::Value::Bool(true));
}
let cwd = session_cwd.unwrap_or_else(|| cwd.to_path_buf());
let mcp_started = std::time::Instant::now();
@@ -670,7 +670,7 @@ pub(crate) fn execute(
params["query"] = serde_json::Value::String(q.clone());
}
let request = acp::ExtRequest::new(
"x.ai/session/list",
"kigi/session/list",
serde_json::value::to_raw_value(&params)
.expect("serialize session list params")
.into(),
@@ -725,7 +725,7 @@ pub(crate) fn execute(
tasks
.spawn(async move {
let request = acp::ExtRequest::new(
"x.ai/sessions/list",
"kigi/sessions/list",
serde_json::value::to_raw_value(&serde_json::json!({}))
.expect("serialize roster list params")
.into(),
@@ -743,7 +743,7 @@ pub(crate) fn execute(
}
None => {
tracing::warn!(
"failed to parse x.ai/sessions/list response"
"failed to parse kigi/sessions/list response"
);
TaskResult::RosterFailed {
error: "parse error".to_string(),
@@ -768,7 +768,7 @@ pub(crate) fn execute(
{ "cwd" : cwd.to_string_lossy(), "limit" : 30, }
);
let request = acp::ExtRequest::new(
"x.ai/session/list",
"kigi/session/list",
serde_json::value::to_raw_value(&params)
.expect("serialize session list params")
.into(),
@@ -1211,7 +1211,7 @@ pub(crate) fn execute(
{ "sessionId" : session_id.0.to_string(), }
);
let notification = acp::ExtNotification::new(
"x.ai/toggle_plan_mode",
"kigi/toggle_plan_mode",
serde_json::value::to_raw_value(&params)
.expect("serialize toggle_plan_mode params")
.into(),
@@ -1233,7 +1233,7 @@ pub(crate) fn execute(
"expectedVersion" : expected_version, }
);
let notification = acp::ExtNotification::new(
"x.ai/queue/remove",
"kigi/queue/remove",
serde_json::value::to_raw_value(&params)
.expect("serialize queue/remove params")
.into(),
@@ -1253,7 +1253,7 @@ pub(crate) fn execute(
ordered_ids, }
);
let notification = acp::ExtNotification::new(
"x.ai/queue/reorder",
"kigi/queue/reorder",
serde_json::value::to_raw_value(&params)
.expect("serialize queue/reorder params")
.into(),
@@ -1272,7 +1272,7 @@ pub(crate) fn execute(
{ "sessionId" : session_id.0.to_string(), }
);
let notification = acp::ExtNotification::new(
"x.ai/queue/clear",
"kigi/queue/clear",
serde_json::value::to_raw_value(&params)
.expect("serialize queue/clear params")
.into(),
@@ -1292,7 +1292,7 @@ pub(crate) fn execute(
new_text, }
);
let notification = acp::ExtNotification::new(
"x.ai/queue/edit",
"kigi/queue/edit",
serde_json::value::to_raw_value(&params)
.expect("serialize queue/edit params")
.into(),
@@ -1315,7 +1315,7 @@ pub(crate) fn execute(
params["newText"] = serde_json::Value::String(new_text);
}
let notification = acp::ExtNotification::new(
"x.ai/queue/interject",
"kigi/queue/interject",
serde_json::value::to_raw_value(&params)
.expect("serialize queue/interject params")
.into(),
@@ -1396,7 +1396,7 @@ pub(crate) fn execute(
{ "sessionId" : session_id.0.to_string(), }
);
let req = acp::ExtRequest::new(
"x.ai/compact_conversation",
"kigi/compact_conversation",
serde_json::value::to_raw_value(&params)
.expect("serialize compact params")
.into(),
@@ -1419,7 +1419,7 @@ pub(crate) fn execute(
session_id, }
);
let req = acp::ExtRequest::new(
"x.ai/prompt_history",
"kigi/prompt_history",
serde_json::value::to_raw_value(&params)
.expect("serialize prompt_history params")
.into(),
@@ -1467,7 +1467,7 @@ pub(crate) fn execute(
task_id: task_id.clone(),
};
let req = acp::ExtRequest::new(
"x.ai/task/kill",
"kigi/task/kill",
serde_json::value::to_raw_value(&params)
.expect("serialize kill params")
.into(),
@@ -1500,7 +1500,7 @@ pub(crate) fn execute(
subagent_id, }
);
let req = acp::ExtRequest::new(
"x.ai/subagent/cancel",
"kigi/subagent/cancel",
serde_json::value::to_raw_value(&params)
.expect("serialize cancel params")
.into(),
@@ -1527,7 +1527,7 @@ pub(crate) fn execute(
{ "sessionId" : session_id.0.to_string(), "taskId" : task_id, }
);
let req = acp::ExtRequest::new(
"x.ai/scheduler/delete",
"kigi/scheduler/delete",
serde_json::value::to_raw_value(&params)
.expect("serialize scheduler delete params")
.into(),
@@ -1547,7 +1547,7 @@ pub(crate) fn execute(
tool_call_id, }
);
let req = acp::ExtRequest::new(
"x.ai/terminal/background",
"kigi/terminal/background",
serde_json::value::to_raw_value(&params)
.expect("serialize background params")
.into(),
@@ -1862,7 +1862,7 @@ pub(crate) fn execute(
}
let params = serde_json::json!({});
let req = acp::ExtRequest::new(
"x.ai/auth/get_url",
"kigi/auth/get_url",
serde_json::value::to_raw_value(&params)
.expect("serialize auth_url params")
.into(),
@@ -1901,7 +1901,7 @@ pub(crate) fn execute(
.spawn(async move {
let params = serde_json::json!({ "code" : code });
let req = acp::ExtRequest::new(
"x.ai/auth/submit_code",
"kigi/auth/submit_code",
serde_json::value::to_raw_value(&params)
.expect("serialize auth code params")
.into(),
@@ -1935,7 +1935,7 @@ pub(crate) fn execute(
{ "sessionId" : session_id.0.to_string(), "cache" : cache, }
);
let req = acp::ExtRequest::new(
"x.ai/mcp/list",
"kigi/mcp/list",
serde_json::value::to_raw_value(&params)
.expect("serialize mcp/list params")
.into(),
@@ -1976,7 +1976,7 @@ pub(crate) fn execute(
server_name, }
);
let req = acp::ExtRequest::new(
"x.ai/mcp/auth_trigger",
"kigi/mcp/auth_trigger",
serde_json::value::to_raw_value(&params)
.expect("serialize mcp/auth_trigger params")
.into(),
@@ -2024,7 +2024,7 @@ pub(crate) fn execute(
{ "sessionId" : session_id.0.to_string(), }
);
let req = acp::ExtRequest::new(
"x.ai/hooks/list",
"kigi/hooks/list",
serde_json::value::to_raw_value(&params)
.expect("serialize hooks/list params")
.into(),
@@ -2061,7 +2061,7 @@ pub(crate) fn execute(
{ "sessionId" : session_id.0.to_string(), }
);
let req = acp::ExtRequest::new(
"x.ai/plugins/list",
"kigi/plugins/list",
serde_json::value::to_raw_value(&params)
.expect("serialize plugins/list params")
.into(),
@@ -2099,7 +2099,7 @@ pub(crate) fn execute(
action,
};
let req = acp::ExtRequest::new(
"x.ai/hooks/action",
"kigi/hooks/action",
serde_json::value::to_raw_value(&req_body)
.expect("serialize hooks/action params")
.into(),
@@ -2139,7 +2139,7 @@ pub(crate) fn execute(
action,
};
let req = acp::ExtRequest::new(
"x.ai/plugins/action",
"kigi/plugins/action",
serde_json::value::to_raw_value(&req_body)
.expect("serialize plugins/action params")
.into(),
@@ -2176,7 +2176,7 @@ pub(crate) fn execute(
.spawn(async move {
let params = serde_json::json!({ "cwd" : "." });
let req = acp::ExtRequest::new(
"x.ai/skills/list",
"kigi/skills/list",
serde_json::value::to_raw_value(&params)
.expect("serialize skills/list params")
.into(),
@@ -2215,7 +2215,7 @@ pub(crate) fn execute(
{ "name" : skill_name, "enabled" : enabled, "cwd" : ".", }
);
let req = acp::ExtRequest::new(
"x.ai/skills/toggle",
"kigi/skills/toggle",
serde_json::value::to_raw_value(&params)
.expect("serialize skills/toggle params")
.into(),
@@ -2235,7 +2235,7 @@ pub(crate) fn execute(
.map_err(|_| "couldn't toggle skill".to_string());
if parsed.is_ok() {
let refresh = acp::ExtRequest::new(
"x.ai/skills/refresh-baseline",
"kigi/skills/refresh-baseline",
serde_json::value::to_raw_value(&serde_json::json!({}))
.expect("serialize empty params")
.into(),
@@ -2273,7 +2273,7 @@ pub(crate) fn execute(
config: *config,
};
let req = acp::ExtRequest::new(
"x.ai/mcp/upsert",
"kigi/mcp/upsert",
serde_json::value::to_raw_value(&req_body)
.expect("serialize mcp/upsert params")
.into(),
@@ -2308,7 +2308,7 @@ pub(crate) fn execute(
server_name,
};
let req = acp::ExtRequest::new(
"x.ai/mcp/delete",
"kigi/mcp/delete",
serde_json::value::to_raw_value(&req_body)
.expect("serialize mcp/delete params")
.into(),
@@ -2337,7 +2337,7 @@ pub(crate) fn execute(
server_name, "enabled" : enabled, }
);
let req = acp::ExtRequest::new(
"x.ai/mcp/toggle",
"kigi/mcp/toggle",
serde_json::value::to_raw_value(&params)
.expect("serialize mcp/toggle params")
.into(),
@@ -2367,7 +2367,7 @@ pub(crate) fn execute(
server_name, "tool_name" : tool_name, "enabled" : enabled, }
);
let req = acp::ExtRequest::new(
"x.ai/mcp/toggle_tool",
"kigi/mcp/toggle_tool",
serde_json::value::to_raw_value(&params)
.expect("serialize mcp/toggle_tool params")
.into(),
@@ -2446,7 +2446,7 @@ pub(crate) fn execute(
cwd: String,
}
let request = acp::ExtRequest::new(
"x.ai/session/rename",
"kigi/session/rename",
serde_json::value::to_raw_value(
&RenameRequest {
session_id: session_id.0.to_string(),
@@ -2503,7 +2503,7 @@ pub(crate) fn execute(
cwd: String,
}
let request = acp::ExtRequest::new(
"x.ai/session/delete",
"kigi/session/delete",
serde_json::value::to_raw_value(
&DeleteRequest {
session_id: session_id.clone(),
@@ -2605,7 +2605,7 @@ pub(crate) fn execute(
}
};
let request = acp::ExtRequest::new(
"x.ai/feedback",
"kigi/feedback",
raw_params.into(),
);
match acp_send(request, &tx).await {
@@ -2636,7 +2636,7 @@ pub(crate) fn execute(
tasks
.spawn(async move {
let request = acp::ExtRequest::new(
"x.ai/memory/rewrite",
"kigi/memory/rewrite",
serde_json::value::to_raw_value(
&serde_json::json!(
{ "sessionId" : session_id.0.to_string(), "rawText" :
@@ -2703,7 +2703,7 @@ pub(crate) fn execute(
tasks
.spawn(async move {
let request = acp::ExtRequest::new(
"x.ai/btw",
"kigi/btw",
serde_json::value::to_raw_value(
&serde_json::json!(
{ "sessionId" : session_id.0.to_string(), "question" :
@@ -2746,7 +2746,7 @@ pub(crate) fn execute(
tasks
.spawn(async move {
let request = acp::ExtRequest::new(
"x.ai/recap",
"kigi/recap",
serde_json::value::to_raw_value(
&serde_json::json!(
{ "sessionId" : session_id.0.to_string(), "auto" : auto, }
@@ -2790,7 +2790,7 @@ pub(crate) fn execute(
blocks.as_deref(),
);
let request = acp::ExtRequest::new(
"x.ai/interject",
"kigi/interject",
serde_json::value::to_raw_value(&params)
.expect("serialize interject params")
.into(),
@@ -2820,7 +2820,7 @@ pub(crate) fn execute(
.spawn(async move {
let params = serde_json::json!({ "kind" : kind, "name" : name });
let request = acp::ExtRequest::new(
"x.ai/bundle/entry/get",
"kigi/bundle/entry/get",
serde_json::value::to_raw_value(&params)
.expect("serialize bundle/entry/get params")
.into(),
@@ -2876,7 +2876,7 @@ pub(crate) fn execute(
tasks
.spawn(async move {
let request = acp::ExtRequest::new(
"x.ai/bundle/status",
"kigi/bundle/status",
serde_json::value::to_raw_value(&serde_json::json!({}))
.expect("serialize bundle/status params")
.into(),
@@ -2935,7 +2935,7 @@ pub(crate) fn execute(
.spawn(async move {
let params = serde_json::json!({ "cwd" : cwd });
let req = acp::ExtRequest::new(
"x.ai/commands/list",
"kigi/commands/list",
serde_json::value::to_raw_value(&params)
.expect("serialize commands/list params")
.into(),
@@ -2971,7 +2971,7 @@ pub(crate) fn execute(
tasks
.spawn(async move {
let request = acp::ExtRequest::new(
"x.ai/rewind/points",
"kigi/rewind/points",
serde_json::value::to_raw_value(
&serde_json::json!(
{ "sessionId" : session_id.0.to_string() }
@@ -3030,7 +3030,7 @@ pub(crate) fn execute(
tasks
.spawn(async move {
let request = acp::ExtRequest::new(
"x.ai/rewind/execute",
"kigi/rewind/execute",
serde_json::value::to_raw_value(
&serde_json::json!(
{ "sessionId" : session_id.0.to_string(),
@@ -3084,7 +3084,7 @@ pub(crate) fn execute(
tasks
.spawn(async move {
let request = acp::ExtRequest::new(
"x.ai/rewind/execute",
"kigi/rewind/execute",
serde_json::value::to_raw_value(
&serde_json::json!(
{ "sessionId" : session_id.0.to_string(),
@@ -3144,7 +3144,7 @@ pub(crate) fn execute(
{ "query" : query, "limit" : 20, "includeContent" : true, }
);
let request = acp::ExtRequest::new(
"x.ai/session/search",
"kigi/session/search",
serde_json::value::to_raw_value(&params)
.expect("serialize deep search params")
.into(),
@@ -3229,7 +3229,7 @@ pub(crate) fn execute(
parent_is_worktree,
);
let req = acp::ExtRequest::new(
"x.ai/session/fork",
"kigi/session/fork",
serde_json::value::to_raw_value(&payload)
.expect("serialize fork params")
.into(),
@@ -3308,7 +3308,7 @@ pub(crate) fn execute(
tasks
.spawn(async move {
let req = acp::ExtRequest::new(
"x.ai/billing",
"kigi/billing",
serde_json::value::to_raw_value(&serde_json::json!({}))
.expect("serialize usage params")
.into(),
@@ -3362,7 +3362,7 @@ pub(crate) fn execute(
token_only, }
);
let req = acp::ExtRequest::new(
"x.ai/suggest",
"kigi/suggest",
serde_json::value::to_raw_value(&params)
.expect("serialize suggest params")
.into(),
@@ -3401,7 +3401,7 @@ pub(crate) fn execute(
session_id, }
);
let req = acp::ExtRequest::new(
"x.ai/suggestPrompt",
"kigi/suggestPrompt",
serde_json::value::to_raw_value(&params)
.expect("serialize suggestPrompt params")
.into(),
@@ -3428,13 +3428,13 @@ pub(crate) fn execute(
}
(false, meta)
}
/// Fetch session info from ACP via `x.ai/session/info`.
/// Fetch session info from ACP via `kigi/session/info`.
async fn fetch_session_info(
session_id: &acp::SessionId,
tx: &AcpAgentTx,
) -> Result<SessionInfoResponse, String> {
let request = acp::ExtRequest::new(
"x.ai/session/info",
"kigi/session/info",
serde_json::value::to_raw_value(
&serde_json::json!({ "sessionId" : session_id.0.to_string() }),
)
@@ -3584,7 +3584,7 @@ fn prompt_request_meta(
}
serde_json::Value::Object(map)
}
/// Build the `x.ai/interject` params. The optional structured `content`
/// Build the `kigi/interject` params. The optional structured `content`
/// (text + images) is omitted ENTIRELY when `None` so the legacy wire
/// shape stays byte-identical. Extracted from the spawn for testability.
fn build_interject_params(
@@ -67,7 +67,7 @@ fn prompt_request_meta_omits_screen_mode_when_unset() {
assert_eq!(meta, serde_json::json!({ "promptId" : "p-2" }));
}
/// Text-only interjections must omit the `content` key entirely — the
/// legacy `x.ai/interject` wire shape stays byte-identical.
/// legacy `kigi/interject` wire shape stays byte-identical.
#[test]
fn interject_params_omit_content_when_no_blocks() {
let sid = acp::SessionId::new("s1");
@@ -83,7 +83,7 @@ fn interject_params_omit_content_when_no_blocks() {
fn picker_keeps_conversation_with_empty_cwd_and_missing_updated_at() {
let payload = serde_json::json!(
{ "sessions" : [{ "sessionId" : "conv_abc", "cwd" : "", "summary" :
"Compare GPU vendors", "source" : "conversation", "_meta" : { "x.ai/session" : {
"Compare GPU vendors", "source" : "conversation", "_meta" : { "kigi/session" : {
"kind" : "chat" } } }] }
);
let entries = parse_session_picker_entries(&payload);
@@ -97,7 +97,7 @@ fn picker_keeps_old_conversation_past_cutoff() {
let payload = serde_json::json!(
{ "sessions" : [{ "sessionId" : "conv_old", "cwd" : "", "summary" :
"Ancient chat", "source" : "conversation", "updatedAt" : "2020-01-01T00:00:00Z",
"_meta" : { "x.ai/session" : { "kind" : "chat" } } }] }
"_meta" : { "kigi/session" : { "kind" : "chat" } } }] }
);
let entries = parse_session_picker_entries(&payload);
assert_eq!(entries.len(), 1, "old conversation must still render");
@@ -112,13 +112,13 @@ fn picker_drops_local_with_missing_updated_at() {
let entries = parse_session_picker_entries(&payload);
assert!(entries.is_empty(), "local rows still require a parseable updatedAt");
}
/// Untitled grok.com chats must stay listed, rendered as "Untitled".
/// Untitled kimi.com chats must stay listed, rendered as "Untitled".
#[test]
fn picker_keeps_untitled_conversation_as_untitled() {
let payload = serde_json::json!(
{ "sessions" : [{ "sessionId" : "conv_untitled", "cwd" : "", "summary" : "",
"source" : "conversation", "updatedAt" : "2026-07-01T00:00:00Z", "_meta" : {
"x.ai/session" : { "kind" : "chat" } } }] }
"kigi/session" : { "kind" : "chat" } } }] }
);
let entries = parse_session_picker_entries(&payload);
assert_eq!(entries.len(), 1, "untitled conversation must not vanish");
@@ -277,7 +277,7 @@ fn interject_params_carry_content_when_blocks_present() {
assert_eq!(content.len(), 1);
assert_eq!(content[0] ["text"], "look at [Image #1]");
}
/// `x.ai/billing` ext result (a serialized shell `UsageResponse`) parses
/// `kigi/billing` ext result (a serialized shell `UsageResponse`) parses
/// into typed rows; unknown labels/reset hints survive the round trip.
#[test]
fn parse_usage_response_reads_rows_from_fixture() {
@@ -473,7 +473,7 @@ async fn persist_setting_type_mismatch_errors_simple_mode() {
}
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
/// Spawn a fake ACP agent that counts `x.ai/yolo_mode_changed`
/// Spawn a fake ACP agent that counts `kigi/yolo_mode_changed`
/// notifications. Exits when the channel closes.
fn spawn_fake_acp_agent(
mut rx: tokio::sync::mpsc::UnboundedReceiver<kigi_acp_lib::AcpAgentMessage>,
@@ -483,7 +483,7 @@ fn spawn_fake_acp_agent(
tokio::spawn(async move {
while let Some(msg) = rx.recv().await {
if let kigi_acp_lib::AcpAgentMessage::ExtNotification(args) = msg {
if args.request.method.as_ref() == "x.ai/yolo_mode_changed" {
if args.request.method.as_ref() == "kigi/yolo_mode_changed" {
counter_clone.fetch_add(1, Ordering::SeqCst);
}
let _ = args.response_tx.send(Ok(()));
@@ -590,7 +590,7 @@ async fn persist_permission_mode_acp_notification_fires_once_on_best_effort() {
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
assert_eq!(
counter.load(Ordering::SeqCst), 1,
"ACP `x.ai/yolo_mode_changed` notification must fire exactly once \
"ACP `kigi/yolo_mode_changed` notification must fire exactly once \
on BestEffort path (regardless of disk outcome)",
);
assert!(
@@ -972,7 +972,7 @@ async fn fetch_session_list_pushes_query_and_echoes_seq() {
tokio::spawn(async move {
while let Some(msg) = rx.recv().await {
if let AcpAgentMessage::ExtMethod(args) = msg {
assert_eq!(args.request.method.as_ref(), "x.ai/session/list");
assert_eq!(args.request.method.as_ref(), "kigi/session/list");
let params: serde_json::Value = serde_json::from_str(
args.request.params.get(),
)
@@ -1094,7 +1094,7 @@ fn agent_profile_names_are_valid_builtins() {
ask_user: false,
..Default::default()
},
"grok-build-plan",
"kigi-plan",
),
(
SessionFlags {
@@ -1103,7 +1103,7 @@ fn agent_profile_names_are_valid_builtins() {
ask_user: false,
..Default::default()
},
"grok-build-plan-no-subagents",
"kigi-plan-no-subagents",
),
(
SessionFlags {
@@ -1112,7 +1112,7 @@ fn agent_profile_names_are_valid_builtins() {
ask_user: true,
..Default::default()
},
"grok-build-plan",
"kigi-plan",
),
(
SessionFlags {
@@ -1121,7 +1121,7 @@ fn agent_profile_names_are_valid_builtins() {
ask_user: true,
..Default::default()
},
"grok-build-plan-no-subagents",
"kigi-plan-no-subagents",
),
(
SessionFlags {
@@ -1130,7 +1130,7 @@ fn agent_profile_names_are_valid_builtins() {
ask_user: true,
..Default::default()
},
"grok-build-ask-user",
"kigi-ask-user",
),
(
SessionFlags {
@@ -1139,7 +1139,7 @@ fn agent_profile_names_are_valid_builtins() {
ask_user: true,
..Default::default()
},
"grok-build-ask-user",
"kigi-ask-user",
),
];
for (flags, expected_name) in test_cases {
@@ -1156,13 +1156,13 @@ fn agent_profile_names_are_valid_builtins() {
);
}
}
/// Default flags produce no agent profile (uses grok-build default).
/// Default flags produce no agent profile (uses kigi default).
#[test]
fn default_flags_produce_no_profile() {
let flags = SessionFlags::default();
assert_eq!(flags.agent_profile(), None);
}
/// --subagents alone produces no profile (grok-build already has TaskTool).
/// --subagents alone produces no profile (kigi already has TaskTool).
#[test]
fn subagents_without_plan_produces_no_profile() {
let flags = SessionFlags {
@@ -1178,7 +1178,7 @@ fn subagents_without_plan_produces_no_profile() {
/// escape hatch and drops `agentProfile` — the tests would then assert the
/// wrong branch. Empty string counts as unset (`!s.trim().is_empty()`).
/// Callers must be `#[serial_test::serial(KIGI_AGENT)]` (process-global env).
fn without_grok_agent() -> crate::test_util::EnvVarGuard {
fn without_kigi_agent() -> crate::test_util::EnvVarGuard {
crate::test_util::EnvVarGuard::set("KIGI_AGENT", "")
}
/// At the runtime defaults (every `--no-*` flag false → every
@@ -1187,7 +1187,7 @@ fn without_grok_agent() -> crate::test_util::EnvVarGuard {
#[serial_test::serial(KIGI_AGENT)]
#[test]
fn runtime_default_flags_produce_plan_meta() {
let _env = without_grok_agent();
let _env = without_kigi_agent();
let flags = SessionFlags {
plan_mode: true,
subagents: true,
@@ -1195,7 +1195,7 @@ fn runtime_default_flags_produce_plan_meta() {
..Default::default()
};
let meta = flags.to_meta().unwrap();
assert_eq!(meta["agentProfile"], "grok-build-plan");
assert_eq!(meta["agentProfile"], "kigi-plan");
assert!(meta.get("askUserQuestion").is_none());
assert_eq!(meta["yoloMode"], false);
}
@@ -1204,7 +1204,7 @@ fn runtime_default_flags_produce_plan_meta() {
#[serial_test::serial(KIGI_AGENT)]
#[test]
fn plan_only_meta() {
let _env = without_grok_agent();
let _env = without_kigi_agent();
let flags = SessionFlags {
plan_mode: true,
subagents: false,
@@ -1212,7 +1212,7 @@ fn plan_only_meta() {
..Default::default()
};
let meta = flags.to_meta().unwrap();
assert_eq!(meta["agentProfile"], "grok-build-plan-no-subagents");
assert_eq!(meta["agentProfile"], "kigi-plan-no-subagents");
assert_eq!(meta["askUserQuestion"], false);
assert_eq!(meta["yoloMode"], false);
}
@@ -1220,7 +1220,7 @@ fn plan_only_meta() {
#[serial_test::serial(KIGI_AGENT)]
#[test]
fn plan_with_subagents_meta() {
let _env = without_grok_agent();
let _env = without_kigi_agent();
let flags = SessionFlags {
plan_mode: true,
subagents: true,
@@ -1228,15 +1228,15 @@ fn plan_with_subagents_meta() {
..Default::default()
};
let meta = flags.to_meta().unwrap();
assert_eq!(meta["agentProfile"], "grok-build-plan");
assert_eq!(meta["agentProfile"], "kigi-plan");
assert_eq!(meta["askUserQuestion"], false);
assert_eq!(meta["yoloMode"], false);
}
/// --ask-user alone selects the grok-build-ask-user profile.
/// --ask-user alone selects the kigi-ask-user profile.
#[serial_test::serial(KIGI_AGENT)]
#[test]
fn ask_user_alone_meta() {
let _env = without_grok_agent();
let _env = without_kigi_agent();
let flags = SessionFlags {
plan_mode: false,
subagents: false,
@@ -1244,7 +1244,7 @@ fn ask_user_alone_meta() {
..Default::default()
};
let meta = flags.to_meta().unwrap();
assert_eq!(meta["agentProfile"], "grok-build-ask-user");
assert_eq!(meta["agentProfile"], "kigi-ask-user");
assert!(meta.get("askUserQuestion").is_none());
assert_eq!(meta["yoloMode"], false);
}
@@ -1252,7 +1252,7 @@ fn ask_user_alone_meta() {
#[serial_test::serial(KIGI_AGENT)]
#[test]
fn plan_with_ask_user_uses_plan_profile() {
let _env = without_grok_agent();
let _env = without_kigi_agent();
let flags = SessionFlags {
plan_mode: true,
subagents: false,
@@ -1260,14 +1260,14 @@ fn plan_with_ask_user_uses_plan_profile() {
..Default::default()
};
let meta = flags.to_meta().unwrap();
assert_eq!(meta["agentProfile"], "grok-build-plan-no-subagents");
assert_eq!(meta["agentProfile"], "kigi-plan-no-subagents");
assert!(meta.get("askUserQuestion").is_none());
assert_eq!(meta["yoloMode"], false);
}
/// --no-plan --no-subagents --no-ask-user picks the default profile but
/// must still emit `askUserQuestion: false` so the shell can strip the
/// tool at the builder. Mirrors the runtime: `subagents` toggle alone
/// does not need an `agentProfile` (default `grok-build` already has it).
/// does not need an `agentProfile` (default `kigi` already has it).
#[test]
fn subagents_alone_emits_only_ask_user_question_disable() {
let flags = SessionFlags {
@@ -1280,12 +1280,12 @@ fn subagents_alone_emits_only_ask_user_question_disable() {
assert!(meta.get("agentProfile").is_none());
assert_eq!(meta["askUserQuestion"], false);
}
/// All three flags on at the runtime default produce grok-build-plan
/// All three flags on at the runtime default produce kigi-plan
/// and no `askUserQuestion` field.
#[serial_test::serial(KIGI_AGENT)]
#[test]
fn all_flags_meta() {
let _env = without_grok_agent();
let _env = without_kigi_agent();
let flags = SessionFlags {
plan_mode: true,
subagents: true,
@@ -1293,7 +1293,7 @@ fn all_flags_meta() {
..Default::default()
};
let meta = flags.to_meta().unwrap();
assert_eq!(meta["agentProfile"], "grok-build-plan");
assert_eq!(meta["agentProfile"], "kigi-plan");
assert!(meta.get("askUserQuestion").is_none());
assert_eq!(meta["yoloMode"], false);
}
@@ -1388,7 +1388,7 @@ fn to_meta_chat_mode_stamps_kind_and_omits_agent_profile() {
..Default::default()
};
let meta = flags.to_meta().expect("chat_mode must emit meta");
assert_eq!(meta["x.ai/session"] ["kind"], "chat");
assert_eq!(meta["kigi/session"] ["kind"], "chat");
assert!(
meta.get("agentProfile").is_none(), "K12: chat mode must omit Build agentProfile"
);
@@ -1414,7 +1414,7 @@ fn load_meta_chat_kind_alone_stamps_kind_and_strips_profile() {
scrub_chat_workspace_bind_meta(&mut meta);
}
let meta = meta.expect("chat_kind must produce meta");
assert_eq!(meta["x.ai/session"] ["kind"], "chat");
assert_eq!(meta["kigi/session"] ["kind"], "chat");
assert!(
meta.get("agentProfile").is_none(),
"entry chat_kind must strip Build agentProfile"
@@ -1444,7 +1444,7 @@ fn chat_create_meta_never_includes_workspace_bind_keys_when_cloud_fields_set() {
apply_chat_kind_meta(&mut meta);
scrub_chat_workspace_bind_meta(&mut meta);
let meta = meta.expect("chat create must emit meta");
assert_eq!(meta["x.ai/session"] ["kind"], "chat");
assert_eq!(meta["kigi/session"] ["kind"], "chat");
assert_chat_meta_has_no_workspace_bind_keys(
&serde_json::Value::Object(meta.clone()),
);
@@ -1457,15 +1457,15 @@ fn chat_load_meta_never_includes_workspace_bind_keys() {
{
let obj = meta.get_or_insert_with(acp::Meta::new);
obj.insert("envId".into(), serde_json::json!("env-poison"));
obj.insert("x.ai/cloud_server_id".into(), serde_json::json!("srv-poison"));
obj.insert("kigi/cloud_server_id".into(), serde_json::json!("srv-poison"));
obj.insert(
"x.ai/cloud_existing_workspace".into(),
"kigi/cloud_existing_workspace".into(),
serde_json::json!({ "server_id" : "srv-poison", "cwd" : "/ws", }),
);
}
scrub_chat_workspace_bind_meta(&mut meta);
let meta = meta.expect("chat load must emit meta");
assert_eq!(meta["x.ai/session"] ["kind"], "chat");
assert_eq!(meta["kigi/session"] ["kind"], "chat");
assert_chat_meta_has_no_workspace_bind_keys(
&serde_json::Value::Object(meta.clone()),
);
@@ -1491,9 +1491,9 @@ fn agent_profile_definitions_have_correct_names() {
use std::str::FromStr;
use kigi_agent::config::BuiltinAgentName;
for name in [
"grok-build-plan",
"grok-build-plan-no-subagents",
"grok-build-ask-user",
"kigi-plan",
"kigi-plan-no-subagents",
"kigi-ask-user",
] {
let builtin = BuiltinAgentName::from_str(name).unwrap();
let def = builtin.definition();
@@ -1542,23 +1542,23 @@ fn format_session_info_shows_conversation_id_when_present() {
}
#[test]
fn format_session_info_shows_resolved_when_enabled_and_different() {
let info = make_session_info("grok-4.5", Some("grok-4.3"), 1000, 10000);
let info = make_session_info("kigi-4.5", Some("kigi-4.3"), 1000, 10000);
let text = format_session_info(&info, None, true);
assert!(text.contains("Model: grok-4.5 (grok-4.3)"));
assert!(text.contains("Model: kigi-4.5 (kigi-4.3)"));
}
#[test]
fn format_session_info_hides_resolved_when_disabled() {
let info = make_session_info("grok-4.5", Some("grok-4.3"), 1000, 10000);
let info = make_session_info("kigi-4.5", Some("kigi-4.3"), 1000, 10000);
let text = format_session_info(&info, None, false);
assert!(text.contains("Model: grok-4.5"));
assert!(! text.contains("grok-4.3"));
assert!(text.contains("Model: kigi-4.5"));
assert!(! text.contains("kigi-4.3"));
}
#[test]
fn format_session_info_no_parens_when_resolved_matches_requested() {
let info = make_session_info("grok-4.5", Some("grok-4.5"), 1000, 10000);
let info = make_session_info("kigi-4.5", Some("kigi-4.5"), 1000, 10000);
let text = format_session_info(&info, None, true);
assert!(text.contains("Model: grok-4.5"));
assert!(! text.contains("(grok-4.5)"));
assert!(text.contains("Model: kigi-4.5"));
assert!(! text.contains("(kigi-4.5)"));
}
#[test]
fn format_session_info_shows_model_hash_when_catalog_flag_set() {
@@ -1647,7 +1647,7 @@ fn session_picker_entry_maps_to_dormant_roster_row() {
cwd: "/repo/app".to_string(),
hostname: Some("box".to_string()),
source: "local".to_string(),
model_id: Some("grok-4".to_string()),
model_id: Some("kigi-4".to_string()),
num_messages: 3,
last_active_at: Some(updated),
branch: None,
@@ -1660,7 +1660,7 @@ fn session_picker_entry_maps_to_dormant_roster_row() {
assert_eq!(roster.title.as_deref(), Some("Wire up dashboard"));
assert_eq!(roster.cwd, "/repo/app");
assert!(roster.is_worktree, "worktree_label present → is_worktree");
assert_eq!(roster.model_id.as_deref(), Some("grok-4"));
assert_eq!(roster.model_id.as_deref(), Some("kigi-4"));
assert_eq!(roster.activity, RosterActivity::Dormant);
assert!(! roster.resident);
assert_eq!(roster.last_change_unix_ms, updated.timestamp_millis());
@@ -72,7 +72,7 @@ struct ReinitOutcome {
struct AgentLoadOutcome {
agent_id: super::agent::AgentId,
success: bool,
/// `x.ai/runningPromptId` from the reload response: the turn another
/// `kigi/runningPromptId` from the reload response: the turn another
/// client is driving mid-reconnect, adopted at finalize (mirrors the
/// `SessionLoaded` adoption in `dispatch.rs`).
running_prompt_id: Option<String>,
@@ -635,12 +635,12 @@ pub(crate) async fn run(
crate::acp::AuthStartMode::Command => super::app_view::AuthMode::Command,
};
} else {
// --force-login: find the grok.com method from the advertised list
let grok_com = connection
// --force-login: find the kimi.com method from the advertised list
let kigi_com = connection
.auth_methods
.iter()
.find(|m| m.id().0.as_ref() == "grok.com");
if let Some(method) = grok_com {
.find(|m| m.id().0.as_ref() == "kimi-code");
if let Some(method) = kigi_com {
app.login_label = Some(method.name().to_string());
app.login_method_id = Some(method.id().clone());
let is_provider = method
@@ -655,7 +655,7 @@ pub(crate) async fn run(
super::app_view::AuthMode::Pending
};
} else {
// No grok.com method available, use the first method as fallback
// No kimi.com method available, use the first method as fallback
let first = &connection.auth_methods[0];
app.login_label = Some(first.name().to_string());
app.login_method_id = Some(first.id().clone());
@@ -667,7 +667,7 @@ pub(crate) async fn run(
// by reusing dispatch_login. Effects are stashed and drained after
// the initial render so the user sees the auth UI right away.
// Empty auth_methods (preferred_method pin with no credentials) is
// fail-closed: do not invent grok.com / auto-start OIDC.
// fail-closed: do not invent kimi.com / auto-start OIDC.
tracing::info!(
method_id = ?app.login_method_id,
methods_empty = connection.auth_methods.is_empty(),
@@ -1243,7 +1243,7 @@ pub(crate) async fn run(
}
}
// `grok dashboard` startup: open the dashboard view immediately. The
// `kigi dashboard` startup: open the dashboard view immediately. The
// CLI subcommand wrote a `KIGI_OPEN_DASHBOARD_AT_STARTUP=1` env var
// so we don't have to thread a flag through every arg struct.
if std::env::var("KIGI_OPEN_DASHBOARD_AT_STARTUP").as_deref() == Ok("1") {
@@ -564,7 +564,7 @@ mod tests {
let probed = RefCell::new(Vec::new());
let enabled = gated_sources_async_with(
EnabledForeignSessionSources::default(),
Path::new("/grok"),
Path::new("/kigi"),
|path| {
probed.borrow_mut().push(path.to_path_buf());
std::future::ready(true)
@@ -585,7 +585,7 @@ mod tests {
codex: true,
..Default::default()
},
Path::new("/grok"),
Path::new("/kigi"),
|path| {
probed.borrow_mut().push(path.to_path_buf());
std::future::ready(false)
@@ -609,7 +609,7 @@ mod tests {
#[tokio::test]
async fn async_gate_supports_bundled_and_user_skill_locations() {
let enabled = gated_sources_async_with(compat_all(), Path::new("/grok"), |path| {
let enabled = gated_sources_async_with(compat_all(), Path::new("/kigi"), |path| {
let path = path.to_string_lossy();
std::future::ready(
path.contains("bundled/skills/resume-claude")
@@ -75,7 +75,7 @@ async fn bounded<T>(what: &str, fut: impl std::future::Future<Output = T>) -> T
.unwrap_or_else(|_| panic!("leader-cluster bring-up timed out: {what}"))
}
/// The grok home the agent actually persisted under: `kigi_home()` is
/// The kigi home the agent actually persisted under: `kigi_home()` is
/// process-cached, so an earlier test in this binary may have pinned it.
fn effective_kigi_home() -> PathBuf {
kigi_config::kigi_home()
@@ -1233,7 +1233,7 @@ mod tests {
fn key(source: &str) -> MermaidCacheKey {
MermaidCacheKey::derive(
source,
ThemeKind::GrokNight,
ThemeKind::KigiNight,
80,
MermaidRenderQuality::Terminal,
)
@@ -1768,22 +1768,22 @@ mod tests {
fn is_render_subcommand_matches_only_argv1() {
let argv = |v: &[&str]| v.iter().map(std::ffi::OsString::from).collect::<Vec<_>>();
assert!(is_render_subcommand(&argv(&[
"grok",
"kigi",
MERMAID_RENDER_SUBCOMMAND
])));
assert!(is_render_subcommand(&argv(&[
"grok",
"kigi",
MERMAID_RENDER_SUBCOMMAND,
"--out",
"/tmp/x.png",
])));
// Normal invocations are not the render child.
assert!(!is_render_subcommand(&argv(&["grok"])));
assert!(!is_render_subcommand(&argv(&["grok", "chat"])));
assert!(!is_render_subcommand(&argv(&["kigi"])));
assert!(!is_render_subcommand(&argv(&["kigi", "chat"])));
assert!(!is_render_subcommand(&argv(&[])));
// The subcommand only counts as argv[1], not deeper in the args.
assert!(!is_render_subcommand(&argv(&[
"grok",
"kigi",
"chat",
MERMAID_RENDER_SUBCOMMAND,
])));
@@ -2052,12 +2052,12 @@ mod tests {
let src = "flowchart LR\nA-->B";
let dark_key = MermaidCacheKey::derive(
src,
ThemeKind::GrokNight,
ThemeKind::KigiNight,
80,
MermaidRenderQuality::Terminal,
);
let light_key =
MermaidCacheKey::derive(src, ThemeKind::GrokDay, 80, MermaidRenderQuality::Terminal);
MermaidCacheKey::derive(src, ThemeKind::KigiDay, 80, MermaidRenderQuality::Terminal);
assert_ne!(
dark_key.cache_filename(),
light_key.cache_filename(),
@@ -2116,7 +2116,7 @@ mod tests {
/// hence the cache key — is deterministic).
fn agent_with_session(name: &str) -> AgentView {
use_test_mermaid_dir();
let cwd = PathBuf::from("/grok-mermaid-test").join(name);
let cwd = PathBuf::from("/kigi-mermaid-test").join(name);
let mut agent = crate::app::agent_view::test_agent_view(Some(name), cwd);
agent.last_terminal_size = (100, 40);
agent
@@ -2301,7 +2301,7 @@ mod tests {
// An on-click render in flight, keyed at the click-time theme + width.
let click_key =
MermaidCacheKey::derive(src, ThemeKind::GrokNight, 80, MermaidRenderQuality::Open);
MermaidCacheKey::derive(src, ThemeKind::KigiNight, 80, MermaidRenderQuality::Open);
let mut rt = MermaidRuntime::new();
rt.pending.push(PendingMermaidAction {
key: click_key.clone(),
@@ -2312,7 +2312,7 @@ mod tests {
// A later (live) theme + width derives a DIFFERENT full key for the same
// source — full-key matching would no longer find the pending render...
let live_key =
MermaidCacheKey::derive(src, ThemeKind::GrokDay, 240, MermaidRenderQuality::Open);
MermaidCacheKey::derive(src, ThemeKind::KigiDay, 240, MermaidRenderQuality::Open);
assert_ne!(
click_key, live_key,
"a theme/width change alters the full cache key",
+51 -51
View File
@@ -306,7 +306,7 @@ fn resolve_hunk_tracker_mode(
/// its history). Sessions not found locally are restored from remote storage.
///
/// Returns `Ok(true)` when the user accepted a pending update. The caller
/// should print a message telling the user to relaunch `grok`.
/// should print a message telling the user to relaunch `kigi`.
pub async fn run(
args: PagerArgs,
bg_update_rx: Option<
@@ -618,9 +618,9 @@ fn print_exit_resume_hint(session_id: &str, minimal: bool, w: &mut impl Write) {
let _ = writeln!(w);
let _ = writeln!(w, "Resume this session with:");
if minimal {
let _ = writeln!(w, " grok --minimal --resume {session_id}");
let _ = writeln!(w, " kigi --minimal --resume {session_id}");
} else {
let _ = writeln!(w, " grok --resume {session_id}");
let _ = writeln!(w, " kigi --resume {session_id}");
}
}
/// Screen-mode relaunch failure fallback (same quit tail as plain resume).
@@ -1169,16 +1169,16 @@ pub(crate) fn set_terminal_title(title: &str) {
}
/// Sanitized/truncated window title. Strips control characters: crossterm's
/// `SetTitle` emits the string raw inside an OSC sequence, so an embedded
/// BEL/ESC (titles can arrive from grok.com conversation metadata) would
/// BEL/ESC (titles can arrive from kimi.com conversation metadata) would
/// terminate the OSC early and let the remainder inject arbitrary escape
/// sequences into the terminal.
fn terminal_title_string(title: &str) -> String {
let sanitized: String = title.chars().filter(|c| !c.is_control()).collect();
if sanitized.is_empty() {
"grok".into()
"kigi".into()
} else {
let truncated: String = sanitized.chars().take(80 - 6).collect();
format!("{} - grok", truncated)
format!("{} - kigi", truncated)
}
}
fn set_panic_hook(mode: ScreenMode) {
@@ -1221,11 +1221,11 @@ mod tests {
fn terminal_title_strips_control_characters() {
assert_eq!(
terminal_title_string("evil\x07\x1b]52;c;payload\x07title"),
"evil]52;c;payloadtitle - grok"
"evil]52;c;payloadtitle - kigi"
);
assert_eq!(terminal_title_string("\x07\x1b\x00"), "grok");
assert_eq!(terminal_title_string(""), "grok");
assert_eq!(terminal_title_string("My chat"), "My chat - grok");
assert_eq!(terminal_title_string("\x07\x1b\x00"), "kigi");
assert_eq!(terminal_title_string(""), "kigi");
assert_eq!(terminal_title_string("My chat"), "My chat - kigi");
}
#[test]
fn hunk_tracker_mode_nothing_set_is_none() {
@@ -1316,24 +1316,24 @@ mod tests {
}
#[test]
fn cli_leader_and_no_leader_conflict() {
let result = try_parse_pager(&["grok-pager", "--leader", "--no-leader"]);
let result = try_parse_pager(&["kigi-pager", "--leader", "--no-leader"]);
assert!(result.is_err());
}
#[test]
fn cli_leader_flag_parses() {
let args = try_parse_pager(&["grok-pager", "--leader"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--leader"]).unwrap();
assert!(args.leader);
assert!(!args.no_leader);
}
#[test]
fn cli_no_leader_flag_parses() {
let args = try_parse_pager(&["grok-pager", "--no-leader"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--no-leader"]).unwrap();
assert!(!args.leader);
assert!(args.no_leader);
}
#[test]
fn cli_neither_leader_flag_defaults_false() {
let args = try_parse_pager(&["grok-pager"]).unwrap();
let args = try_parse_pager(&["kigi-pager"]).unwrap();
assert!(!args.leader);
assert!(!args.no_leader);
}
@@ -1348,13 +1348,13 @@ mod tests {
/// main() must reject the combination at runtime.
#[test]
fn cli_top_level_leader_with_agent_subcommand_parses_flag() {
let args = try_parse_pager(&["grok-pager", "--leader", "agent", "stdio"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--leader", "agent", "stdio"]).unwrap();
assert!(args.leader);
assert!(matches!(args.command, Some(Command::Agent(_))));
}
#[test]
fn cli_top_level_no_leader_with_agent_subcommand_parses_flag() {
let args = try_parse_pager(&["grok-pager", "--no-leader", "agent", "stdio"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--no-leader", "agent", "stdio"]).unwrap();
assert!(args.no_leader);
assert!(matches!(args.command, Some(Command::Agent(_))));
}
@@ -1362,7 +1362,7 @@ mod tests {
/// (kimi-cli parity, F6 — the `acp` alias relies on the same default).
#[test]
fn cli_bare_agent_subcommand_defaults_to_stdio() {
let args = try_parse_pager(&["grok-pager", "agent"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "agent"]).unwrap();
let Some(Command::Agent(agent)) = args.command else {
panic!("expected agent subcommand");
};
@@ -1376,73 +1376,73 @@ mod tests {
}
#[test]
fn cli_resume_parses_session_id() {
let args = try_parse_pager(&["grok-pager", "--resume", "abc-123"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--resume", "abc-123"]).unwrap();
assert_eq!(args.session_to_resume(), Some("abc-123"));
}
#[test]
fn cli_short_r_parses_session_id() {
let args = try_parse_pager(&["grok-pager", "-r", "abc-123"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "-r", "abc-123"]).unwrap();
assert_eq!(args.session_to_resume(), Some("abc-123"));
}
#[test]
fn cli_load_alias_parses_session_id() {
let args = try_parse_pager(&["grok-pager", "--load", "abc-123"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--load", "abc-123"]).unwrap();
assert_eq!(args.session_to_resume(), Some("abc-123"));
}
#[test]
fn cli_resume_preferred_over_load() {
let mut args = try_parse_pager(&["grok-pager", "--resume", "from-resume"]).unwrap();
let mut args = try_parse_pager(&["kigi-pager", "--resume", "from-resume"]).unwrap();
args.load_session = Some("from-load".into());
assert_eq!(args.session_to_resume(), Some("from-resume"));
}
#[test]
fn cli_continue_flag_parses() {
let args = try_parse_pager(&["grok-pager", "--continue"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--continue"]).unwrap();
assert!(args.continue_last_session);
assert_eq!(args.session_to_resume(), None);
}
#[test]
fn cli_continue_short_c_parses() {
let args = try_parse_pager(&["grok-pager", "-c"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "-c"]).unwrap();
assert!(args.continue_last_session);
}
#[test]
fn cli_resume_no_id_sets_empty_sentinel() {
let args = try_parse_pager(&["grok-pager", "--resume"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--resume"]).unwrap();
assert_eq!(args.resume_session.as_deref(), Some(""));
assert!(args.resume_most_recent());
assert_eq!(args.session_to_resume(), None);
}
#[test]
fn cli_short_r_no_id_sets_empty_sentinel() {
let args = try_parse_pager(&["grok-pager", "-r"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "-r"]).unwrap();
assert_eq!(args.resume_session.as_deref(), Some(""));
assert!(args.resume_most_recent());
}
#[test]
fn cli_resume_with_id_is_not_most_recent() {
let args = try_parse_pager(&["grok-pager", "--resume", "abc-123"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--resume", "abc-123"]).unwrap();
assert!(!args.resume_most_recent());
assert_eq!(args.session_to_resume(), Some("abc-123"));
}
#[test]
fn cli_no_resume_is_not_most_recent() {
let args = try_parse_pager(&["grok-pager"]).unwrap();
let args = try_parse_pager(&["kigi-pager"]).unwrap();
assert!(!args.resume_most_recent());
}
#[test]
fn cli_continue_conflicts_with_resume() {
let result = try_parse_pager(&["grok-pager", "--continue", "--resume", "abc"]);
let result = try_parse_pager(&["kigi-pager", "--continue", "--resume", "abc"]);
assert!(result.is_err());
}
#[test]
fn cli_continue_conflicts_with_load() {
let result = try_parse_pager(&["grok-pager", "--continue", "--load", "abc"]);
let result = try_parse_pager(&["kigi-pager", "--continue", "--load", "abc"]);
assert!(result.is_err());
}
#[test]
fn cli_no_session_flags_defaults() {
let args = try_parse_pager(&["grok-pager"]).unwrap();
let args = try_parse_pager(&["kigi-pager"]).unwrap();
assert!(!args.continue_last_session);
assert!(args.worktree.is_none());
assert_eq!(args.session_to_resume(), None);
@@ -1452,7 +1452,7 @@ mod tests {
/// binary given that flag fails clap parsing instead of silently ignoring.
#[test]
fn cli_chat_flag_rejected_without_feature() {
assert!(try_parse_pager(&["grok-pager", "--chat"]).is_err());
assert!(try_parse_pager(&["kigi-pager", "--chat"]).is_err());
}
#[test]
fn chat_mode_leader_guard_truth_table() {
@@ -1469,49 +1469,49 @@ mod tests {
}
#[test]
fn cli_worktree_flag_parses() {
let args = try_parse_pager(&["grok-pager", "--worktree"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--worktree"]).unwrap();
assert_eq!(args.worktree.as_deref(), Some(""));
}
#[test]
fn cli_worktree_short_w_parses() {
let args = try_parse_pager(&["grok-pager", "-w"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "-w"]).unwrap();
assert_eq!(args.worktree.as_deref(), Some(""));
}
#[test]
fn cli_worktree_with_label() {
let args = try_parse_pager(&["grok-pager", "-w", "my-label"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "-w", "my-label"]).unwrap();
assert_eq!(args.worktree.as_deref(), Some("my-label"));
}
#[test]
fn cli_worktree_long_with_label() {
let args = try_parse_pager(&["grok-pager", "--worktree", "fix-bug"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--worktree", "fix-bug"]).unwrap();
assert_eq!(args.worktree.as_deref(), Some("fix-bug"));
}
#[test]
fn cli_worktree_with_empty_string() {
let args = try_parse_pager(&["grok-pager", "-w", ""]).unwrap();
let args = try_parse_pager(&["kigi-pager", "-w", ""]).unwrap();
assert_eq!(args.worktree.as_deref(), Some(""));
}
#[test]
fn cli_worktree_with_resume_parses() {
let args = try_parse_pager(&["grok-pager", "-w", "--resume", "abc"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "-w", "--resume", "abc"]).unwrap();
assert_eq!(args.worktree.as_deref(), Some(""));
assert_eq!(args.session_to_resume(), Some("abc"));
}
#[test]
fn cli_worktree_label_with_resume() {
let args = try_parse_pager(&["grok-pager", "-w", "my-label", "--resume", "abc"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "-w", "my-label", "--resume", "abc"]).unwrap();
assert_eq!(args.worktree.as_deref(), Some("my-label"));
assert_eq!(args.session_to_resume(), Some("abc"));
}
#[test]
fn cli_worktree_default_none() {
let args = try_parse_pager(&["grok-pager"]).unwrap();
let args = try_parse_pager(&["kigi-pager"]).unwrap();
assert!(args.worktree.is_none());
}
#[test]
fn cli_session_id_parses() {
let args = try_parse_pager(&["grok-pager", "--session-id", "my-id"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--session-id", "my-id"]).unwrap();
assert_eq!(args.session_id.as_deref(), Some("my-id"));
assert!(matches!(
args.session_startup_intent().unwrap(),
@@ -1520,38 +1520,38 @@ mod tests {
}
#[test]
fn cli_session_id_short_s_parses() {
let args = try_parse_pager(&["grok-pager", "-s", "my-id"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "-s", "my-id"]).unwrap();
assert_eq!(args.session_id.as_deref(), Some("my-id"));
}
#[test]
fn cli_session_id_with_resume_requires_fork() {
let args = try_parse_pager(&["grok-pager", "-s", "a", "--resume", "b"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "-s", "a", "--resume", "b"]).unwrap();
assert!(args.session_startup_intent().is_err());
}
#[test]
fn cli_session_id_with_continue_requires_fork() {
let args = try_parse_pager(&["grok-pager", "-s", "a", "--continue"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "-s", "a", "--continue"]).unwrap();
assert!(args.session_startup_intent().is_err());
}
#[test]
fn cli_session_id_with_resume_and_fork_ok() {
let args =
try_parse_pager(&["grok-pager", "-s", "a", "--resume", "b", "--fork-session"]).unwrap();
try_parse_pager(&["kigi-pager", "-s", "a", "--resume", "b", "--fork-session"]).unwrap();
assert!(args.session_startup_intent().is_ok());
}
#[test]
fn cli_session_id_default_none() {
let args = try_parse_pager(&["grok-pager"]).unwrap();
let args = try_parse_pager(&["kigi-pager"]).unwrap();
assert!(args.session_id.is_none());
}
#[test]
fn cli_no_alt_screen_flag_parses() {
let args = try_parse_pager(&["grok-pager", "--no-alt-screen"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "--no-alt-screen"]).unwrap();
assert!(args.no_alt_screen);
}
#[test]
fn cli_no_alt_screen_default_false() {
let args = try_parse_pager(&["grok-pager"]).unwrap();
let args = try_parse_pager(&["kigi-pager"]).unwrap();
assert!(!args.no_alt_screen);
}
#[test]
@@ -1580,12 +1580,12 @@ mod tests {
#[test]
fn cli_completions_parses() {
use clap_complete::Shell;
let args = try_parse_pager(&["grok-pager", "completions", "zsh"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "completions", "zsh"]).unwrap();
assert!(matches!(
args.command,
Some(Command::Completions { shell: Shell::Zsh })
));
let args = try_parse_pager(&["grok-pager", "completions", "bash"]).unwrap();
let args = try_parse_pager(&["kigi-pager", "completions", "bash"]).unwrap();
assert!(matches!(
args.command,
Some(Command::Completions { shell: Shell::Bash })
@@ -1607,7 +1607,7 @@ mod tests {
print_exit_resume_hint("sess-abc", false, &mut buf);
assert_eq!(
String::from_utf8(buf).unwrap(),
"\nResume this session with:\n grok --resume sess-abc\n"
"\nResume this session with:\n kigi --resume sess-abc\n"
);
}
#[test]
@@ -1616,7 +1616,7 @@ mod tests {
print_exit_resume_hint("sess-abc", true, &mut buf);
assert_eq!(
String::from_utf8(buf).unwrap(),
"\nResume this session with:\n grok --minimal --resume sess-abc\n"
"\nResume this session with:\n kigi --minimal --resume sess-abc\n"
);
}
#[test]
@@ -40,7 +40,7 @@ pub enum PromptMode {
/// When `Some`, this is a server-authoritative shared-queue row and
/// `server_id` is the agent's stable `prompt_id`. On save we route
/// the change through `Action::QueueEditShared` (and rely on the
/// `x.ai/queue/changed` rebroadcast for the visual result) instead
/// `kigi/queue/changed` rebroadcast for the visual result) instead
/// of mutating the local `pending_prompts` mirror. `None` is the
/// pre-existing local-origin path.
server_id: Option<String>,
@@ -412,7 +412,7 @@ impl AgentView {
}
match server_id {
Some(server_id) => {
// Server rows: the queue wire (`x.ai/queue/interject` newText)
// Server rows: the queue wire (`kigi/queue/interject` newText)
// is text-only, so composer images can't ride along — known
// limitation, dropped with an accurate toast.
if !self.prompt.images.is_empty() {
+11 -11
View File
@@ -3,8 +3,8 @@
//! The leader process hosts session actors and exposes a roster API the
//! pager consumes in leader mode (FleetView dashboard):
//!
//! - Request/response `x.ai/sessions/list` → [`RosterListResponse`].
//! - Broadcast notification `x.ai/sessions/changed` → [`RosterChanged`].
//! - Request/response `kigi/sessions/list` → [`RosterListResponse`].
//! - Broadcast notification `kigi/sessions/changed` → [`RosterChanged`].
//!
//! These structs mirror the producer-side wire format (camelCase JSON,
//! snake_case activity enum). They are deserialize-only — the pager never
@@ -57,14 +57,14 @@ pub struct RosterEntry {
pub origin: RosterOrigin,
}
/// Response to `x.ai/sessions/list`.
/// Response to `kigi/sessions/list`.
#[derive(Debug, Clone, Default, Deserialize)]
pub struct RosterListResponse {
#[serde(default)]
pub sessions: Vec<RosterEntry>,
}
/// Broadcast payload for `x.ai/sessions/changed`.
/// Broadcast payload for `kigi/sessions/changed`.
#[derive(Debug, Clone, Default, Deserialize)]
pub struct RosterChanged {
#[serde(default)]
@@ -73,7 +73,7 @@ pub struct RosterChanged {
pub removed: Vec<String>,
}
/// Parse an `x.ai/sessions/list` ext-response body into a [`RosterListResponse`].
/// Parse an `kigi/sessions/list` ext-response body into a [`RosterListResponse`].
///
/// The agent serializes the response through
/// `ExtMethodResult::success(..).to_ext_response()` (see
@@ -89,8 +89,8 @@ pub struct RosterChanged {
/// key). That was the original bug: the poll returned an empty roster on every
/// tick and — because [`crate::app::actions::TaskResult::RosterLoaded`] replaces
/// `leader_roster` wholesale — also clobbered any entry delivered by the
/// `x.ai/sessions/changed` broadcast. Mirrors how `Effect::FetchSessionList`
/// unwraps `result` for `x.ai/session/list`.
/// `kigi/sessions/changed` broadcast. Mirrors how `Effect::FetchSessionList`
/// unwraps `result` for `kigi/session/list`.
pub fn parse_roster_list_response(body: &str) -> Option<RosterListResponse> {
let value: serde_json::Value = serde_json::from_str(body).ok()?;
let payload = value.get("result").unwrap_or(&value);
@@ -110,7 +110,7 @@ mod tests {
title: Some("Fix the roster".to_string()),
cwd: "/repo/worktree".to_string(),
is_worktree: true,
model_id: Some("grok-4".to_string()),
model_id: Some("kigi-4".to_string()),
reasoning_effort: None,
yolo: true,
activity: agent::RosterActivity::Working,
@@ -141,7 +141,7 @@ mod tests {
sessions: vec![agent_entry()],
};
// Exact wire bytes the agent emits for `x.ai/sessions/list`.
// Exact wire bytes the agent emits for `kigi/sessions/list`.
let ext_response = ExtMethodResult::success(agent_resp)
.to_ext_response()
.expect("agent serializes the roster response");
@@ -172,7 +172,7 @@ mod tests {
assert_eq!(e.title.as_deref(), Some("Fix the roster"));
assert_eq!(e.cwd, "/repo/worktree");
assert!(e.is_worktree);
assert_eq!(e.model_id.as_deref(), Some("grok-4"));
assert_eq!(e.model_id.as_deref(), Some("kigi-4"));
assert!(e.yolo);
assert_eq!(e.activity, RosterActivity::Working);
assert!(e.resident);
@@ -190,7 +190,7 @@ mod tests {
assert_eq!(parsed.sessions[0].session_id, "s1");
}
/// `x.ai/sessions/changed` round-trip: serialize the agent's `RosterChanged`
/// `kigi/sessions/changed` round-trip: serialize the agent's `RosterChanged`
/// exactly as `emit_roster_changed` does (bare params, no `result`
/// envelope) and confirm the pager's `RosterChanged` recovers `upserted` /
/// `removed` and the nested entry fields (camelCase). Regression guard for
@@ -71,7 +71,7 @@ fn flag_takes_value(flag: &str) -> bool {
///
/// Strips prior session-selection / mode flags, one-shot session-creation
/// directives, and any bare positional prompt so a cold-start
/// `grok "do the thing"` does not re-submit on resume. Keeps everything else
/// `kigi "do the thing"` does not re-submit on resume. Keeps everything else
/// (e.g. `--no-leader`, `--model`, endpoint overrides) intact, including the
/// value token that follows value-taking flags.
///
@@ -175,7 +175,7 @@ pub(crate) fn build_screen_mode_relaunch_args(
continue;
}
// Bare positional prompt (e.g. `grok "fix the bug"`). Must not re-fire
// Bare positional prompt (e.g. `kigi "fix the bug"`). Must not re-fire
// on resume. Clap positionals never start with `-`. Values for earlier
// flags were already consumed above, so any remaining bare word here is
// the prompt.
@@ -210,7 +210,7 @@ pub(crate) fn screen_mode_relaunch_resume_hint(session_id: &str, want_minimal: b
} else {
"--fullscreen"
};
format!("{KIGI_SCREEN_MODE_ENV}={mode} grok {flag} --resume {session_id}")
format!("{KIGI_SCREEN_MODE_ENV}={mode} kigi {flag} --resume {session_id}")
}
/// Replace the current process with a relaunch into the requested screen mode.
@@ -331,7 +331,7 @@ pub(crate) fn parse_screen_mode(value: Option<&str>) -> Option<super::ScreenMode
///
/// Reads **and removes** the variable so the override is truly one-shot: it
/// must not linger in this process's environment where every spawned child
/// (tool shells, workers, nested `grok` invocations) would inherit a forced
/// (tool shells, workers, nested `kigi` invocations) would inherit a forced
/// screen mode the user never asked for.
///
/// When set, the returned mode **wins** over CLI flags (`--minimal`,
@@ -440,7 +440,7 @@ mod tests {
#[test]
fn adds_minimal_and_resume() {
let out = build_screen_mode_relaunch_args(args(&["grok", "--no-leader"]), "abc", true);
let out = build_screen_mode_relaunch_args(args(&["kigi", "--no-leader"]), "abc", true);
assert_eq!(
as_strs(&out),
vec!["--no-leader", "--resume", "abc", "--minimal"]
@@ -451,7 +451,7 @@ mod tests {
/// resolution still works without the env override.
#[test]
fn adds_fullscreen_and_resume() {
let out = build_screen_mode_relaunch_args(args(&["grok", "--no-leader"]), "abc", false);
let out = build_screen_mode_relaunch_args(args(&["kigi", "--no-leader"]), "abc", false);
assert_eq!(
as_strs(&out),
vec!["--no-leader", "--resume", "abc", "--fullscreen"]
@@ -466,7 +466,7 @@ mod tests {
fn strips_session_id_flag() {
let out = build_screen_mode_relaunch_args(
args(&[
"grok",
"kigi",
"--session-id",
"11111111-1111-1111-1111-111111111111",
"--no-leader",
@@ -480,7 +480,7 @@ mod tests {
);
let out = build_screen_mode_relaunch_args(
args(&["grok", "-s", "11111111-1111-1111-1111-111111111111"]),
args(&["kigi", "-s", "11111111-1111-1111-1111-111111111111"]),
"new",
false,
);
@@ -494,7 +494,7 @@ mod tests {
fn strips_worktree_and_restore_code() {
let out = build_screen_mode_relaunch_args(
args(&[
"grok",
"kigi",
"-w",
"feature-x",
"--worktree-ref",
@@ -518,7 +518,7 @@ mod tests {
fn strips_eq_forms_of_one_shot_flags() {
let out = build_screen_mode_relaunch_args(
args(&[
"grok",
"kigi",
"--session-id=u1",
"--worktree=wt",
"--worktree-ref=main",
@@ -539,7 +539,7 @@ mod tests {
#[test]
fn strips_bare_worktree_without_eating_next_flag() {
let out = build_screen_mode_relaunch_args(
args(&["grok", "--worktree", "--no-leader"]),
args(&["kigi", "--worktree", "--no-leader"]),
"new",
false,
);
@@ -552,7 +552,7 @@ mod tests {
#[test]
fn strips_prior_minimal_and_resume() {
let out = build_screen_mode_relaunch_args(
args(&["grok", "--minimal", "--resume", "old", "--no-leader"]),
args(&["kigi", "--minimal", "--resume", "old", "--no-leader"]),
"new",
false,
);
@@ -570,7 +570,7 @@ mod tests {
#[test]
fn strips_prior_fullscreen_flag() {
let out = build_screen_mode_relaunch_args(
args(&["grok", "--fullscreen", "--resume", "old", "--no-leader"]),
args(&["kigi", "--fullscreen", "--resume", "old", "--no-leader"]),
"new",
true,
);
@@ -584,7 +584,7 @@ mod tests {
#[test]
fn strips_short_resume_and_continue() {
let out = build_screen_mode_relaunch_args(
args(&["grok", "-r", "old", "-c", "--no-leader"]),
args(&["kigi", "-r", "old", "-c", "--no-leader"]),
"sid",
true,
);
@@ -598,7 +598,7 @@ mod tests {
#[test]
fn strips_resume_equals_form() {
let out = build_screen_mode_relaunch_args(
args(&["grok", "--resume=old-id", "--no-leader"]),
args(&["kigi", "--resume=old-id", "--no-leader"]),
"sid",
false,
);
@@ -611,7 +611,7 @@ mod tests {
#[test]
fn strips_positional_prompt() {
let out = build_screen_mode_relaunch_args(
args(&["grok", "--no-leader", "fix the bug"]),
args(&["kigi", "--no-leader", "fix the bug"]),
"sid",
true,
);
@@ -624,11 +624,11 @@ mod tests {
#[test]
fn double_dash_and_following_positionals_dropped() {
// `grok --no-leader -- "fix the bug"`: everything after `--` is the
// `kigi --no-leader -- "fix the bug"`: everything after `--` is the
// prompt. The separator itself must go too, or the appended
// `--resume <id>` would be parsed as positional prompt words.
let out = build_screen_mode_relaunch_args(
args(&["grok", "--no-leader", "--", "fix the bug"]),
args(&["kigi", "--no-leader", "--", "fix the bug"]),
"sid",
false,
);
@@ -644,9 +644,9 @@ mod tests {
// as the bare positional prompt (regression: relaunch argv drops flag values).
let out = build_screen_mode_relaunch_args(
args(&[
"grok",
"kigi",
"--model",
"grok-4",
"kigi-4",
"--cwd",
"/tmp/proj",
"--leader-socket",
@@ -663,7 +663,7 @@ mod tests {
as_strs(&out),
vec![
"--model",
"grok-4",
"kigi-4",
"--cwd",
"/tmp/proj",
"--leader-socket",
@@ -682,7 +682,7 @@ mod tests {
#[test]
fn keeps_equals_form_and_short_model_flag() {
let out = build_screen_mode_relaunch_args(
args(&["grok", "-m", "grok-4", "--cwd=/tmp/proj", "--no-leader"]),
args(&["kigi", "-m", "kigi-4", "--cwd=/tmp/proj", "--no-leader"]),
"sid",
false,
);
@@ -690,7 +690,7 @@ mod tests {
as_strs(&out),
vec![
"-m",
"grok-4",
"kigi-4",
"--cwd=/tmp/proj",
"--no-leader",
"--resume",
@@ -705,7 +705,7 @@ mod tests {
// `--no-leader` is boolean; the bare word after it is the prompt and
// must be dropped, not attached as a spurious value.
let out = build_screen_mode_relaunch_args(
args(&["grok", "--no-leader", "fix the bug"]),
args(&["kigi", "--no-leader", "fix the bug"]),
"sid",
false,
);
@@ -717,9 +717,9 @@ mod tests {
#[test]
fn resume_without_value_then_flag_is_not_eaten() {
// `grok --resume --no-leader` (resume most-recent; next token is a flag).
// `kigi --resume --no-leader` (resume most-recent; next token is a flag).
let out = build_screen_mode_relaunch_args(
args(&["grok", "--resume", "--no-leader"]),
args(&["kigi", "--resume", "--no-leader"]),
"sid",
false,
);
@@ -833,11 +833,11 @@ mod tests {
// explicit flag keeps the resume in the right mode if the env is dropped.
assert_eq!(
screen_mode_relaunch_resume_hint("abc-sid", false),
"KIGI_SCREEN_MODE=fullscreen grok --fullscreen --resume abc-sid"
"KIGI_SCREEN_MODE=fullscreen kigi --fullscreen --resume abc-sid"
);
assert_eq!(
screen_mode_relaunch_resume_hint("abc-sid", true),
"KIGI_SCREEN_MODE=minimal grok --minimal --resume abc-sid"
"KIGI_SCREEN_MODE=minimal kigi --minimal --resume abc-sid"
);
}
@@ -26,7 +26,7 @@ pub enum DeferredSessionStartup {
parent_cwd: Option<PathBuf>,
new_session_id: Option<String>,
},
/// Fresh plain Grok session whose first prompt resumes a foreign tool session.
/// Fresh plain Kigi session whose first prompt resumes a foreign tool session.
ForeignResume {
tool: kigi_workspace::foreign_sessions::ForeignSessionTool,
native_id: String,
@@ -53,7 +53,7 @@ impl DeferredStartupActions {
std::mem::take(self)
}
}
/// Build `x.ai/session/fork` params shared by TUI effects and headless.
/// Build `kigi/session/fork` params shared by TUI effects and headless.
///
/// `new_cwd` is the write namespace for the child (parent session cwd when
/// cross-cwd); preflight must use the same path via [`effective_fork_new_cwd`].
@@ -114,7 +114,7 @@ pub fn parent_session_is_worktree(session_id: &str, cwd: &Path) -> bool {
}
false
}
/// Parse `newSessionId` from an `x.ai/session/fork` ACP response body.
/// Parse `newSessionId` from an `kigi/session/fork` ACP response body.
pub fn fork_response_new_session_id(resp_json: &str) -> Option<String> {
let v: serde_json::Value = serde_json::from_str(resp_json).unwrap_or_default();
if v.get("error").is_some_and(|e| !e.is_null()) {
@@ -375,7 +375,7 @@ pub struct MaterializeCtx {
pub has_worktree: bool,
/// When true, attempt remote restore if the session is not on disk.
pub allow_remote_restore: bool,
/// Process-wide flag: resume targets are grok.com conversations, not
/// Process-wide flag: resume targets are kimi.com conversations, not
/// the local disk store. Always `false` without the optional feature;
/// setting it anyway errors rather than silently falling back to disk.
pub chat_mode: bool,
@@ -404,12 +404,12 @@ async fn most_recent_session_id(cwd: &str) -> anyhow::Result<(String, Option<Str
let first = summaries.first().ok_or_else(|| {
anyhow::anyhow!(
"No session found for current directory. \
Use 'grok' to start a new session."
Use 'kigi' to start a new session."
)
})?;
Ok((first.info.id.to_string(), first.display_title_opt()))
}
/// `AuthManager` for direct grok.com calls made outside the agent (pre-ACP
/// `AuthManager` for direct kimi.com calls made outside the agent (pre-ACP
/// `--continue` conversation listing, the GCS restore effect). Wires the
/// auth-provider refresher before the first `auth()`: without it, environments
/// that mint credentials via `auth_provider_command` report `NoOauth`.
@@ -689,14 +689,14 @@ mod tests {
#[test]
fn intent_default_is_new_auto() {
assert_eq!(
parse(&["grok"]).session_startup_intent().unwrap(),
parse(&["kigi"]).session_startup_intent().unwrap(),
SessionStartupIntent::NewAuto
);
}
#[test]
fn intent_resume_id() {
assert_eq!(
parse(&["grok", "--resume", "abc"])
parse(&["kigi", "--resume", "abc"])
.session_startup_intent()
.unwrap(),
SessionStartupIntent::Resume {
@@ -708,7 +708,7 @@ mod tests {
#[test]
fn intent_resume_empty_is_most_recent() {
assert_eq!(
parse(&["grok", "--resume"])
parse(&["kigi", "--resume"])
.session_startup_intent()
.unwrap(),
SessionStartupIntent::Resume {
@@ -720,7 +720,7 @@ mod tests {
#[test]
fn intent_continue() {
assert_eq!(
parse(&["grok", "-c"]).session_startup_intent().unwrap(),
parse(&["kigi", "-c"]).session_startup_intent().unwrap(),
SessionStartupIntent::Resume {
session_id: None,
most_recent_for_cwd: true,
@@ -730,7 +730,7 @@ mod tests {
#[test]
fn intent_session_id_alone_is_new_with_id() {
assert_eq!(
parse(&["grok", "--session-id", "my-id"])
parse(&["kigi", "--session-id", "my-id"])
.session_startup_intent()
.unwrap(),
SessionStartupIntent::NewWithId {
@@ -740,7 +740,7 @@ mod tests {
}
#[test]
fn intent_session_id_with_resume_without_fork_errors() {
let err = parse(&["grok", "-r", "a", "-s", "b"])
let err = parse(&["kigi", "-r", "a", "-s", "b"])
.session_startup_intent()
.unwrap_err();
assert_eq!(err, StartupFlagError::SessionIdRequiresFork);
@@ -748,7 +748,7 @@ mod tests {
#[test]
fn intent_fork_with_resume() {
assert_eq!(
parse(&["grok", "-r", "old", "--fork-session"])
parse(&["kigi", "-r", "old", "--fork-session"])
.session_startup_intent()
.unwrap(),
SessionStartupIntent::ForkFrom {
@@ -761,7 +761,7 @@ mod tests {
#[test]
fn intent_fork_with_resume_and_new_id() {
assert_eq!(
parse(&["grok", "-r", "old", "--fork-session", "-s", "new"])
parse(&["kigi", "-r", "old", "--fork-session", "-s", "new"])
.session_startup_intent()
.unwrap(),
SessionStartupIntent::ForkFrom {
@@ -773,21 +773,21 @@ mod tests {
}
#[test]
fn intent_fork_alone_errors() {
let err = parse(&["grok", "--fork-session"])
let err = parse(&["kigi", "--fork-session"])
.session_startup_intent()
.unwrap_err();
assert_eq!(err, StartupFlagError::ForkRequiresResumeOrContinue);
}
#[test]
fn intent_fork_with_worktree_errors() {
let err = parse(&["grok", "-r", "a", "--fork-session", "-w"])
let err = parse(&["kigi", "-r", "a", "--fork-session", "-w"])
.session_startup_intent()
.unwrap_err();
assert_eq!(err, StartupFlagError::ForkWithWorktree);
}
#[test]
fn intent_from_flags_matches_pager_args() {
let args = parse(&["grok", "-r", "old", "--fork-session", "-s", "new"]);
let args = parse(&["kigi", "-r", "old", "--fork-session", "-s", "new"]);
let from_flags = session_startup_intent_from_flags(SessionStartupFlags {
session_id: Some("new"),
resume_session_id: Some("old"),
@@ -888,7 +888,7 @@ mod tests {
}
#[test]
fn materialize_ctx_chat_mode_from_args() {
assert!(!MaterializeCtx::from_pager_args(&parse(&["grok"])).chat_mode);
assert!(!MaterializeCtx::from_pager_args(&parse(&["kigi"])).chat_mode);
}
/// Explicit-id resume under `--chat` passes the id through untouched:
/// no disk resolution, no GCS restore (the cwd does not even exist).
+9 -9
View File
@@ -110,7 +110,7 @@ thread_local! {
static REPLAY_KIGI_SHARE_DIR : std::cell::RefCell < Option < std::path::PathBuf >> = const
{ std::cell::RefCell::new(None) };
}
/// Override grok home for disk-replay unit tests (thread-local; production never sets this).
/// Override kigi home for disk-replay unit tests (thread-local; production never sets this).
#[cfg(test)]
pub(crate) fn set_replay_kigi_home_for_tests(home: Option<std::path::PathBuf>) {
REPLAY_KIGI_SHARE_DIR.with(|h| *h.borrow_mut() = home);
@@ -764,15 +764,15 @@ mod tests {
#[test]
fn subagent_meta_all_fields() {
assert_eq!(
format_subagent_meta(Some("researcher"), Some("analyst"), Some("grok-3")),
" (researcher \u{00b7} analyst \u{00b7} grok-3)"
format_subagent_meta(Some("researcher"), Some("analyst"), Some("kigi-3")),
" (researcher \u{00b7} analyst \u{00b7} kigi-3)"
);
}
#[test]
fn subagent_meta_partial_skips_nones() {
assert_eq!(
format_subagent_meta(Some("researcher"), None, Some("grok-3")),
" (researcher \u{00b7} grok-3)"
format_subagent_meta(Some("researcher"), None, Some("kigi-3")),
" (researcher \u{00b7} kigi-3)"
);
}
#[test]
@@ -813,8 +813,8 @@ mod tests {
#[test]
fn subagent_meta_collapses_duplicate_persona_role() {
assert_eq!(
format_subagent_meta(Some("reviewer"), Some("reviewer"), Some("grok-3")),
" (reviewer \u{00b7} grok-3)"
format_subagent_meta(Some("reviewer"), Some("reviewer"), Some("kigi-3")),
" (reviewer \u{00b7} kigi-3)"
);
}
#[test]
@@ -841,8 +841,8 @@ mod tests {
#[test]
fn subagent_meta_drops_both_empty_persona_role() {
assert_eq!(
format_subagent_meta(Some(""), Some(" "), Some("grok-3")),
" (grok-3)"
format_subagent_meta(Some(""), Some(" "), Some("kigi-3")),
" (kigi-3)"
);
}
#[test]
@@ -1,7 +1,7 @@
//! Finalizing a turn from a terminal turn signal.
//!
//! The pager learns a turn reached its terminal outcome from two rails: the
//! fire-and-forget `x.ai/session/prompt_complete` broadcast (the one-release
//! fire-and-forget `kigi/session/prompt_complete` broadcast (the one-release
//! compat path for not-yet-upgraded leaders) and the durable, persisted+replayed
//! `XaiSessionUpdate::TurnCompleted`. Both converge on
//! [`finalize_turn_from_terminal`] so the turn-finalize behavior lives in one
@@ -0,0 +1 @@
Based on Grok Build Open Source
@@ -152,7 +152,7 @@ fn draw(f: &mut ratatui::Frame, app: &App) {
)),
Line::from(""),
Line::from(
"I'm Grok Build, an interactive CLI agent built to help with software engineering tasks.",
"I'm Kigi, an interactive CLI agent built to help with software engineering tasks.",
),
Line::from(
"This sample is here so you can drag over real text while watching raw mouse events.",
@@ -4,7 +4,7 @@ use std::time::Duration;
use crossterm::ExecutableCommand;
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers};
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
use kigi_tools::implementations::grok_build::ask_user_question::{Question, QuestionOption};
use kigi_tools::implementations::kigi::ask_user_question::{Question, QuestionOption};
use kigi_tui::theme::Theme;
use kigi_tui::views::prompt_widget::StashedPrompt;
use kigi_tui::views::question_view::{
@@ -84,7 +84,7 @@ impl App {
"I am thinking through how to answer your question before responding.",
));
scrollback.push_block(RenderBlock::agent_message(
"I'm Grok Build, an interactive CLI agent built to help with software engineering tasks like coding, debugging, refactoring, and exploring codebases.",
"I'm Kigi, an interactive CLI agent built to help with software engineering tasks like coding, debugging, refactoring, and exploring codebases.",
));
scrollback.push_block(RenderBlock::agent_message(
"Try drag-selecting text. Double-click toggles fold by default; set Text selection → Word select for double-click word / triple-click line.",
@@ -1,4 +1,4 @@
pub const PAGER_CLIENT_TYPE: &str = "grok-pager";
pub const PAGER_CLIENT_TYPE: &str = "kigi-pager";
pub const HEADLESS_CLIENT_TYPE: &str = "kigi";
pub const PAGER_CLIENT_VERSION: &str = kigi_version::VERSION;
+14 -14
View File
@@ -1,4 +1,4 @@
//! `grok completions <shell>` — generate shell completion scripts.
//! `kigi completions <shell>` — generate shell completion scripts.
//!
//! Used by the installers and npm postinstall; must stay side-effect free
//! (no network, auth, tracing, or tokio).
@@ -10,16 +10,16 @@ use crate::app::PagerArgs;
/// Generate and print the completion script for the given shell.
pub fn run(shell: Shell) {
// Ensure the script always uses the public "grok" name (matches historical
// Ensure the script always uses the public "kigi" name (matches historical
// behavior and what the installers + docs expect).
let mut cmd = PagerArgs::command().name("grok");
let mut cmd = PagerArgs::command().name("kigi");
if shell != Shell::Zsh {
generate(shell, &mut cmd, "grok", &mut std::io::stdout());
generate(shell, &mut cmd, "kigi", &mut std::io::stdout());
return;
}
// zsh needs post-processing (see fix_zsh_root_prompt_positional).
let mut buf = Vec::new();
generate(shell, &mut cmd, "grok", &mut buf);
generate(shell, &mut cmd, "kigi", &mut buf);
match String::from_utf8(buf) {
Ok(script) => print!("{}", fix_zsh_root_prompt_positional(&script)),
// clap_complete output is generated from Rust strings, so this arm is
@@ -39,7 +39,7 @@ pub fn run(shell: Shell) {
/// The generated root `_arguments` spec emits a `'::prompt …'` slot before
/// the subcommand slot but dispatches subcommands with `case $line[2]`. zsh
/// assigns the typed subcommand to the *prompt* slot (`$line[1]`), leaves
/// `$line[2]` empty, and the dispatch falls through — so `grok worktree <TAB>`
/// `$line[2]` empty, and the dispatch falls through — so `kigi worktree <TAB>`
/// re-offers every top-level command. (`hide = true` on the positional does
/// not change the generated script.)
///
@@ -64,8 +64,8 @@ fn fix_zsh_root_prompt_positional(script: &str) -> String {
r#"words=($line[1] "${words[@]}")"#,
),
(
r#"curcontext="${curcontext%:*:*}:grok-command-$line[2]:""#,
r#"curcontext="${curcontext%:*:*}:grok-command-$line[1]:""#,
r#"curcontext="${curcontext%:*:*}:kigi-command-$line[2]:""#,
r#"curcontext="${curcontext%:*:*}:kigi-command-$line[1]:""#,
),
(r#"case $line[2] in"#, r#"case $line[1] in"#),
] {
@@ -80,15 +80,15 @@ mod tests {
/// Generate the zsh completion script exactly like `run` does.
fn zsh_script() -> String {
let mut cmd = PagerArgs::command().name("grok");
let mut cmd = PagerArgs::command().name("kigi");
let mut buf = Vec::new();
generate(Shell::Zsh, &mut cmd, "grok", &mut buf);
generate(Shell::Zsh, &mut cmd, "kigi", &mut buf);
String::from_utf8(buf).expect("completion script is UTF-8")
}
// The optional `[PROMPT]` positional (app/cli.rs) makes clap_complete emit
// a `::prompt` slot before the subcommand slot and dispatch on `$line[2]`,
// so `grok worktree <TAB>` re-offered every top-level command (upstream
// so `kigi worktree <TAB>` re-offered every top-level command (upstream
// clap-rs/clap#6282).
#[test]
fn zsh_completions_drop_prompt_slot_and_dispatch_on_line_1() {
@@ -112,15 +112,15 @@ mod tests {
"root dispatch must be shifted to $line[1]"
);
assert!(
fixed.contains(r#"curcontext="${curcontext%:*:*}:grok-command-$line[1]:""#),
fixed.contains(r#"curcontext="${curcontext%:*:*}:kigi-command-$line[1]:""#),
"root dispatch context must use $line[1]"
);
// Subcommand dispatch blocks (already on $line[1]) must survive.
assert!(
fixed.contains("grok-worktree-command-$line[1]"),
fixed.contains("kigi-worktree-command-$line[1]"),
"nested subcommand dispatch must be untouched"
);
// The subcommand list itself must still be offered at the root.
assert!(fixed.contains("_grok_commands"), "root command list intact");
assert!(fixed.contains("_kigi_commands"), "root command list intact");
}
}
+3 -3
View File
@@ -2060,7 +2060,7 @@ mod tests {
#[test]
fn notification_none_protocol_no_warnings() {
let ctx = TerminalContext {
brand: TerminalName::GrokDesktop,
brand: TerminalName::KigiDesktop,
..Default::default()
};
let query = FakeTmuxQuery::healthy_modern();
@@ -2084,7 +2084,7 @@ mod tests {
assert!(supports_focus_tracking(TerminalName::Terminator));
assert!(supports_focus_tracking(TerminalName::WarpTerminal));
assert!(supports_focus_tracking(TerminalName::VsCode));
assert!(supports_focus_tracking(TerminalName::GrokDesktop));
assert!(supports_focus_tracking(TerminalName::KigiDesktop));
assert!(!supports_focus_tracking(TerminalName::AppleTerminal));
assert!(!supports_focus_tracking(TerminalName::Unknown));
assert!(!supports_focus_tracking(TerminalName::Otty));
@@ -2133,7 +2133,7 @@ mod tests {
line.starts_with(&format!(" themes {n}/{total}: ")),
"level {level:?}: {line}"
);
assert!(line.contains("groknight") && line.contains("grokday"));
assert!(line.contains("kiginight") && line.contains("kigiday"));
assert!(!line.contains("tokyonight"));
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ use crate::terminal::{TerminalName, terminal_context};
/// Fed from three places, all off the render path:
/// - [`cwd_git_info_lazy`] — a lazy, throttled refresh when a view reads a cwd.
/// - [`populate_from_cwd_async`] — an eager warm at startup / on a cwd change.
/// - [`update_from_notification`] — the `x.ai/git_head_changed` ACP
/// - [`update_from_notification`] — the `kigi/git_head_changed` ACP
/// notification, so a branch switch inside an agent reflects immediately
/// instead of waiting out [`CWD_GIT_REFRESH_TTL`].
type CwdCacheEntry = (Option<CwdGitInfo>, Instant);
+24 -24
View File
@@ -1,7 +1,7 @@
//! Headless single-turn mode (`grok -p "prompt"`).
//! Headless single-turn mode (`kigi -p "prompt"`).
//!
//! Runs the agent in-process via
//! `spawn_grok_shell`, sends the ACP lifecycle (init → auth → session → prompt),
//! `spawn_kigi_shell`, sends the ACP lifecycle (init → auth → session → prompt),
//! streams text to stdout, and exits cleanly via `CancellationToken`.
use std::collections::HashSet;
@@ -24,7 +24,7 @@ use kigi_shell::sampling::types::{
use kigi_shell::util::config as cli_config;
use crate::acp::model_state::{EffortTokenError, ModelState};
use crate::acp::spawn::spawn_grok_shell;
use crate::acp::spawn::spawn_kigi_shell;
use crate::client_identity::{HEADLESS_CLIENT_TYPE, PAGER_CLIENT_VERSION};
// ── Types ────────────────────────────────────────────────────────────────
@@ -618,7 +618,7 @@ async fn open_session(
let mut m = acp::Meta::new();
m.insert("noReplay".into(), serde_json::Value::Bool(true));
if let Some(true) = restore_code {
m.insert("x.ai/restore_code".into(), serde_json::Value::Bool(true));
m.insert("kigi/restore_code".into(), serde_json::Value::Bool(true));
}
Some(m)
}),
@@ -694,7 +694,7 @@ async fn fork_then_open(
let parent_is_worktree = parent_session_is_worktree(parent_id, &write_cwd);
let payload = fork_session_params(parent_id, &write_cwd, new_id, parent_is_worktree);
let req = acp::ExtRequest::new(
"x.ai/session/fork",
"kigi/session/fork",
serde_json::value::to_raw_value(&payload)
.expect("serialize fork params")
.into(),
@@ -796,7 +796,7 @@ async fn apply_headless_model_and_effort(
.map_err(|e| {
if let Some(name) = model_name {
anyhow::anyhow!(
"Couldn't set model '{}': {}. Run 'grok models' to see available models.",
"Couldn't set model '{}': {}. Run 'kigi models' to see available models.",
name,
e
)
@@ -916,7 +916,7 @@ pub async fn run_single_turn(
let cancel = CancellationToken::new();
let memory_config = agent_config.memory_config.clone();
let spawned = match spawn_grok_shell(agent_config, &cancel, memory_config).await {
let spawned = match spawn_kigi_shell(agent_config, &cancel, memory_config).await {
Ok(s) => s,
Err(e) => {
let msg = format!("Couldn't start session: {e}");
@@ -1121,9 +1121,9 @@ pub async fn run_single_turn(
let mut ttf_logged = false;
let mut prompt_fut = Box::pin(acp_send(request, &acp_tx));
let mut prompt_result = None;
// Pending background work: bash/monitor via x.ai/task_backgrounded +
// Pending background work: bash/monitor via kigi/task_backgrounded +
// task_completed; background subagents via SubagentSpawned + SubagentFinished
// on x.ai/session_notification (prefixed `subagent:{id}` in pending_bg).
// on kigi/session_notification (prefixed `subagent:{id}` in pending_bg).
// Tracked regardless of wait_for_background so the exit reaper always
// sees still-running work; the flag only gates waiting.
// No idle/quiet polling and no wait for server-side auto-wake text — exit
@@ -1336,13 +1336,13 @@ fn reap_request_for_key(
) -> serde_json::Result<acp::ExtRequest> {
let (method, params) = match key.strip_prefix("subagent:") {
Some(id) => (
"x.ai/subagent/cancel",
"kigi/subagent/cancel",
serde_json::value::to_raw_value(&CancelSubagentRequest {
subagent_id: id.to_string(),
})?,
),
None => (
"x.ai/task/kill",
"kigi/task/kill",
serde_json::value::to_raw_value(&KillTaskRequest {
session_id: session_id.0.to_string(),
task_id: key.to_string(),
@@ -1557,7 +1557,7 @@ fn handle_ext_notification(
let method = notif.request.method.as_ref();
// Background task lifecycle uses dedicated methods (not session_notification).
if method == "x.ai/task_backgrounded" {
if method == "kigi/task_backgrounded" {
#[derive(serde::Deserialize)]
struct TaskBgEnvelope {
update: TaskBgUpdate,
@@ -1587,7 +1587,7 @@ fn handle_ext_notification(
return ExtEvent::None;
}
if method == "x.ai/task_completed" {
if method == "kigi/task_completed" {
#[derive(serde::Deserialize)]
struct TaskDoneEnvelope {
update: TaskDoneUpdate,
@@ -1615,12 +1615,12 @@ fn handle_ext_notification(
return ExtEvent::None;
}
if method == "x.ai/monitor_event" {
if method == "kigi/monitor_event" {
return ExtEvent::MonitorEvent;
}
match method {
"x.ai/session_notification" | "x.ai/session/update" => {}
"kigi/session_notification" | "kigi/session/update" => {}
_ => return ExtEvent::None,
}
@@ -1800,7 +1800,7 @@ mod tests {
fn reap_request_for_task_kills_with_session_scope() {
let session_id = acp::SessionId::new("sess-1");
let request = super::reap_request_for_key("task-42", &session_id).unwrap();
assert_eq!(request.method.as_ref(), "x.ai/task/kill");
assert_eq!(request.method.as_ref(), "kigi/task/kill");
let params: serde_json::Value = serde_json::from_str(request.params.get()).unwrap();
assert_eq!(params["sessionId"], "sess-1");
assert_eq!(params["taskId"], "task-42");
@@ -1810,7 +1810,7 @@ mod tests {
fn reap_request_for_subagent_cancels_with_stripped_id() {
let session_id = acp::SessionId::new("sess-1");
let request = super::reap_request_for_key("subagent:sub-7", &session_id).unwrap();
assert_eq!(request.method.as_ref(), "x.ai/subagent/cancel");
assert_eq!(request.method.as_ref(), "kigi/subagent/cancel");
let params: serde_json::Value = serde_json::from_str(request.params.get()).unwrap();
assert_eq!(params["subagentId"], "sub-7");
}
@@ -2009,10 +2009,10 @@ mod tests {
#[test]
fn headless_task_backgrounded_parses_task_id() {
// `make_ext_notif` wraps the arg under `update`, so pass
// the inner update object (matching the real `x.ai/task_backgrounded`
// the inner update object (matching the real `kigi/task_backgrounded`
// wire shape: `{ "update": { "sessionUpdate": ..., "task_id": ... } }`).
let notif = make_ext_notif(
"x.ai/task_backgrounded",
"kigi/task_backgrounded",
serde_json::json!({
"sessionUpdate": "task_backgrounded",
"task_id": "task-abc",
@@ -2027,7 +2027,7 @@ mod tests {
#[test]
fn headless_task_backgrounded_with_monitor_description_is_monitor() {
let notif = make_ext_notif(
"x.ai/task_backgrounded",
"kigi/task_backgrounded",
serde_json::json!({
"sessionUpdate": "task_backgrounded",
"task_id": "mon-1",
@@ -2048,7 +2048,7 @@ mod tests {
// this test guards against a future `rename_all = "camelCase"` on
// `TaskSnapshot` silently turning waiting into a no-op.
let notif = make_ext_notif(
"x.ai/task_completed",
"kigi/task_completed",
serde_json::json!({
"sessionUpdate": "task_completed",
"task_snapshot": { "task_id": "task-abc" }
@@ -2063,7 +2063,7 @@ mod tests {
#[test]
fn headless_subagent_spawned_and_finished_parse() {
let spawned = make_ext_notif(
"x.ai/session_notification",
"kigi/session_notification",
serde_json::json!({
"sessionUpdate": "subagent_spawned",
"subagent_id": "sub-1",
@@ -2078,7 +2078,7 @@ mod tests {
ExtEvent::SubagentSpawned { subagent_id } if subagent_id == "sub-1"
));
let finished = make_ext_notif(
"x.ai/session_notification",
"kigi/session_notification",
serde_json::json!({
"sessionUpdate": "subagent_finished",
"subagent_id": "sub-1",
@@ -2107,7 +2107,7 @@ mod tests {
let raw = serde_json::value::to_raw_value(&payload).unwrap();
let (tx, _rx) = tokio::sync::oneshot::channel();
let notif = kigi_acp_lib::AcpArgs {
request: acp::ExtNotification::new("x.ai/other", raw.into()),
request: acp::ExtNotification::new("kigi/other", raw.into()),
response_tx: tx,
}
.boxed();
+1 -1
View File
@@ -340,7 +340,7 @@ impl ScrollConfig {
| TerminalName::Windsurf
| TerminalName::Zed => 1,
TerminalName::Kitty => 3,
TerminalName::GrokDesktop
TerminalName::KigiDesktop
| TerminalName::Vte
| TerminalName::Terminator
| TerminalName::WindowsTerminal
+1 -1
View File
@@ -1,4 +1,4 @@
//! kigi-tui — Grok Build TUI.
//! kigi-tui — Kigi TUI.
//!
//! A clean-room implementation built on the v3 pager rendering engine.

Some files were not shown because too many files have changed in this diff Show More