§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
@@ -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