§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
@@ -3,7 +3,7 @@
//! that cannot read the `!Send` `MvpAgent` state on the `LocalSet`).
//!
//! The leader's `agent_busy` flag only counts IPC (Unix-socket) requests;
//! relay (grok.com WebSocket) traffic is bridged straight into the agent's
//! relay (kimi.com WebSocket) traffic is bridged straight into the agent's
//! ACP stdin and never sets it, so a relay-driven leader (devbox / remote)
//! always looked idle and got restarted mid-turn on every update —
//! surfacing as "Subagent result channel dropped".
+18 -18
View File
@@ -83,7 +83,7 @@ const MAX_AUTO_UPDATE_BUSY_DEFERRALS: u32 = 24;
///
/// Idle means BOTH `agent_busy` is false (no IPC client request in flight)
/// AND `activity.is_busy()` is false (no running turn, parked interaction,
/// or live subagent). The second signal covers relay-driven (grok.com
/// or live subagent). The second signal covers relay-driven (kimi.com
/// WebSocket) leaders, whose traffic bypasses the IPC server and never sets
/// `agent_busy`.
///
@@ -217,10 +217,10 @@ fn spawn_agent_local(
}
/// Build a newline-terminated JSON-RPC request line for an internal
/// `x.ai/...` extension method, for injection into the agent's inbound ACP
/// `kigi/...` extension method, for injection into the agent's inbound ACP
/// stream by the leader's own watcher tasks (config hot-reload, skills).
///
/// The wire method is written **`_`-prefixed** (`_x.ai/internal/...`):
/// The wire method is written **`_`-prefixed** (`_kigi/internal/...`):
/// `agent-client-protocol`'s inbound decoder routes a non-built-in method to
/// `ext_method` only when it carries the `_` extension prefix and rejects
/// bare custom methods with `-32601 method_not_found`. These injections were
@@ -239,7 +239,7 @@ fn internal_reload_request_line(id: &str, method: &str, params: serde_json::Valu
format!("{}\n", msg)
}
/// Start a skills file watcher and wire it to inject `x.ai/internal/reload_skills`
/// Start a skills file watcher and wire it to inject `kigi/internal/reload_skills`
/// messages into the shared ACP incoming stream when SKILL.md files change on disk.
///
/// Returns the watcher guard (must be kept alive for the lifetime of the session)
@@ -264,7 +264,7 @@ where
info!("Skill directory changed on disk, reloading skills for all sessions");
let line = internal_reload_request_line(
"skills-reload",
"x.ai/internal/reload_skills",
"kigi/internal/reload_skills",
serde_json::json!({}),
);
let mut tx = skills_tx.lock().await;
@@ -297,7 +297,7 @@ pub async fn run_stdio_agent(
// are identifiable by version in diagnostic logs.
kigi_log::unified_log::set_version(kigi_version::VERSION);
// Log the client that launched us (set by grok-desktop when spawning `grok agent stdio`).
// Log the client that launched us (set by kigi-desktop when spawning `kigi agent stdio`).
// This appears early in unified.jsonl and is extremely useful for auth diagnostics.
if let Ok(version) = std::env::var("KIGI_CLIENT_VERSION") {
crate::unified_log::info(
@@ -369,8 +369,8 @@ pub async fn run_stdio_agent(
auth_manager.start_proactive_refresh(tokio_util::sync::CancellationToken::new());
// Pause refreshes across system sleep so an OIDC refresh can't straddle a
// suspend (which can revoke the refresh token and force re-login).
// `grok agent stdio` is a local/interactive entrypoint (spawned by
// grok-desktop), so it needs the gate like the leader and pager paths;
// `kigi agent stdio` is a local/interactive entrypoint (spawned by
// kigi-desktop), so it needs the gate like the leader and pager paths;
// no-op where the OS listener is unavailable.
auth_manager.start_system_power_listener();
@@ -907,7 +907,7 @@ pub async fn run_leader(
models_manager_for_config.on_auth_changed().await;
let line = internal_reload_request_line(
"config-auth-reloaded",
"x.ai/internal/reload_all_mcp_servers",
"kigi/internal/reload_all_mcp_servers",
serde_json::json!({}),
);
let mut tx = acp_tx_for_config.lock().await;
@@ -929,7 +929,7 @@ pub async fn run_leader(
info!("MCP server config change detected — reloading active sessions");
let line = internal_reload_request_line(
"config-reload-mcp",
"x.ai/internal/reload_all_mcp_servers",
"kigi/internal/reload_all_mcp_servers",
serde_json::json!({}),
);
let mut tx = acp_tx_for_config.lock().await;
@@ -952,7 +952,7 @@ pub async fn run_leader(
);
let line = internal_reload_request_line(
"config-reload-project-mcp",
"x.ai/internal/reload_project_mcp_servers",
"kigi/internal/reload_project_mcp_servers",
serde_json::json!({ "cwd": cwd.to_string_lossy() }),
);
let mut tx = acp_tx_for_config.lock().await;
@@ -967,7 +967,7 @@ pub async fn run_leader(
info!("Model config change detected — reloading agent model list");
let line = internal_reload_request_line(
"config-reload-models",
"x.ai/internal/reload_models",
"kigi/internal/reload_models",
serde_json::json!({}),
);
let mut tx = acp_tx_for_config.lock().await;
@@ -977,7 +977,7 @@ pub async fn run_leader(
}
ConfigUpdate::ModelsCacheChanged => {
// External write to ~/.kigi/models_cache.json
// (another grok process fetched a fresher /v1/models
// (another kigi process fetched a fresher /v1/models
// catalog). Injected into the agent's ACP stream —
// NOT applied directly on the manager — so it is
// serialized behind any `reload_models` from the
@@ -993,7 +993,7 @@ pub async fn run_leader(
info!("Models cache change detected — reloading agent model catalog");
let line = internal_reload_request_line(
"config-reload-models-cache",
"x.ai/internal/reload_models_cache",
"kigi/internal/reload_models_cache",
serde_json::json!({}),
);
let mut tx = acp_tx_for_config.lock().await;
@@ -1030,7 +1030,7 @@ pub async fn run_leader(
info!("UI config change detected by watcher");
let notification = serde_json::json!({
"jsonrpc": "2.0",
"method": "x.ai/config_changed",
"method": "kigi/config_changed",
"params": {
"section": "ui",
"changes": {
@@ -1114,13 +1114,13 @@ mod tests {
fn internal_reload_request_line_uses_wire_ext_prefix() {
let line = internal_reload_request_line(
"config-reload-models",
"x.ai/internal/reload_models",
"kigi/internal/reload_models",
serde_json::json!({}),
);
assert!(line.ends_with('\n'), "must be a newline-terminated line");
let msg: serde_json::Value = serde_json::from_str(line.trim_end()).unwrap();
assert_eq!(
msg["method"], "_x.ai/internal/reload_models",
msg["method"], "_kigi/internal/reload_models",
"wire method must carry the `_` ext prefix or the ACP decoder \
rejects it with method_not_found"
);
@@ -1130,7 +1130,7 @@ mod tests {
// Params must pass through verbatim (project-MCP reload carries cwd).
let line = internal_reload_request_line(
"config-reload-project-mcp",
"x.ai/internal/reload_project_mcp_servers",
"kigi/internal/reload_project_mcp_servers",
serde_json::json!({ "cwd": "/repo/x" }),
);
let msg: serde_json::Value = serde_json::from_str(line.trim_end()).unwrap();
@@ -101,7 +101,7 @@ pub struct BuiltAuthMethods {
/// Ordering (when each method is enabled):
/// 1. `xai.api_key` (if `has_external_api_key`)
/// 2. `cached_token` (if `has_cached_token`)
/// 3. `grok.com` (the Kimi Code device login)
/// 3. `kimi-code` (the Kimi Code device login)
///
/// `default_auth_method_id`:
/// - `cached_token` if `has_cached_token`
@@ -153,7 +153,7 @@ pub fn build_auth_methods(inputs: AuthMethodsBuildInputs<'_>) -> BuiltAuthMethod
pub enum AuthMethodKind {
XaiApiKey,
CachedToken,
GrokCom,
KimiCode,
Unknown,
}
@@ -162,7 +162,7 @@ impl AuthMethodKind {
match id.0.as_ref() {
XAI_API_KEY_METHOD_ID => Self::XaiApiKey,
CACHED_TOKEN_AUTH_METHOD_ID => Self::CachedToken,
KIGI_COM_METHOD_ID => Self::GrokCom,
KIMI_CODE_METHOD_ID => Self::KimiCode,
_ => Self::Unknown,
}
}
@@ -174,12 +174,12 @@ impl AuthMethodKind {
/// `true` for session-based methods (cached_token, interactive login).
pub fn is_session_based(self) -> bool {
matches!(self, Self::CachedToken | Self::GrokCom)
matches!(self, Self::CachedToken | Self::KimiCode)
}
/// Requires user interaction (device-code login in the browser).
pub fn needs_interactive_login(self) -> bool {
matches!(self, Self::GrokCom)
matches!(self, Self::KimiCode)
}
pub fn auth_error_message(self) -> &'static str {
@@ -259,7 +259,7 @@ pub fn method_id_after_cached_token_unavailable(has_external_api_key: bool) -> &
if has_external_api_key {
XAI_API_KEY_METHOD_ID
} else {
KIGI_COM_METHOD_ID
KIMI_CODE_METHOD_ID
}
}
@@ -287,17 +287,20 @@ pub fn cached_token_auth_method() -> acp::AuthMethod {
)
}
/// Interactive login method id. The literal `"grok.com"` is kept for ACP
/// wire-compat with the in-repo pager (renaming is a cross-crate wire change
/// deferred to the command-surface milestone).
pub const KIGI_COM_METHOD_ID: &str = "grok.com";
/// Interactive login method id, advertised over ACP by this agent and
/// selected by the in-repo pager. Both sides of the ACP boundary live in
/// this repo, so the id is renamed in lockstep everywhere.
pub const KIMI_CODE_METHOD_ID: &str = "kimi-code";
/// The Kimi Code device-code login.
pub fn kimi_code_auth_method(label: Option<&str>) -> acp::AuthMethod {
let name = label.unwrap_or("Kimi Code");
acp::AuthMethod::Agent(
acp::AuthMethodAgent::new(acp::AuthMethodId::new(KIGI_COM_METHOD_ID), name.to_string())
.description(Some(format!("Sign in with {name}"))),
acp::AuthMethodAgent::new(
acp::AuthMethodId::new(KIMI_CODE_METHOD_ID),
name.to_string(),
)
.description(Some(format!("Sign in with {name}"))),
)
}
@@ -323,14 +326,14 @@ mod tests {
fn after_cached_token_unavailable_falls_to_interactive_login() {
assert_eq!(
method_id_after_cached_token_unavailable(false),
KIGI_COM_METHOD_ID,
KIMI_CODE_METHOD_ID,
);
}
/// Classifier matrix for all auth method variants.
#[test]
fn auth_method_kind_classifier_matrix() {
let session_methods = [CACHED_TOKEN_AUTH_METHOD_ID, KIGI_COM_METHOD_ID];
let session_methods = [CACHED_TOKEN_AUTH_METHOD_ID, KIMI_CODE_METHOD_ID];
for id in session_methods {
let kind = AuthMethodKind::from_id(&acp::AuthMethodId::new(id));
assert!(kind.is_session_based(), "{id} must be session-based");
@@ -345,7 +348,7 @@ mod tests {
assert!(!unknown.is_session_based());
// Only the interactive login needs a browser.
assert!(
AuthMethodKind::from_id(&acp::AuthMethodId::new(KIGI_COM_METHOD_ID))
AuthMethodKind::from_id(&acp::AuthMethodId::new(KIMI_CODE_METHOD_ID))
.needs_interactive_login()
);
assert!(
@@ -428,7 +431,7 @@ mod tests {
});
assert_eq!(
method_ids(&built),
vec![XAI_API_KEY_METHOD_ID, KIGI_COM_METHOD_ID]
vec![XAI_API_KEY_METHOD_ID, KIMI_CODE_METHOD_ID]
);
assert_eq!(default_id(&built), Some(XAI_API_KEY_METHOD_ID));
assert!(
@@ -451,7 +454,7 @@ mod tests {
vec![
XAI_API_KEY_METHOD_ID,
CACHED_TOKEN_AUTH_METHOD_ID,
KIGI_COM_METHOD_ID
KIMI_CODE_METHOD_ID
]
);
assert_eq!(default_id(&built), Some(CACHED_TOKEN_AUTH_METHOD_ID));
@@ -466,7 +469,7 @@ mod tests {
});
assert_eq!(
method_ids(&built),
vec![CACHED_TOKEN_AUTH_METHOD_ID, KIGI_COM_METHOD_ID]
vec![CACHED_TOKEN_AUTH_METHOD_ID, KIMI_CODE_METHOD_ID]
);
assert_eq!(default_id(&built), Some(CACHED_TOKEN_AUTH_METHOD_ID));
assert_eq!(
@@ -480,7 +483,7 @@ mod tests {
#[test]
fn fresh_user_only_advertises_interactive_login() {
let built = build_auth_methods(default_inputs());
assert_eq!(method_ids(&built), vec![KIGI_COM_METHOD_ID]);
assert_eq!(method_ids(&built), vec![KIMI_CODE_METHOD_ID]);
assert_eq!(default_id(&built), None);
}
@@ -1,7 +1,7 @@
//! Legacy `--chat` gateway gate.
//!
//! The grok.com chat-product model picker (`/rest/modes`, `ChatModesManager`)
//! was removed with the xAI proxy: those "modes" came from a grok backend with
//! The kimi.com chat-product model picker (`/rest/modes`, `ChatModesManager`)
//! was removed with the xAI proxy: those "modes" came from a kigi backend with
//! no Kimi counterpart. Only the process-mode gate survives so the `--chat`
//! frontend path stays a compile-time-off no-op across crates without a
//! cross-crate churn to delete every reference.
@@ -10,7 +10,7 @@
pub const KIGI_CHAT_MODE_ENV: &str = "KIGI_CHAT_MODE";
/// True when the process is a gateway light-frontend (`--chat`) agent.
/// Hard-off: the grok chat-modes backend is gone, so this is always `false`.
/// Hard-off: the kigi chat-modes backend is gone, so this is always `false`.
pub fn process_chat_mode_enabled() -> bool {
false
}
+149 -140
View File
@@ -37,13 +37,11 @@ pub enum AgentMode {
Generic,
}
/// Default agent type when the server or user config doesn't specify one.
pub const DEFAULT_AGENT_TYPE: &str = "grok-build-plan";
pub const DEFAULT_AGENT_TYPE: &str = "kigi-plan";
/// Serde default for `ModelInfo.agent_type` and `ModelEntryConfig.agent_type`.
pub fn default_agent_type() -> String {
DEFAULT_AGENT_TYPE.to_owned()
}
/// Default base URL for the public xAI API.
pub const API_BASE_URL_DEFAULT: &str = "https://api.x.ai/v1";
/// One or more environment variable names that may hold a model API key.
///
/// Serde `untagged`: accepts a string or an array in TOML/JSON.
@@ -143,9 +141,14 @@ pub struct EndpointsConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub coding_api_base_url: Option<String>,
/// Base URL for direct (BYOK / external-API-key) API calls.
///
/// There is NO built-in default endpoint: a BYOK/custom endpoint must be
/// explicitly configured via `[endpoints] api_base_url` in config.toml,
/// the `KIGI_API_BASE_URL` env var, `--api-base-url`, or a managed
/// requirements pin. `None` = not configured.
/// Accepts the legacy `xai_api_base_url` config key.
#[serde(alias = "xai_api_base_url")]
pub api_base_url: String,
#[serde(alias = "xai_api_base_url", skip_serializing_if = "Option::is_none")]
pub api_base_url: Option<String>,
/// Optional extra access-header value (applied only with the optional
/// non-production feature, and only for matching first-party hosts).
#[serde(skip_serializing_if = "Option::is_none")]
@@ -225,7 +228,7 @@ impl EndpointsConfig {
pub fn resolve_feedback_base_url(&self) -> String {
blank_as_unset(&self.feedback_base_url).unwrap_or_else(|| self.proxy_url())
}
/// Managed deployment-config URL (`grok setup`): explicit `managed_config_url`,
/// Managed deployment-config URL (`kigi setup`): explicit `managed_config_url`,
/// else `proxy_url` + `/deployment/config`. Never `api_base_url`, so the
/// deployment key reaches the proxy, not the inference host.
pub fn resolve_managed_config_url(&self) -> String {
@@ -252,8 +255,7 @@ impl Default for EndpointsConfig {
fn default() -> Self {
Self {
coding_api_base_url: std::env::var("KIGI_CODE_BASE_URL").ok(),
api_base_url: std::env::var("KIGI_API_BASE_URL")
.unwrap_or_else(|_| API_BASE_URL_DEFAULT.to_owned()),
api_base_url: env_string("KIGI_API_BASE_URL"),
alpha_test_key: None,
models_base_url: env_string("KIGI_MODELS_BASE_URL"),
models_list_url: env_string("KIGI_MODELS_LIST_URL"),
@@ -1000,7 +1002,7 @@ pub struct Config {
/// `[model.*]` overrides from config.toml. Resolve via `resolve_model_list()`.
#[serde(skip)]
pub config_models: IndexMap<String, ConfigModelOverride>,
/// Warnings from `[model.*]` parsing; surfaced by `grok inspect`.
/// Warnings from `[model.*]` parsing; surfaced by `kigi inspect`.
#[serde(skip)]
pub model_override_warnings: Vec<super::config_model_override_parse::ModelOverrideWarning>,
pub kimi_code_config: KimiCodeConfig,
@@ -1076,7 +1078,7 @@ pub struct Config {
/// Typed as `KimiCodeConfig` (same schema) so sub-field typos are caught.
#[serde(default, skip_serializing)]
pub auth: Option<KimiCodeConfig>,
/// `[desktop]` section — owned by grok-desktop (Electron app), opaque to the CLI agent.
/// `[desktop]` section — owned by kigi-desktop (Electron app), opaque to the CLI agent.
#[serde(default, skip_serializing)]
pub desktop: Option<toml::Value>,
/// `[tips]` section — consumed by `merge_tips`.
@@ -1311,13 +1313,13 @@ pub use kigi_shared::ui_config::{ContextualHints, UiConfig};
/// 2. CLI `--agent-profile` flag
/// 3. `[agent]` config.toml section (this config)
/// 4. `KIGI_AGENT` env var
/// 5. Default `grok-build` agent
/// 5. Default `kigi` agent
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct AgentSelectionConfig {
/// Name of a built-in or discovered agent definition.
/// Looked up via `kigi_agent::discovery::by_name_in_cwd()`.
/// Examples: "grok-build", "browser-use", or a custom agent name.
/// Examples: "kigi", "browser-use", or a custom agent name.
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
/// Path to an agent definition file (.md with YAML frontmatter).
@@ -1718,7 +1720,7 @@ impl Config {
.default(false)
.resolve()
}
/// Server-side doom-loop check policy (the `x-grok-doom-loop-check`
/// Server-side doom-loop check policy (the `x-kigi-doom-loop-check`
/// header, trigger parsing, and confident-signal resampling, all
/// applied by the sampler). Merged
/// PER-FIELD across the `[doom_loop_recovery]` TOML table and the
@@ -2146,7 +2148,7 @@ impl Config {
resolve_mcp_auto_restart(None, None, self.features.mcp_auto_restart, None, None)
}
/// Resolve whether the pager subscribes to the per-server
/// `x.ai/mcp/server_status` push.
/// `kigi/mcp/server_status` push.
///
/// Thin delegate to the canonical
/// [`resolve_mcp_push_server_status`] free function — mirrors the
@@ -2251,7 +2253,7 @@ pub fn resolve_mcp_auto_restart(
/// the precedence is single-sourced.
///
/// The default is `true` — the pager's subscription to
/// `x.ai/mcp/server_status` is wired default-on, with this
/// `kigi/mcp/server_status` is wired default-on, with this
/// flag existing primarily as a kill switch.
pub fn resolve_mcp_push_server_status(
requirement: Option<bool>,
@@ -2347,7 +2349,7 @@ impl SyncBoolFlag {
self.disable_env = Some(name);
self
}
/// Either-direction env resolver (typically `GROK_*`). Returns
/// Either-direction env resolver (typically `KIGI_*`). Returns
/// `Some(enabled)` for an explicit signal, `None` to fall through.
pub const fn enable_env(mut self, resolver: fn() -> Option<bool>) -> Self {
self.enable_env = Some(resolver);
@@ -2830,7 +2832,7 @@ pub struct ModelEntryConfig {
pub id: Option<String>,
/// The routing slug sent in API requests.
pub model: String,
/// The base URL of the model. e.g. "https://api.x.ai/v1"
/// The base URL of the model. e.g. "https://byok.example/v1"
pub base_url: String,
/// Human-readable display name of the model.
#[serde(skip_serializing_if = "Option::is_none")]
@@ -2899,7 +2901,7 @@ pub struct ModelEntryConfig {
#[serde(default, skip_serializing_if = "is_false")]
pub use_concise: bool,
/// The type of system prompt to use for this model.
/// e.g. "grok-build", "codex".
/// e.g. "kigi", "codex".
#[serde(default = "default_agent_type")]
pub agent_type: String,
/// Maximum seconds to wait between SSE chunks during inference streaming.
@@ -3143,7 +3145,7 @@ pub struct ModelInfo {
/// concise tool output, concise user message prefix, reduced toolset).
pub use_concise: bool,
/// The type of agent configuration to use for this model.
/// Always has a value; defaults to `"grok-build-plan"` when the server
/// Always has a value; defaults to `"kigi-plan"` when the server
/// or user config doesn't specify one.
#[serde(default = "default_agent_type")]
pub agent_type: String,
@@ -3523,9 +3525,9 @@ pub struct Features {
/// Default: true (index any git repo). Patterns can explicitly match non-git directories.
#[serde(default)]
pub codebase_indexing: CodebaseIndexingSetting,
/// Show a blocking warning when Grok starts outside a Git repository.
/// Show a blocking warning when Kigi starts outside a Git repository.
/// Default: false. Used as the local fallback when the `non_git_warning` remote settings
/// flag in `grok_build_settings` is absent. When the remote flag is present it takes
/// flag in `kigi_settings` is absent. When the remote flag is present it takes
/// precedence — `Some(false)` from remote settings overrides `true` here.
#[serde(default)]
pub non_git_warning: bool,
@@ -3600,7 +3602,7 @@ pub struct Features {
///
/// When `true` (default), each successfully-handshaken MCP
/// client gets a poller that detects rmcp service-loop
/// termination and pushes `x.ai/mcp/server_status` updates to
/// termination and pushes `kigi/mcp/server_status` updates to
/// the client. When `false`, neither watchers nor the
/// dispatcher are spawned — useful as an emergency kill switch
/// for the rollout. `None` = defer to env / default (true).
@@ -3622,13 +3624,13 @@ pub struct Features {
/// Resolved via [`Config::resolve_mcp_auto_restart`].
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mcp_auto_restart: Option<bool>,
/// Pager-side subscription to the `x.ai/mcp/server_status` push.
/// Pager-side subscription to the `kigi/mcp/server_status` push.
///
/// When `true` (default), the pager subscribes to the per-server
/// status delta the shell emits via the dispatcher and
/// patches the MCP servers modal in-place (no re-fetch round
/// trip). When `false`, the pager ignores the push and falls
/// back to the legacy `x.ai/mcp/tools_changed` debounced refetch
/// back to the legacy `kigi/mcp/tools_changed` debounced refetch
/// path. `None` = defer to env / default (true).
///
/// The pager-side gate
@@ -4169,7 +4171,7 @@ mod tests {
tools: Some(vec!["read_file".into()]),
..Default::default()
};
let mut cases = vec![(AgentDefinition::default_grok_build(), true)];
let mut cases = vec![(AgentDefinition::default_kigi(), true)];
for (mut definition, expected_injection) in cases {
overrides.apply_to_definition(&mut definition);
assert_eq!(definition.tools, vec!["read_file".to_string()]);
@@ -4185,13 +4187,13 @@ mod tests {
let toml_src = r#"
enabled = true
prompt_type = "no_user_tool_prefix"
classifier_model = "grok-4.5"
classifier_model = "kigi-4.5"
reasoning_effort = "low"
"#;
let from_toml: AutoModeConfig = toml::from_str(toml_src).unwrap();
let json = serde_json::json!(
{ "enabled" : true, "prompt_type" : "no_user_tool_prefix", "classifier_model"
: "grok-4.5", "reasoning_effort" : "low" }
: "kigi-4.5", "reasoning_effort" : "low" }
);
let from_json: AutoModeConfig = serde_json::from_value(json).unwrap();
for cfg in [&from_toml, &from_json] {
@@ -4200,7 +4202,7 @@ reasoning_effort = "low"
cfg.prompt_type,
Some(ClassifierPromptType::NoUserToolPrefix)
);
assert_eq!(cfg.classifier_model.as_deref(), Some("grok-4.5"));
assert_eq!(cfg.classifier_model.as_deref(), Some("kigi-4.5"));
assert_eq!(cfg.reasoning_effort, Some(ReasoningEffort::Low));
}
let empty: AutoModeConfig = toml::from_str("").unwrap();
@@ -4450,7 +4452,7 @@ reasoning_effort = "low"
let (model, cfg) = finalize_image_describe_sampler_config(None, &active, Some(3));
assert_eq!(model, "composer-session-model");
assert_eq!(cfg.model, "composer-session-model");
assert_ne!(cfg.model, "grok-build");
assert_ne!(cfg.model, "kigi");
}
#[test]
fn finalize_image_describe_sampler_some_stamps_session_fields() {
@@ -4459,20 +4461,20 @@ reasoning_effort = "low"
..Default::default()
};
let aux = SamplerConfig {
model: "grok-build".into(),
model: "kigi".into(),
..Default::default()
};
let (model, cfg) = finalize_image_describe_sampler_config(Some(aux), &active, Some(7));
assert_eq!(model, "grok-build");
assert_eq!(cfg.model, "grok-build");
assert_eq!(model, "kigi");
assert_eq!(cfg.model, "kigi");
assert_eq!(cfg.max_retries, Some(7));
}
#[test]
fn resolve_aux_model_honors_grok_build_override() {
fn resolve_aux_model_honors_kigi_override() {
let endpoints = EndpointsConfig::default();
let mut catalog = IndexMap::new();
catalog.insert(
"grok-build".to_string(),
"kigi".to_string(),
test_model_entry(
"v9m-rl-learnability-tp8",
"https://vendor.example/v1",
@@ -4481,9 +4483,8 @@ reasoning_effort = "low"
None,
),
);
let resolved =
resolve_aux_model_sampling_config("grok-build", &catalog, &endpoints, None, None)
.expect("override entry has an API key, so resolution succeeds");
let resolved = resolve_aux_model_sampling_config("kigi", &catalog, &endpoints, None, None)
.expect("override entry has an API key, so resolution succeeds");
assert_eq!(resolved.model, "v9m-rl-learnability-tp8");
assert_eq!(resolved.base_url, "https://vendor.example/v1");
assert_eq!(resolved.api_key.as_deref(), Some("vendor-key"));
@@ -4493,7 +4494,7 @@ reasoning_effort = "low"
let raw_config: toml::Value = toml::from_str(
r#"
[model.my-custom-model]
model = "grok-4.5"
model = "kigi-4.5"
base_url = "https://api.example.com/v1"
context_window = 200000
api_key = "sk-test-key-12345"
@@ -4503,7 +4504,7 @@ reasoning_effort = "low"
let cfg = Config::new_from_toml_cfg(&raw_config).expect("config should parse");
let resolved = resolve_model_list(&cfg, None);
let model = resolved.get("my-custom-model").expect("model should exist");
assert_eq!(model.info.model, "grok-4.5");
assert_eq!(model.info.model, "kigi-4.5");
assert_eq!(model.info.base_url, "https://api.example.com/v1");
assert_eq!(model.api_key, Some("sk-test-key-12345".to_string()));
}
@@ -4633,8 +4634,9 @@ reasoning_effort = "low"
auth_scheme: AuthScheme::Bearer,
};
assert_eq!(
api_key_creds.base_url, endpoints.api_base_url,
"{model_id}: ExternalApiKey must route to api.x.ai"
Some(api_key_creds.base_url.as_str()),
endpoints.api_base_url.as_deref(),
"{model_id}: ExternalApiKey must route to the configured BYOK endpoint"
);
}
}
@@ -4874,7 +4876,7 @@ reasoning_effort = "low"
#[test]
fn proxy_messages_models_use_bearer_auth_scheme() {
let mut model = test_model_entry(
"grok-4.5",
"kigi-4.5",
kigi_env::PRODUCTION_ENDPOINTS.coding_api_base_url,
None,
None,
@@ -4937,7 +4939,7 @@ reasoning_effort = "low"
#[test]
fn auth_scheme_defaults_to_bearer_when_not_set_in_config() {
let model = test_model_entry(
"grok-4.5",
"kigi-4.5",
"https://api.example.com/v1",
Some("sk-openai-test"),
None,
@@ -4994,7 +4996,7 @@ reasoning_effort = "low"
byok_from_lookup(&ModelLookup::Loaded(Some(&byok))),
ModelByok::Byok,
);
let session = test_model_entry("m", "https://api.x.ai/v1", None, None, None);
let session = test_model_entry("m", "https://byok.example/v1", None, None, None);
assert_eq!(
byok_from_lookup(&ModelLookup::Loaded(Some(&session))),
ModelByok::NotByok,
@@ -5257,10 +5259,10 @@ reasoning_effort = "low"
}
#[test]
fn sampling_config_context_window_from_entry_or_default() {
let model = test_model_entry("any-model", "https://api.x.ai/v1", None, None, None);
let model = test_model_entry("any-model", "https://byok.example/v1", None, None, None);
let config = sampling_config_for_model(&model, resolve_credentials(&model, None), None);
assert_eq!(config.context_window, 200_000);
let mut model = test_model_entry("any-model", "https://api.x.ai/v1", None, None, None);
let mut model = test_model_entry("any-model", "https://byok.example/v1", None, None, None);
model.info.context_window = NonZeroU64::new(256_000).unwrap();
let config = sampling_config_for_model(&model, resolve_credentials(&model, None), None);
assert_eq!(config.context_window, 256_000);
@@ -5270,7 +5272,7 @@ reasoning_effort = "low"
let raw_config: toml::Value = toml::from_str(
r#"
[model.my-responses-model]
model = "grok-4.5"
model = "kigi-4.5"
base_url = "https://api.example.com/v1"
context_window = 200000
api_backend = "responses"
@@ -5289,7 +5291,7 @@ reasoning_effort = "low"
let raw_config: toml::Value = toml::from_str(
r#"
[model.my-chat-model]
model = "grok-4.5"
model = "kigi-4.5"
base_url = "https://api.example.com/v1"
context_window = 200000
api_backend = "chat_completions"
@@ -5309,7 +5311,7 @@ reasoning_effort = "low"
let raw_config: toml::Value = toml::from_str(
r#"
[model.my-claude]
model = "grok-4.5"
model = "kigi-4.5"
base_url = "https://messages.example.com"
context_window = 200000
api_backend = "messages"
@@ -5331,7 +5333,7 @@ reasoning_effort = "low"
let raw_config: toml::Value = toml::from_str(
r#"
[model.my-claude]
model = "grok-4.5"
model = "kigi-4.5"
base_url = "https://messages.example.com"
context_window = 200000
api_backend = "messages"
@@ -5354,7 +5356,7 @@ reasoning_effort = "low"
let raw_config: toml::Value = toml::from_str(
r#"
[model.my-openai]
model = "grok-4.5"
model = "kigi-4.5"
base_url = "https://api.example.com/v1"
context_window = 200000
api_backend = "chat_completions"
@@ -5374,7 +5376,7 @@ reasoning_effort = "low"
let raw_config: toml::Value = toml::from_str(
r#"
[model.my-model]
model = "grok-4.5"
model = "kigi-4.5"
base_url = "https://api.example.com/v1"
context_window = 200000
"#,
@@ -5574,7 +5576,7 @@ reasoning_effort = "low"
assert_eq!(model.info.agent_type, "codex");
}
#[test]
fn model_agent_type_defaults_to_grok_build() {
fn model_agent_type_defaults_to_kigi() {
let raw_config: toml::Value = toml::from_str(
r#"
[model.my-model]
@@ -5834,12 +5836,12 @@ reasoning_effort = "low"
r#"
[model.visible-model]
model = "visible-model"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 200000
[model.hidden-model]
model = "hidden-model"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 200000
hidden = true
"#,
@@ -5874,7 +5876,7 @@ reasoning_effort = "low"
disabled_models = ["to-disable"]
[model.to-disable]
model = "to-disable"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 200000
"#,
)
@@ -5891,7 +5893,7 @@ reasoning_effort = "low"
hidden_models = ["to-hide"]
[model.to-hide]
model = "to-hide"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 200000
"#,
)
@@ -5911,15 +5913,15 @@ reasoning_effort = "low"
allowed_models = ["keep-*", "explicit-key", "explicit-model-id"]
[model.to-drop]
model = "to-drop"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 256000
[model.keep-one]
model = "keep-one"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 256000
[model.explicit-key]
model = "explicit-model-id"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 256000
"#,
)
@@ -5944,7 +5946,7 @@ reasoning_effort = "low"
allowed_models = []
[model.foo]
model = "foo"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 256000
"#,
)
@@ -5958,11 +5960,11 @@ reasoning_effort = "low"
#[test]
fn invalid_glob_is_rejected_by_validation() {
use crate::agent::models::ModelGlobSet;
assert!(ModelGlobSet::compile(Some(&vec!["grok[".to_string()])).is_err());
assert!(ModelGlobSet::compile(Some(&vec!["kigi[".to_string()])).is_err());
let raw: toml::Value = toml::from_str(
r#"
[models]
allowed_models = ["grok["]
allowed_models = ["kigi["]
"#,
)
.unwrap();
@@ -5982,13 +5984,13 @@ reasoning_effort = "low"
r#"
[model.oauth-only-model]
model = "oauth-only-model"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 200000
supported_in_api = false
[model.public-model]
model = "public-model"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 200000
"#,
)
@@ -6013,8 +6015,8 @@ reasoning_effort = "low"
let raw_config: toml::Value = toml::from_str(
r#"
[model.slow-model]
model = "grok-4.5"
base_url = "https://api.x.ai/v1"
model = "kigi-4.5"
base_url = "https://byok.example/v1"
context_window = 200000
inference_idle_timeout_secs = 600
"#,
@@ -6030,8 +6032,8 @@ reasoning_effort = "low"
let raw_config: toml::Value = toml::from_str(
r#"
[model.default-model]
model = "grok-fast"
base_url = "https://api.x.ai/v1"
model = "kigi-fast"
base_url = "https://byok.example/v1"
context_window = 200000
"#,
)
@@ -6136,7 +6138,7 @@ reasoning_effort = "low"
);
assert_eq!(
sampling.base_url, "https://inference.example.com/v1",
"should route to the user's custom endpoint, not api.x.ai"
"should route to the user's custom endpoint, not the BYOK endpoint"
);
unsafe { std::env::remove_var("ENTERPRISE_AUTH_TOKEN") };
}
@@ -6229,8 +6231,8 @@ reasoning_effort = "low"
fn config_models_default_custom_model_is_in_resolved_model_list() {
let (_, models) = resolve_models_from_toml(
r#"
[model.acme-grok]
model = "grok-4.5"
[model.acme-kigi]
model = "kigi-4.5"
base_url = "https://inference.example.com/v1"
context_window = 256000
env_key = "ENTERPRISE_AUTH_TOKEN"
@@ -6238,11 +6240,11 @@ reasoning_effort = "low"
None,
);
assert!(
models.contains_key("acme-grok"),
models.contains_key("acme-kigi"),
"user-defined model must be in the resolved model list"
);
let model = models.get("acme-grok").unwrap();
assert_eq!(model.info.model, "grok-4.5");
let model = models.get("acme-kigi").unwrap();
assert_eq!(model.info.model, "kigi-4.5");
assert_eq!(model.info.base_url, "https://inference.example.com/v1");
}
#[test]
@@ -6342,7 +6344,7 @@ reasoning_effort = "low"
"https://proxy.api/v1",
None,
None,
Some("https://api.x.ai/v1"),
Some("https://byok.example/v1"),
);
let sampling = resolve_sampling(&model_no_key, Some("session-key"));
assert_eq!(
@@ -6361,7 +6363,7 @@ reasoning_effort = "low"
"env key should be used when no session and no model credentials"
);
assert_eq!(
sampling.base_url, "https://api.x.ai/v1",
sampling.base_url, "https://byok.example/v1",
"env key should route to api_base_url"
);
unsafe { std::env::remove_var("XAI_API_KEY") };
@@ -6447,17 +6449,17 @@ reasoning_effort = "low"
fn e2e_acp_model_info_no_dedup_on_model_field() {
let mut models = IndexMap::new();
models.insert(
"default-grok".to_string(),
"default-kigi".to_string(),
test_model_entry(
crate::models::default_model(),
"https://api.kimi.com/coding/v1",
None,
None,
Some("https://api.x.ai/v1"),
Some("https://byok.example/v1"),
),
);
models.insert(
"acme-grok".to_string(),
"acme-kigi".to_string(),
test_model_entry(
crate::models::default_model(),
"https://inference.example.com/v1",
@@ -6473,11 +6475,11 @@ reasoning_effort = "low"
"both entries should survive in ACP model list"
);
assert!(
acp_models.contains_key(&acp::ModelId::new("default-grok")),
acp_models.contains_key(&acp::ModelId::new("default-kigi")),
"default entry should be addressable by map key"
);
assert!(
acp_models.contains_key(&acp::ModelId::new("acme-grok")),
acp_models.contains_key(&acp::ModelId::new("acme-kigi")),
"user entry should be addressable by map key"
);
}
@@ -6550,6 +6552,16 @@ reasoning_effort = "low"
unsafe { std::env::remove_var(k) };
}
}
/// PRD §9: there is no built-in BYOK endpoint default — `api_base_url`
/// stays unset unless explicitly configured (config key, env var, CLI
/// flag, or requirements pin).
#[test]
#[serial]
fn api_base_url_has_no_default() {
unset_endpoint_env_vars();
assert_eq!(EndpointsConfig::default().api_base_url, None);
}
/// INVARIANT: auxiliary-service resolvers resolve to the cli-chat-proxy, never
/// `api_base_url` — overriding ONLY inference keeps every aux endpoint on
/// the proxy; explicit per-service overrides win verbatim.
@@ -6559,7 +6571,7 @@ reasoning_effort = "low"
unset_endpoint_env_vars();
let inference = "https://inference.acme-corp.example/xai/v1";
let cfg = EndpointsConfig {
api_base_url: inference.to_string(),
api_base_url: Some(inference.to_string()),
coding_api_base_url: None,
..Default::default()
};
@@ -6572,7 +6584,7 @@ reasoning_effort = "low"
format!("{proxy}/deployment/config")
);
assert_eq!(cfg.resolve_feedback_base_url(), proxy);
assert_eq!(cfg.api_base_url, inference);
assert_eq!(cfg.api_base_url.as_deref(), Some(inference));
let overridden = EndpointsConfig {
coding_api_base_url: Some("https://proxy.enterprise.example/v1".to_string()),
managed_config_url: Some(
@@ -7495,13 +7507,13 @@ reverify_after = 6
}
fn planner_pair() -> crate::util::config::GoalRoleModel {
crate::util::config::GoalRoleModel {
model: "grok-4".to_string(),
model: "kigi-4".to_string(),
agent_type: "general-purpose".to_string(),
}
}
fn strategist_pair() -> crate::util::config::GoalRoleModel {
crate::util::config::GoalRoleModel {
model: "grok-4.5".to_string(),
model: "kigi-4.5".to_string(),
agent_type: "cursor".to_string(),
}
}
@@ -7701,29 +7713,29 @@ reverify_after = 6
let toml_str = r#"
[goal]
enabled = true
planner_model = { model = "grok-build", agent_type = "grok-build-plan" }
planner_model = { model = "kigi", agent_type = "kigi-plan" }
[goal.strategist_model]
model = "grok-composer-2.5-fast"
model = "kigi-composer-2.5-fast"
agent_type = "cursor"
[[goal.skeptic_models]]
model = "grok-build"
agent_type = "grok-build-plan"
model = "kigi"
agent_type = "kigi-plan"
[[goal.skeptic_models]]
model = "grok-composer-2.5-fast"
model = "kigi-composer-2.5-fast"
agent_type = "cursor"
"#;
let raw: toml::Value = toml::from_str(toml_str).unwrap();
let cfg = Config::new_from_toml_cfg(&raw).unwrap();
assert_eq!(cfg.goal.planner_model.as_ref().unwrap().model, "grok-build");
assert_eq!(cfg.goal.planner_model.as_ref().unwrap().model, "kigi");
assert_eq!(
cfg.goal.strategist_model.as_ref().unwrap().agent_type,
"cursor"
);
assert_eq!(cfg.goal.skeptic_models.len(), 2);
assert_eq!(cfg.goal.skeptic_models[0].model, "grok-build");
assert_eq!(cfg.goal.skeptic_models[0].model, "kigi");
assert_eq!(
cfg.resolve_goal_planner_model(false).source,
ConfigSource::Config
@@ -7737,7 +7749,7 @@ agent_type = "cursor"
[goal]
enabled = true
classifier_max_runs = 6
planner_model = { agent_type = "grok-build-plan" }
planner_model = { agent_type = "kigi-plan" }
"#;
let raw: toml::Value = toml::from_str(toml_str).unwrap();
let cfg = Config::new_from_toml_cfg(&raw)
@@ -7752,21 +7764,21 @@ planner_model = { agent_type = "grok-build-plan" }
enabled = true
[[goal.skeptic_models]]
model = "grok-build"
agent_type = "grok-build-plan"
model = "kigi"
agent_type = "kigi-plan"
[[goal.skeptic_models]]
agent_type = "cursor"
[[goal.skeptic_models]]
model = "grok-composer-2.5-fast"
model = "kigi-composer-2.5-fast"
agent_type = "cursor"
"#;
let raw: toml::Value = toml::from_str(toml_str).unwrap();
let cfg = Config::new_from_toml_cfg(&raw).unwrap();
assert_eq!(cfg.goal.skeptic_models.len(), 2);
assert_eq!(cfg.goal.skeptic_models[0].model, "grok-build");
assert_eq!(cfg.goal.skeptic_models[1].model, "grok-composer-2.5-fast");
assert_eq!(cfg.goal.skeptic_models[0].model, "kigi");
assert_eq!(cfg.goal.skeptic_models[1].model, "kigi-composer-2.5-fast");
}
/// Acceptance test: a full managed-config `[goal]` block resolves end-to-end,
/// every value sourced from config (not remote/default).
@@ -7783,26 +7795,26 @@ classifier_enabled = true
planner_enabled = true
verifier_count = 3
classifier_max_runs = 6
planner_model = { model = "grok-build", agent_type = "grok-build-plan" }
strategist_model = { model = "grok-composer-2.5-fast", agent_type = "cursor" }
planner_model = { model = "kigi", agent_type = "kigi-plan" }
strategist_model = { model = "kigi-composer-2.5-fast", agent_type = "cursor" }
[[goal.skeptic_models]]
model = "grok-build"
agent_type = "grok-build-plan"
model = "kigi"
agent_type = "kigi-plan"
[[goal.skeptic_models]]
model = "grok-composer-2.5-fast"
model = "kigi-composer-2.5-fast"
agent_type = "cursor"
"#,
)
.unwrap();
let cfg = Config::new_from_toml_cfg(&raw).expect("[goal] config must parse");
let grok_build = crate::util::config::GoalRoleModel {
model: "grok-build".into(),
agent_type: "grok-build-plan".into(),
let kigi = crate::util::config::GoalRoleModel {
model: "kigi".into(),
agent_type: "kigi-plan".into(),
};
let composer = crate::util::config::GoalRoleModel {
model: "grok-composer-2.5-fast".into(),
model: "kigi-composer-2.5-fast".into(),
agent_type: "cursor".into(),
};
let goal_enabled = cfg.resolve_goal().value;
@@ -7814,10 +7826,7 @@ agent_type = "cursor"
let use_current = cfg.resolve_goal_use_current_model_only().value;
assert!(!use_current);
let planner = cfg.resolve_goal_planner_model(use_current);
assert_eq!(
planner.value,
GoalRoleModelChoice::Explicit(grok_build.clone())
);
assert_eq!(planner.value, GoalRoleModelChoice::Explicit(kigi.clone()));
assert_eq!(planner.source, ConfigSource::Config);
assert_eq!(
cfg.resolve_goal_strategist_model(use_current).value,
@@ -7826,7 +7835,7 @@ agent_type = "cursor"
assert_eq!(
cfg.resolve_goal_skeptic_models(use_current).value,
vec![
GoalRoleModelChoice::Explicit(grok_build),
GoalRoleModelChoice::Explicit(kigi),
GoalRoleModelChoice::Explicit(composer),
]
);
@@ -7910,7 +7919,7 @@ agent_type = "cursor"
management_api_key = "mgmt-key"
gcs_service_account_key = "gcs-key"
[models]
default = "grok-3"
default = "kigi-3"
[ui]
yolo = true
theme = "dark"
@@ -8532,25 +8541,25 @@ hooks = true
let mut value: toml::Value = toml::from_str(
r#"
[models]
default = "grok-build"
default = "kigi"
[[version_overrides]]
minimum_version = "1.8.0"
[version_overrides.models]
default = "grok-4.5"
default = "kigi-4.5"
"#,
)
.unwrap();
let v = semver::Version::parse("1.8.0").unwrap();
kigi_config::apply_version_overrides(&mut value, &v).unwrap();
let cfg = Config::new_from_toml_cfg(&value).unwrap();
assert_eq!(cfg.models.default.as_deref(), Some("grok-4.5"));
assert_eq!(cfg.models.default.as_deref(), Some("kigi-4.5"));
}
/// Reproduce the enterprise managed config bug: [model.kigi-build] sets
/// context_window=500k for model="grok-4.5", but
/// [models].default="grok-4.5" resolves to the bare
/// context_window=500k for model="kigi-4.5", but
/// [models].default="kigi-4.5" resolves to the bare
/// prefetched entry (256k) because Layer 3 only overrides key
/// "kigi-build", not key "grok-4.5".
/// "kigi-build", not key "kigi-4.5".
///
/// After the Layer 4 slug propagation fix, both keys should have 500k.
#[test]
@@ -8559,10 +8568,10 @@ default = "grok-4.5"
let raw: toml::Value = toml::from_str(
r#"
[models]
default = "grok-4.5"
default = "kigi-4.5"
[model.kigi-build]
model = "grok-4.5"
model = "kigi-4.5"
context_window = 500000
base_url = "https://inference.example.com/v1"
api_backend = "responses"
@@ -8572,25 +8581,25 @@ default = "grok-4.5"
let cfg = Config::new_from_toml_cfg(&raw).expect("config should parse");
let mut prefetched = IndexMap::new();
let mut entry = test_model_entry(
"grok-4.5",
"kigi-4.5",
"https://inference.example.com/v1",
None,
None,
None,
);
entry.info.context_window = NonZeroU64::new(default_cw).unwrap();
prefetched.insert("grok-4.5".to_owned(), entry);
prefetched.insert("kigi-4.5".to_owned(), entry);
let resolved = resolve_model_list(&cfg, Some(prefetched));
let by_key = resolved
.get("kigi-build")
.expect("kigi-build key must exist");
assert_eq!(by_key.info.context_window.get(), 500_000);
assert_eq!(by_key.info.model, "grok-4.5");
let by_latest = resolved.get("grok-4.5").expect("grok-4.5 key must exist");
assert_eq!(by_key.info.model, "kigi-4.5");
let by_latest = resolved.get("kigi-4.5").expect("kigi-4.5 key must exist");
assert_eq!(
by_latest.info.context_window.get(),
500_000,
"BUG: prefetched 'grok-4.5' should inherit 500k from \
"BUG: prefetched 'kigi-4.5' should inherit 500k from \
sibling 'kigi-build' (same model slug), not stay at {default_cw}"
);
}
@@ -8601,24 +8610,24 @@ default = "grok-4.5"
let raw: toml::Value = toml::from_str(
r#"
[model.kigi-build]
model = "grok-4.5"
model = "kigi-4.5"
context_window = 500000
base_url = "https://test.example.com/v1"
api_backend = "responses"
agent_type = "grok-build"
agent_type = "kigi"
"#,
)
.unwrap();
let cfg = Config::new_from_toml_cfg(&raw).expect("config should parse");
let mut prefetched = IndexMap::new();
let mut entry =
test_model_entry("grok-4.5", "https://test.example.com/v1", None, None, None);
test_model_entry("kigi-4.5", "https://test.example.com/v1", None, None, None);
entry.info.context_window = NonZeroU64::new(default_cw).unwrap();
entry.info.agent_type = default_agent_type();
entry.info.api_backend = ApiBackend::default();
prefetched.insert("grok-4.5".to_owned(), entry);
prefetched.insert("kigi-4.5".to_owned(), entry);
let resolved = resolve_model_list(&cfg, Some(prefetched));
let latest = resolved.get("grok-4.5").unwrap();
let latest = resolved.get("kigi-4.5").unwrap();
assert_eq!(
latest.info.agent_type,
default_agent_type(),
@@ -8637,7 +8646,7 @@ default = "grok-4.5"
let raw: toml::Value = toml::from_str(
r#"
[model.kigi-build]
model = "grok-4.5"
model = "kigi-4.5"
context_window = 500000
base_url = "https://test.example.com/v1"
"#,
@@ -8646,11 +8655,11 @@ default = "grok-4.5"
let cfg = Config::new_from_toml_cfg(&raw).expect("config should parse");
let mut prefetched = IndexMap::new();
let mut entry =
test_model_entry("grok-4.5", "https://test.example.com/v1", None, None, None);
test_model_entry("kigi-4.5", "https://test.example.com/v1", None, None, None);
entry.info.context_window = NonZeroU64::new(65_536).unwrap();
prefetched.insert("grok-4.5".to_owned(), entry);
prefetched.insert("kigi-4.5".to_owned(), entry);
let resolved = resolve_model_list(&cfg, Some(prefetched));
let latest = resolved.get("grok-4.5").unwrap();
let latest = resolved.get("kigi-4.5").unwrap();
assert_eq!(
latest.info.context_window.get(),
65_536,
@@ -9017,13 +9026,13 @@ default = "grok-4.5"
fn resolve_model_list_inherits_agent_type_and_api_backend() {
let cfg = Config::default();
let default_cw = DEFAULT_CONTEXT_WINDOW;
let entry = prefetch_model_entry("grok-build", default_cw, ApiBackend::default());
let entry = prefetch_model_entry("kigi", default_cw, ApiBackend::default());
let mut prefetched = IndexMap::new();
prefetched.insert("grok-build".to_owned(), entry);
prefetched.insert("kigi".to_owned(), entry);
let resolved = resolve_model_list(&cfg, Some(prefetched));
let entry = resolved.get("grok-build").expect("model must exist");
let entry = resolved.get("kigi").expect("model must exist");
let defaults = default_model_entries(&EndpointsConfig::default());
if let Some(default) = defaults.get("grok-build") {
if let Some(default) = defaults.get("kigi") {
if default.info.agent_type != DEFAULT_AGENT_TYPE {
assert_eq!(
entry.info.agent_type, default.info.agent_type,
@@ -10,7 +10,7 @@
//! parsed again. Non-table values are dropped with a warning.
//!
//! Warnings are retained on `Config::model_override_warnings` and surfaced by
//! `grok inspect`.
//! `kigi inspect`.
use indexmap::IndexMap;
use serde::Serialize;
@@ -125,7 +125,7 @@ pub(crate) fn log_model_override_warnings(warnings: &[ModelOverrideWarning]) {
if !warnings.is_empty() {
tracing::warn!(
warnings = warnings.len(),
"model_override: parsed with warnings; run `grok inspect` for details"
"model_override: parsed with warnings; run `kigi inspect` for details"
);
}
}
@@ -292,8 +292,8 @@ mod tests {
fn duplicate_compactions_keys_keeps_model() {
let cfg = parse_cfg(
r#"
[model."grok-4.5"]
model = "grok-4.5"
[model."kigi-4.5"]
model = "kigi-4.5"
env_key = "ANTHROPIC_AUTH_TOKEN"
compactions_remaining = 1
send_compactions_remaining = true
@@ -301,8 +301,8 @@ mod tests {
);
let model = cfg
.config_models
.get("grok-4.5")
.expect("grok-4.5 must remain in catalog");
.get("kigi-4.5")
.expect("kigi-4.5 must remain in catalog");
assert_eq!(
model.compactions_remaining,
Some(CompactionsRemaining::Fixed(1))
@@ -312,19 +312,19 @@ mod tests {
&& w.field.as_deref() == Some("send_compactions_remaining")
}));
let resolved = crate::agent::config::resolve_model_list(&cfg, None);
assert!(resolved.contains_key("grok-4.5"));
assert!(resolved.contains_key("kigi-4.5"));
}
#[test]
fn legacy_alias_alone_parses_without_warning() {
let cfg = parse_cfg(
r#"
[model."grok-4.5"]
model = "grok-4.5"
[model."kigi-4.5"]
model = "kigi-4.5"
send_compactions_remaining = 2
"#,
);
let model = cfg.config_models.get("grok-4.5").unwrap();
let model = cfg.config_models.get("kigi-4.5").unwrap();
assert_eq!(
model.compactions_remaining,
Some(CompactionsRemaining::Fixed(2))
@@ -336,17 +336,17 @@ mod tests {
fn invalid_reasoning_effort_skips_field_keeps_model() {
let cfg = parse_cfg(
r#"
[model."grok-4.5"]
model = "grok-4.5"
[model."kigi-4.5"]
model = "kigi-4.5"
env_key = "ANTHROPIC_AUTH_TOKEN"
reasoning_effort = "not-a-level"
"#,
);
let model = cfg
.config_models
.get("grok-4.5")
.expect("grok-4.5 must remain in catalog");
assert_eq!(model.model.as_deref(), Some("grok-4.5"));
.get("kigi-4.5")
.expect("kigi-4.5 must remain in catalog");
assert_eq!(model.model.as_deref(), Some("kigi-4.5"));
assert!(model.reasoning_effort.is_none());
assert!(cfg.model_override_warnings.iter().any(|w| {
w.kind == ModelOverrideWarningKind::InvalidValue
@@ -358,14 +358,14 @@ mod tests {
fn unknown_field_warns_but_keeps_known_fields() {
let (models, warnings) = parse_raw(
r#"
[model."grok-4.5"]
model = "grok-4.5"
[model."kigi-4.5"]
model = "kigi-4.5"
env_key = "TOKEN"
future_field = 1
"#,
);
let entry = models.get("grok-4.5").unwrap();
assert_eq!(entry.model.as_deref(), Some("grok-4.5"));
let entry = models.get("kigi-4.5").unwrap();
assert_eq!(entry.model.as_deref(), Some("kigi-4.5"));
assert_eq!(
entry.env_key.as_ref().and_then(|k| k.primary()),
Some("TOKEN")
@@ -373,7 +373,7 @@ mod tests {
assert_eq!(
warnings,
vec![ModelOverrideWarning {
model_key: Some("grok-4.5".to_owned()),
model_key: Some("kigi-4.5".to_owned()),
field: Some("future_field".to_owned()),
kind: ModelOverrideWarningKind::UnknownField,
reason: "unknown field".to_owned(),
@@ -472,7 +472,7 @@ mod tests {
#[test]
fn non_table_model_section_warns_and_is_ignored() {
let (models, warnings) = parse_raw(r#"model = "grok-4""#);
let (models, warnings) = parse_raw(r#"model = "kigi-4""#);
assert!(models.is_empty());
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].kind, ModelOverrideWarningKind::NotATable);
@@ -5,7 +5,7 @@
use crate::session::SessionCommand;
/// Parse a `x.ai/queue/{remove,reorder,clear,edit,interject}` ext-notification's
/// Parse a `kigi/queue/{remove,reorder,clear,edit,interject}` ext-notification's
/// params into the corresponding [`SessionCommand`].
/// `owner` is the resolved attribution (params `owner`/`clientIdentifier`) used
/// to scope remove/clear to the requesting client's own items, and recorded as
@@ -17,7 +17,7 @@ pub(super) fn parse_queue_edit_command(
owner: Option<String>,
) -> Option<SessionCommand> {
match method {
"x.ai/queue/remove" => {
"kigi/queue/remove" => {
let id = params.get("id").and_then(|v| v.as_str())?.to_string();
// The client supplies the version it last saw; the handler removes
// only on an exact match (stale = benign no-op + rebroadcast).
@@ -32,7 +32,7 @@ pub(super) fn parse_queue_edit_command(
owner,
})
}
"x.ai/queue/reorder" => {
"kigi/queue/reorder" => {
let ordered_ids = params
.get("orderedIds")
.and_then(|v| v.as_array())
@@ -44,8 +44,8 @@ pub(super) fn parse_queue_edit_command(
.unwrap_or_default();
Some(SessionCommand::ReorderQueue { ordered_ids })
}
"x.ai/queue/clear" => Some(SessionCommand::ClearQueue { owner }),
"x.ai/queue/interject" => {
"kigi/queue/clear" => Some(SessionCommand::ClearQueue { owner }),
"kigi/queue/interject" => {
let id = params.get("id").and_then(|v| v.as_str())?.to_string();
// The client supplies the version it last saw; the handler acts
// only on an exact match (stale = benign no-op + rebroadcast).
@@ -68,7 +68,7 @@ pub(super) fn parse_queue_edit_command(
new_text,
})
}
"x.ai/queue/edit" => {
"kigi/queue/edit" => {
let id = params.get("id").and_then(|v| v.as_str())?.to_string();
let new_text = params.get("newText").and_then(|v| v.as_str())?.to_string();
// `owner` is the resolved attribution; for edit it represents the
@@ -88,7 +88,7 @@ pub(super) fn parse_queue_edit_command(
mod tests {
use super::*;
/// Each `x.ai/queue/*` ext-notification maps to the
/// Each `kigi/queue/*` ext-notification maps to the
/// correct versioned/idempotent `SessionCommand`.
#[test]
fn parse_queue_edit_command_maps_each_method() {
@@ -96,7 +96,7 @@ mod tests {
let p = serde_json::json!({
"sessionId": "s1", "id": "p7", "expectedVersion": 3
});
match parse_queue_edit_command("x.ai/queue/remove", &p, Some("grok-tui".into())) {
match parse_queue_edit_command("kigi/queue/remove", &p, Some("kigi-tui".into())) {
Some(SessionCommand::RemoveQueuedPrompt {
id,
expected_version,
@@ -104,14 +104,14 @@ mod tests {
}) => {
assert_eq!(id, "p7");
assert_eq!(expected_version, 3);
assert_eq!(owner.as_deref(), Some("grok-tui"));
assert_eq!(owner.as_deref(), Some("kigi-tui"));
}
_ => panic!("expected RemoveQueuedPrompt"),
}
// remove without expectedVersion defaults to 0.
let p = serde_json::json!({ "sessionId": "s1", "id": "p8" });
match parse_queue_edit_command("x.ai/queue/remove", &p, None) {
match parse_queue_edit_command("kigi/queue/remove", &p, None) {
Some(SessionCommand::RemoveQueuedPrompt {
expected_version, ..
}) => assert_eq!(expected_version, 0),
@@ -120,7 +120,7 @@ mod tests {
// reorder: orderedIds array.
let p = serde_json::json!({ "sessionId": "s1", "orderedIds": ["a", "b", "c"] });
match parse_queue_edit_command("x.ai/queue/reorder", &p, None) {
match parse_queue_edit_command("kigi/queue/reorder", &p, None) {
Some(SessionCommand::ReorderQueue { ordered_ids }) => {
assert_eq!(ordered_ids, vec!["a", "b", "c"]);
}
@@ -129,12 +129,12 @@ mod tests {
// clear: owner-scoped.
match parse_queue_edit_command(
"x.ai/queue/clear",
"kigi/queue/clear",
&serde_json::json!({ "sessionId": "s1" }),
Some("grok-tui".into()),
Some("kigi-tui".into()),
) {
Some(SessionCommand::ClearQueue { owner }) => {
assert_eq!(owner.as_deref(), Some("grok-tui"));
assert_eq!(owner.as_deref(), Some("kigi-tui"));
}
_ => panic!("expected ClearQueue"),
}
@@ -143,7 +143,7 @@ mod tests {
let p = serde_json::json!({
"sessionId": "s1", "id": "p9", "newText": "replacement text"
});
match parse_queue_edit_command("x.ai/queue/edit", &p, Some("grok-vscode".into())) {
match parse_queue_edit_command("kigi/queue/edit", &p, Some("kigi-vscode".into())) {
Some(SessionCommand::EditQueuedPrompt {
id,
new_text,
@@ -151,14 +151,14 @@ mod tests {
}) => {
assert_eq!(id, "p9");
assert_eq!(new_text, "replacement text");
assert_eq!(editor.as_deref(), Some("grok-vscode"));
assert_eq!(editor.as_deref(), Some("kigi-vscode"));
}
_ => panic!("expected EditQueuedPrompt"),
}
// edit without editor (no owner/clientIdentifier) → editor: None.
match parse_queue_edit_command(
"x.ai/queue/edit",
"kigi/queue/edit",
&serde_json::json!({ "sessionId": "s1", "id": "p9", "newText": "x" }),
None,
) {
@@ -171,7 +171,7 @@ mod tests {
// edit without newText → None (can't replace text we don't have).
assert!(
parse_queue_edit_command(
"x.ai/queue/edit",
"kigi/queue/edit",
&serde_json::json!({ "sessionId": "s1", "id": "p9" }),
None,
)
@@ -181,7 +181,7 @@ mod tests {
// edit without id → None (can't target an entry).
assert!(
parse_queue_edit_command(
"x.ai/queue/edit",
"kigi/queue/edit",
&serde_json::json!({ "sessionId": "s1", "newText": "x" }),
None,
)
@@ -192,7 +192,7 @@ mod tests {
let p = serde_json::json!({
"sessionId": "s1", "id": "p10", "expectedVersion": 2
});
match parse_queue_edit_command("x.ai/queue/interject", &p, Some("grok-tui".into())) {
match parse_queue_edit_command("kigi/queue/interject", &p, Some("kigi-tui".into())) {
Some(SessionCommand::InterjectQueuedPrompt {
id,
expected_version,
@@ -201,7 +201,7 @@ mod tests {
}) => {
assert_eq!(id, "p10");
assert_eq!(expected_version, 2);
assert_eq!(owner.as_deref(), Some("grok-tui"));
assert_eq!(owner.as_deref(), Some("kigi-tui"));
assert_eq!(new_text, None, "newText absent → None");
}
_ => panic!("expected InterjectQueuedPrompt"),
@@ -211,7 +211,7 @@ mod tests {
let p = serde_json::json!({
"sessionId": "s1", "id": "p10", "expectedVersion": 2, "newText": "edited"
});
match parse_queue_edit_command("x.ai/queue/interject", &p, None) {
match parse_queue_edit_command("kigi/queue/interject", &p, None) {
Some(SessionCommand::InterjectQueuedPrompt { new_text, .. }) => {
assert_eq!(new_text.as_deref(), Some("edited"));
}
@@ -222,7 +222,7 @@ mod tests {
let p = serde_json::json!({
"sessionId": "s1", "id": "p10", "expectedVersion": 2, "newText": " "
});
match parse_queue_edit_command("x.ai/queue/interject", &p, None) {
match parse_queue_edit_command("kigi/queue/interject", &p, None) {
Some(SessionCommand::InterjectQueuedPrompt { new_text, .. }) => {
assert_eq!(new_text, None, "blank override must be dropped");
}
@@ -231,7 +231,7 @@ mod tests {
// interject without expectedVersion defaults to 0.
match parse_queue_edit_command(
"x.ai/queue/interject",
"kigi/queue/interject",
&serde_json::json!({ "sessionId": "s1", "id": "p11" }),
None,
) {
@@ -243,17 +243,17 @@ mod tests {
// interject without id → None (can't target an entry).
assert!(
parse_queue_edit_command("x.ai/queue/interject", &serde_json::json!({}), None)
parse_queue_edit_command("kigi/queue/interject", &serde_json::json!({}), None)
.is_none()
);
// unknown method → None.
assert!(
parse_queue_edit_command("x.ai/queue/bogus", &serde_json::json!({}), None).is_none()
parse_queue_edit_command("kigi/queue/bogus", &serde_json::json!({}), None).is_none()
);
// remove without id → None (can't target an entry).
assert!(
parse_queue_edit_command("x.ai/queue/remove", &serde_json::json!({}), None).is_none()
parse_queue_edit_command("kigi/queue/remove", &serde_json::json!({}), None).is_none()
);
}
}
@@ -148,7 +148,7 @@ pub fn project_scope_allowed(cwd: &Path) -> bool {
/// is on, the workspace is NOT store-trusted, and repo-local code-exec configs
/// are present (something to gate). Interactivity is forced `true` because the
/// caller already confirmed the client can prompt (it advertised
/// `x.ai/folderTrust.interactive`); the TTY-based [`decide_inputs`] default is
/// `kigi/folderTrust.interactive`); the TTY-based [`decide_inputs`] default is
/// false under the ACP stdio transport. Mirrors the [`decide`] precedence so it
/// cannot drift from the gate: feature-off (kill-switch / opt-out) / store-trusted
/// / no-configs all collapse to a non-`Prompt` verdict and return false.
@@ -226,7 +226,7 @@ pub(crate) fn record_for_test(cwd: &Path, allowed: bool) {
/// `allow_prompt` must be `true` ONLY where a blocking stdin y/N read is safe —
/// i.e. agent `initialize` for the launch directory, before the TUI takes over
/// the terminal. Every other call site (per-session cwd, leader-served sessions
/// whose cwd differs from the launch dir, `grok mcp doctor`) passes `false`, so
/// whose cwd differs from the launch dir, `kigi mcp doctor`) passes `false`, so
/// an unresolved interactive-but-untrusted workspace resolves **fail-closed**
/// (untrusted, no prompt) — only the launch dir is ever prompted for.
pub fn resolve_and_record(cwd: &Path, remote: Option<&RemoteSettings>, allow_prompt: bool) -> bool {
@@ -290,7 +290,7 @@ pub fn resolve_launch_dir_trust(cwd: &Path, remote: Option<&RemoteSettings>) ->
/// - A cached **grant** (`Some(true)`) is durable and short-circuits — neither
/// `store_trusted` nor `recompute` runs.
/// - A cached **untrusted** verdict (`Some(false)`) is re-checked via
/// `store_trusted`: a `grok --trust` grant issued AFTER this workspace was
/// `store_trusted`: a `kigi --trust` grant issued AFTER this workspace was
/// first resolved writes the store, so honor it on the next session without a
/// restart. Without this re-read a long-lived leader would mask the grant.
/// - An **unrecorded** key (`None`) does a full `recompute`, which reports
@@ -389,7 +389,7 @@ fn compute_from_inputs(
/// merged server list when the workspace is untrusted.
///
/// SINGLE SOURCE OF TRUTH for "project-scoped MCP names" across ALL gate sites
/// (session merge, the session-less agent pool, `grok mcp doctor`). It MUST
/// (session merge, the session-less agent pool, `kigi mcp doctor`). It MUST
/// enumerate every project MCP source the loaders read; adding a new repo-local
/// MCP source without extending this fn silently re-opens the gate (guarded by
/// `project_scoped_mcp_names_cover_every_source`).
@@ -1190,9 +1190,9 @@ mod tests {
// can distinguish it from user/plugin servers. Asserts on the specific
// key, so any real `~/.kigi/lsp.json` on the test host is irrelevant.
let tmp = repo_tmp();
let grok = tmp.path().join(".kigi");
std::fs::create_dir_all(&grok).unwrap();
std::fs::write(grok.join("lsp.json"), r#"{"projlsp": {"command": "true"}}"#).unwrap();
let kigi = tmp.path().join(".kigi");
std::fs::create_dir_all(&kigi).unwrap();
std::fs::write(kigi.join("lsp.json"), r#"{"projlsp": {"command": "true"}}"#).unwrap();
let sourced = load_servers_with_plugins_sourced(tmp.path(), &[], &[], &[], &[]);
let (_, source) = sourced.get("projlsp").expect("project server present");
@@ -1209,9 +1209,9 @@ mod tests {
// End-to-end of the load-site gate (Sites A/B): a project server loaded
// from `<cwd>/.kigi/lsp.json` is dropped once the workspace is untrusted.
let tmp = repo_tmp();
let grok = tmp.path().join(".kigi");
std::fs::create_dir_all(&grok).unwrap();
std::fs::write(grok.join("lsp.json"), r#"{"projlsp": {"command": "true"}}"#).unwrap();
let kigi = tmp.path().join(".kigi");
std::fs::create_dir_all(&kigi).unwrap();
std::fs::write(kigi.join("lsp.json"), r#"{"projlsp": {"command": "true"}}"#).unwrap();
let sourced = load_servers_with_plugins_sourced(tmp.path(), &[], &[], &[], &[]);
assert!(
@@ -1243,10 +1243,10 @@ mod tests {
r#"{"mcpServers": {"projjson": {"url": "https://proj.example.com/mcp"}}}"#,
)
.unwrap();
let grok = tmp.path().join(".kigi");
std::fs::create_dir_all(&grok).unwrap();
let kigi = tmp.path().join(".kigi");
std::fs::create_dir_all(&kigi).unwrap();
std::fs::write(
grok.join("config.toml"),
kigi.join("config.toml"),
"[mcp_servers.projtoml]\nurl = \"https://projtoml.example.com/mcp\"\n",
)
.unwrap();
@@ -1264,10 +1264,10 @@ mod tests {
#[test]
fn project_scoped_mcp_names_cover_every_source() {
let tmp = repo_tmp();
let grok = tmp.path().join(".kigi");
std::fs::create_dir_all(&grok).unwrap();
let kigi = tmp.path().join(".kigi");
std::fs::create_dir_all(&kigi).unwrap();
std::fs::write(
grok.join("config.toml"),
kigi.join("config.toml"),
"[mcp_servers.cfgsrv]\nurl = \"https://cfg.example.com/mcp\"\n",
)
.unwrap();
@@ -1339,7 +1339,7 @@ mod tests {
let key = workspace_key(tmp.path());
record(&key, false);
assert!(!project_scope_allowed(tmp.path()));
// Simulate a `grok --trust` grant landing in the store after the
// Simulate a `kigi --trust` grant landing in the store after the
// untrusted verdict was cached: the re-read sees trusted, so the next
// resolve upgrades the cache without a process restart.
let allowed = resolve_and_record_inner(
@@ -226,7 +226,7 @@ fn broadcast_model_changed(
agent
.gateway
.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/session_notification",
"kigi/session_notification",
params.into(),
));
}
@@ -28,27 +28,27 @@ fn backfill_session_summary(summary: &mut Summary) {
}
}
/// Router for x.ai/session/* and x.ai/session_summaries/* methods.
/// Router for kigi/session/* and kigi/session_summaries/* methods.
pub async fn handle(
agent: &MvpAgent,
args: &acp::ExtRequest,
) -> Result<acp::ExtResponse, acp::Error> {
match args.method.as_ref() {
"x.ai/session/info" => handle_session_info(agent, args).await,
"x.ai/session/close" => handle_session_close(agent, args).await,
"x.ai/session/list" => handle_session_list(agent, args).await,
"x.ai/sessions/list" => handle_roster_list(agent, args).await,
m if m.starts_with("x.ai/session_summaries/") => {
"kigi/session/info" => handle_session_info(agent, args).await,
"kigi/session/close" => handle_session_close(agent, args).await,
"kigi/session/list" => handle_session_list(agent, args).await,
"kigi/sessions/list" => handle_roster_list(agent, args).await,
m if m.starts_with("kigi/session_summaries/") => {
handle_session_summaries(agent, args).await
}
_ => Err(acp::Error::method_not_found()),
}
}
/// `x.ai/sessions/list` — the FleetView roster. Returns every
/// `kigi/sessions/list` — the FleetView roster. Returns every
/// resident session plus recently-touched on-disk `Dormant` sessions. Clients
/// poll this while the dashboard is open and reconcile against the
/// `x.ai/sessions/changed` broadcast.
/// `kigi/sessions/changed` broadcast.
async fn handle_roster_list(
agent: &MvpAgent,
_args: &acp::ExtRequest,
@@ -166,7 +166,7 @@ async fn handle_session_close(
// (see `MvpAgent::handle_evict_sessions` / `close_session_explicit`).
agent.request_session_shutdown(&sid);
agent.close_session_explicit(&sid);
tracing::info!(session_id = %req.session_id, "session closed via x.ai/session/close");
tracing::info!(session_id = %req.session_id, "session closed via kigi/session/close");
} else {
tracing::debug!(session_id = %req.session_id, "session/close: session not found (already closed)");
}
@@ -181,7 +181,7 @@ async fn handle_session_summaries(
args: &acp::ExtRequest,
) -> Result<acp::ExtResponse, acp::Error> {
match args.method.as_ref() {
"x.ai/session_summaries/session_list" => {
"kigi/session_summaries/session_list" => {
let req = serde_json::from_str::<SessionListRequest>(args.params.get())?;
let cwd = req.workspace_directory.to_string_lossy().to_string();
@@ -203,7 +203,7 @@ async fn handle_session_summaries(
Ok(acp::ExtResponse::new(value))
}
"x.ai/session_summaries/workspace_list" => {
"kigi/session_summaries/workspace_list" => {
tracing::debug!("xai/session_summaries/workspace_list is working");
let _req = serde_json::from_str::<AllSessionOverviewRequest>(args.params.get())?;
@@ -215,7 +215,7 @@ async fn handle_session_summaries(
summaries_to_overview_response(summaries)
}
"x.ai/session_summaries/workspace_list_recent" => {
"kigi/session_summaries/workspace_list_recent" => {
let req = serde_json::from_str::<RecentSessionsRequest>(args.params.get())?;
let _timer = crate::instrumentation_timer!("session.list_sessions_recent");
+2 -2
View File
@@ -76,7 +76,7 @@ fn resolve_config(cfg: &AgentConfig, auth_manager: &AuthManager) -> AgentConfig
crate::util::config::sync_campaign_fields(&mut cfg);
crate::agent::config::apply_remote_settings_side_effects(cfg.remote_settings.as_ref());
// env var > remote settings > Local. Skip remote settings for Generic (grok -p, subagents).
// env var > remote settings > Local. Skip remote settings for Generic (kigi -p, subagents).
if cfg.storage_mode == StorageMode::Local
&& cfg.mode != crate::agent::config::AgentMode::Generic
{
@@ -86,7 +86,7 @@ fn resolve_config(cfg: &AgentConfig, auth_manager: &AuthManager) -> AgentConfig
if cfg.storage_mode == StorageMode::Writeback
&& !auth_manager.current().is_some_and(|a| a.is_session_auth())
{
tracing::info!("Writeback is disabled: requires auth with grok.com");
tracing::info!("Writeback is disabled: requires auth with kimi.com");
cfg.storage_mode = StorageMode::Local;
}
+141 -163
View File
@@ -405,7 +405,7 @@ impl ModelsManager {
self.reselect_current_model_if_missing(&new_config);
}
// Push the new catalog to connected clients (`x.ai/models/update`).
// Push the new catalog to connected clients (`kigi/models/update`).
// Without this, a long-running agent (leader mode) correctly swaps
// its in-memory catalog on a config.toml `[model.*]`/`[models]` edit,
// but already-connected clients keep rendering the stale model list
@@ -592,10 +592,10 @@ impl ModelsManager {
/// Catalog opt-in to display the served-checkpoint fingerprint for this model.
///
/// `model_id` may be a routing slug (`config.model`, e.g. `grok-4.5`)
/// `model_id` may be a routing slug (`config.model`, e.g. `kigi-4.5`)
/// OR a catalog key; the catalog map is keyed by the config key, which can
/// differ from the slug for custom/enterprise ids (e.g. key `enterprise-grok-build`
/// → slug `grok-4.5`). Resolve to the catalog key first so a slug
/// differ from the slug for custom/enterprise ids (e.g. key `enterprise-kigi`
/// → slug `kigi-4.5`). Resolve to the catalog key first so a slug
/// caller still finds the opted-in entry.
pub fn model_show_model_fingerprint(&self, model_id: &str) -> bool {
let models = self.inner.models.read();
@@ -737,7 +737,7 @@ impl ModelsManager {
acp::SessionModelState::new(current, available.values().cloned().collect());
if let Ok(params) = serde_json::value::to_raw_value(&model_state) {
gw.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/models/update",
"kigi/models/update",
params.into(),
));
}
@@ -749,8 +749,8 @@ impl ModelsManager {
///
/// A long-running leader otherwise only refreshes its catalog from its
/// *own* fetch paths (startup prefetch, auth change, response-header etag).
/// When another grok process sharing `~/.kigi` (a `--no-leader` run, a
/// newer client, grok-desktop) fetches a fresher `/v1/models` catalog and
/// When another kigi process sharing `~/.kigi` (a `--no-leader` run, a
/// newer client, kigi-desktop) fetches a fresher `/v1/models` catalog and
/// persists it, this picks it up without a network round-trip.
///
/// Guards, in order:
@@ -934,7 +934,7 @@ impl ModelsManager {
/// delivering events on macOS after resume from sleep. On each
/// notification the catalog is re-fetched from the server; if the
/// fetch succeeds and the catalog changed, clients are notified
/// via `x.ai/models/update`.
/// via `kigi/models/update`.
pub fn start_auth_refresh_watcher(&self, notify: Arc<tokio::sync::Notify>) {
let mgr = self.clone();
let had_catalog_at_start = *self.inner.has_fetched_real_catalog.read();
@@ -1687,7 +1687,7 @@ struct PrefetchEnv {
fn resolve_prefetch_env_with_auth(auth: Option<KimiAuth>) -> Option<PrefetchEnv> {
let _timer = crate::instrumentation_timer!("startup.early_prefetch_launch");
// Config-aware (not env-only) so the prefetch can't leak the bearer to api.x.ai.
// Config-aware (not env-only) so the prefetch can't leak the bearer to the BYOK endpoint.
let mut endpoints = config::EndpointsConfig::from_effective_config();
if endpoints.deployment_key.is_none() {
@@ -1796,8 +1796,8 @@ fn spawn_prefetch_thread(env: PrefetchEnv) -> EarlyPrefetchHandle {
/// Map a model id (catalog key or routing slug) to its catalog key.
///
/// Sessions persist the routing slug (`[model.X].model`, e.g. `grok-4.5`);
/// the catalog and `/model` picker use config keys (e.g. `enterprise-grok-build`).
/// Sessions persist the routing slug (`[model.X].model`, e.g. `kigi-4.5`);
/// the catalog and `/model` picker use config keys (e.g. `enterprise-kigi`).
/// Last slug match wins so user overrides beat defaults (matches `MvpAgent::resolve_model_id`).
pub(crate) fn resolve_catalog_key(
models: &IndexMap<String, ModelEntry>,
@@ -2070,7 +2070,7 @@ pub fn resolve_model_catalog(
// Skip non-reasoning models so we don't send the field to providers that reject it.
// Also skip models whose effort menu does not include the override (e.g. `--effort none`
// must not stamp `none` onto grok-4.5, which only offers low/medium/high).
// must not stamp `none` onto kigi-4.5, which only offers low/medium/high).
if let Some(effort) = cfg.reasoning_effort_override {
for entry in catalog.values_mut() {
if model_offers_reasoning_effort(&entry.info, effort) {
@@ -2188,7 +2188,7 @@ mod tests {
.try_init();
// Use a temp dir so AuthManager finds no credentials — ensures
// refresh_async bails at the auth check without needing a tokio runtime.
let tmp = std::env::temp_dir().join("grok-test-models-manager");
let tmp = std::env::temp_dir().join("kigi-test-models-manager");
let auth_manager = Arc::new(AuthManager::new(&tmp, KimiCodeConfig::default()));
ModelsManager::new(
None,
@@ -2263,11 +2263,11 @@ mod tests {
allowed_models = ["keep-*"]
[model.zzz-first]
model = "zzz-first"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 256000
[model.keep-one]
model = "keep-one"
base_url = "https://api.x.ai/v1"
base_url = "https://byok.example/v1"
context_window = 256000
"#,
);
@@ -2286,15 +2286,15 @@ mod tests {
let excluded = config_from_toml(
r#"
[models]
default = "grok-3"
allowed_models = ["grok-4*"]
default = "kigi-3"
allowed_models = ["kigi-4*"]
[model.kigi-3]
model = "grok-3"
base_url = "https://api.x.ai/v1"
model = "kigi-3"
base_url = "https://byok.example/v1"
context_window = 256000
[model.kigi-4]
model = "grok-4"
base_url = "https://api.x.ai/v1"
model = "kigi-4"
base_url = "https://byok.example/v1"
context_window = 256000
"#,
);
@@ -2302,7 +2302,7 @@ mod tests {
assert!(
validate_selectable(&excluded, &catalog)
.unwrap_err()
.contains("grok-3")
.contains("kigi-3")
);
// Matches nothing → error.
@@ -2311,8 +2311,8 @@ mod tests {
[models]
allowed_models = ["nomatch-*"]
[model.kigi-4]
model = "grok-4"
base_url = "https://api.x.ai/v1"
model = "kigi-4"
base_url = "https://byok.example/v1"
context_window = 256000
"#,
);
@@ -2362,7 +2362,7 @@ mod tests {
);
// Real switch: both subscribers see the change.
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
tokio::time::timeout(std::time::Duration::from_millis(100), rx_a.changed())
.await
.expect("rx_a saw the switch")
@@ -2380,13 +2380,13 @@ mod tests {
async fn model_switch_generation_snapshot_reflects_current_state() {
let mgr = test_manager();
let start = mgr.model_switch_generation();
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
assert_eq!(mgr.model_switch_generation(), start + 1);
// Idempotent: same id → no bump.
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
assert_eq!(mgr.model_switch_generation(), start + 1);
// Another real change: another bump.
mgr.set_current_model_id(acp::ModelId::new("grok-3"));
mgr.set_current_model_id(acp::ModelId::new("kigi-3"));
assert_eq!(mgr.model_switch_generation(), start + 2);
}
@@ -2430,7 +2430,7 @@ mod tests {
#[test]
fn current_reasoning_effort_seeded_from_config() {
let tmp = std::env::temp_dir().join("grok-test-models-manager-seed");
let tmp = std::env::temp_dir().join("kigi-test-models-manager-seed");
let auth_manager = Arc::new(AuthManager::new(&tmp, KimiCodeConfig::default()));
let mut cfg = config::Config::default();
cfg.models.default_reasoning_effort = Some(ReasoningEffort::Xhigh);
@@ -2504,7 +2504,7 @@ mod tests {
let mut prefetched = IndexMap::new();
// 4.5-style: supports effort, menu is high only (no none).
let mut no_none = ModelEntry {
info: config::ModelInfo::fallback("grok-4.5"),
info: config::ModelInfo::fallback("kigi-4.5"),
api_key: None,
env_key: None,
api_base_url: None,
@@ -2518,7 +2518,7 @@ mod tests {
default: true,
}];
no_none.info.reasoning_effort = Some(ReasoningEffort::High);
prefetched.insert("grok-4.5".to_string(), no_none);
prefetched.insert("kigi-4.5".to_string(), no_none);
// Model that explicitly offers none.
let mut with_none = ModelEntry {
@@ -2539,7 +2539,7 @@ mod tests {
let catalog = resolve_model_catalog(&cfg, Some(prefetched));
assert_eq!(
catalog["grok-4.5"].info.reasoning_effort,
catalog["kigi-4.5"].info.reasoning_effort,
Some(ReasoningEffort::High),
"--effort none must not stamp onto models that do not offer none"
);
@@ -2598,7 +2598,7 @@ mod tests {
assert_eq!(catalog["plain"].info.reasoning_effort, None);
// The internal getters read those derived fields.
let tmp = std::env::temp_dir().join("grok-test-models-manager-menu-only");
let tmp = std::env::temp_dir().join("kigi-test-models-manager-menu-only");
let auth_manager = Arc::new(AuthManager::new(&tmp, KimiCodeConfig::default()));
let mgr = ModelsManager::new(
None,
@@ -2698,37 +2698,37 @@ mod tests {
fn first_apply_refresh_reselects_default_model() {
let mgr = test_manager();
let mut cfg = config::Config::default();
cfg.models.default = Some("grok-3".to_string());
cfg.models.default = Some("kigi-3".to_string());
assert!(!mgr.has_fetched_real_catalog());
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
assert!(mgr.has_fetched_real_catalog());
assert_eq!(mgr.current_model_id().0.as_ref(), "grok-3");
assert_eq!(mgr.current_model_id().0.as_ref(), "kigi-3");
}
#[test]
fn subsequent_apply_refresh_preserves_user_model() {
let mgr = test_manager();
let mut cfg = config::Config::default();
cfg.models.default = Some("grok-3".to_string());
cfg.models.default = Some("kigi-3".to_string());
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
// Simulate on_auth_changed clearing prefetched + etag.
*mgr.inner.prefetched.write() = None;
*mgr.inner.etag.write() = None;
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
assert_eq!(
mgr.current_model_id().0.as_ref(),
"grok-4",
"kigi-4",
"user's model selection must survive auth-change refresh"
);
}
@@ -2737,19 +2737,19 @@ mod tests {
fn subsequent_refresh_reselects_when_model_removed() {
let mgr = test_manager();
let mut cfg = config::Config::default();
cfg.models.default = Some("grok-3".to_string());
cfg.models.default = Some("kigi-3".to_string());
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
// Second refresh with grok-4 removed.
let prefetched = make_prefetched(&["grok-3", "grok-4.5"]);
// Second refresh with kigi-4 removed.
let prefetched = make_prefetched(&["kigi-3", "kigi-4.5"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
assert_eq!(
mgr.current_model_id().0.as_ref(),
"grok-3",
"kigi-3",
"should fall back to config default when current is removed"
);
}
@@ -2773,11 +2773,11 @@ mod tests {
fn apply_config_honors_new_preferred_model() {
let mgr = test_manager();
let mut cfg = config::Config::default();
cfg.models.default = Some("grok-3".to_string());
cfg.models.default = Some("kigi-3".to_string());
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
// Simulate stale inner cfg (no default) from a racing auth refresh.
let mut stale_cfg = config::Config::default();
@@ -2785,12 +2785,12 @@ mod tests {
*mgr.inner.cfg.write() = stale_cfg;
let mut new_cfg = config::Config::default();
new_cfg.models.default = Some("grok-3".to_string());
new_cfg.models.default = Some("kigi-3".to_string());
mgr.apply_config(new_cfg);
assert_eq!(
mgr.current_model_id().0.as_ref(),
"grok-3",
"kigi-3",
"apply_config must honor updated preferred model from config"
);
}
@@ -2800,10 +2800,10 @@ mod tests {
let mgr = test_manager();
let cfg = config::Config::default();
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
// Unrelated config change — preferred model unchanged.
let new_cfg = config::Config::default();
@@ -2811,7 +2811,7 @@ mod tests {
assert_eq!(
mgr.current_model_id().0.as_ref(),
"grok-4",
"kigi-4",
"apply_config must not reset model when preferred hasn't changed"
);
}
@@ -2820,16 +2820,16 @@ mod tests {
fn apply_config_falls_back_when_preferred_not_in_catalog() {
let mgr = test_manager();
let mut cfg = config::Config::default();
cfg.models.default = Some("grok-3".to_string());
cfg.models.default = Some("kigi-3".to_string());
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
// Preferred model not in catalog — falls back to first entry.
let mut new_cfg = config::Config::default();
new_cfg.models.default = Some("grok-nonexistent".to_string());
new_cfg.models.default = Some("kigi-nonexistent".to_string());
mgr.apply_config(new_cfg);
let current = mgr.current_model_id();
@@ -2845,15 +2845,15 @@ mod tests {
fn apply_config_both_none_preferred_preserves_current() {
let mgr = test_manager();
let cfg = config::Config::default();
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
let new_cfg = config::Config::default();
mgr.apply_config(new_cfg);
assert_eq!(
mgr.current_model_id().0.as_ref(),
"grok-4",
"kigi-4",
"both-None preferred must preserve user's runtime model"
);
}
@@ -2862,13 +2862,13 @@ mod tests {
fn apply_config_old_some_new_none_preserves_current() {
let mgr = test_manager();
let mut cfg = config::Config::default();
cfg.models.default = Some("grok-3".to_string());
cfg.models.default = Some("kigi-3".to_string());
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
assert_eq!(mgr.current_model_id().0.as_ref(), "grok-3");
assert_eq!(mgr.current_model_id().0.as_ref(), "kigi-3");
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
// [models] default removed — is_some() guard prevents reset.
let new_cfg = config::Config::default();
@@ -2876,7 +2876,7 @@ mod tests {
assert_eq!(
mgr.current_model_id().0.as_ref(),
"grok-4",
"kigi-4",
"old=Some new=None must not reset model (is_some guard)"
);
}
@@ -2887,29 +2887,29 @@ mod tests {
fn auth_refresh_then_config_reload_preserves_user_model() {
let mgr = test_manager();
let mut cfg = config::Config::default();
cfg.models.default = Some("grok-3".to_string());
cfg.models.default = Some("kigi-3".to_string());
// Initial fetch.
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
// User runs /model grok-4.
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
// User runs /model kigi-4.
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
// Auth refresh races — clears prefetched/etag.
*mgr.inner.prefetched.write() = None;
*mgr.inner.etag.write() = None;
// Second fetch must preserve user's model.
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
assert_eq!(mgr.current_model_id().0.as_ref(), "grok-4");
assert_eq!(mgr.current_model_id().0.as_ref(), "kigi-4");
// Config reload with persisted preference.
let mut new_cfg = config::Config::default();
new_cfg.models.default = Some("grok-4".to_string());
new_cfg.models.default = Some("kigi-4".to_string());
mgr.apply_config(new_cfg);
assert_eq!(mgr.current_model_id().0.as_ref(), "grok-4");
assert_eq!(mgr.current_model_id().0.as_ref(), "kigi-4");
}
// ── disk-cache hot-reload (external models_cache.json writes) ────
@@ -2931,7 +2931,7 @@ mod tests {
let auth_method = mgr.inner.fetch_auth.read().cache_auth_method();
cache.persist(
&make_prefetched(&["grok-4.5", "grok-4.3"]),
&make_prefetched(&["kigi-4.5", "kigi-4.3"]),
Some("etag-ext"),
auth_method,
&mgr.cache_origin(),
@@ -2940,8 +2940,8 @@ mod tests {
mgr.reload_from_cache_manager(&cache);
assert!(mgr.has_fetched_real_catalog());
assert!(mgr.models().contains_key("grok-4.5"));
assert!(mgr.models().contains_key("grok-4.3"));
assert!(mgr.models().contains_key("kigi-4.5"));
assert!(mgr.models().contains_key("kigi-4.3"));
assert_eq!(mgr.inner.etag.read().as_deref(), Some("etag-ext"));
}
@@ -3025,9 +3025,9 @@ mod tests {
fn reload_from_disk_cache_skips_identical_catalog_and_adopts_etag() {
let mgr = test_manager();
let cfg = config::Config::default();
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched.clone()), Some("etag-a".into()));
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
let tmp = tempfile::TempDir::new().unwrap();
let cache = test_cache_manager(tmp.path());
@@ -3043,7 +3043,7 @@ mod tests {
assert_eq!(
mgr.current_model_id().0.as_ref(),
"grok-4",
"kigi-4",
"identical catalog must not disturb the user's model"
);
assert_eq!(
@@ -3068,13 +3068,13 @@ mod tests {
auth_method: Some(auth_method),
origin: Some(mgr.cache_origin()),
etag: Some("etag-stale".into()),
models: make_prefetched(&["grok-stale"]),
models: make_prefetched(&["kigi-stale"]),
};
cache.atomic_write(&stale);
mgr.reload_from_cache_manager(&cache);
assert!(!mgr.models().contains_key("grok-stale"));
assert!(!mgr.models().contains_key("kigi-stale"));
assert!(mgr.inner.etag.read().is_none());
}
@@ -3093,7 +3093,7 @@ mod tests {
CacheAuthMethod::Platforms
};
cache.persist(
&make_prefetched(&["grok-other-auth"]),
&make_prefetched(&["kigi-other-auth"]),
Some("etag-x"),
other,
&mgr.cache_origin(),
@@ -3101,7 +3101,7 @@ mod tests {
mgr.reload_from_cache_manager(&cache);
assert!(!mgr.models().contains_key("grok-other-auth"));
assert!(!mgr.models().contains_key("kigi-other-auth"));
}
/// A cache persisted by a process pointed at a *different backend* (env
@@ -3117,7 +3117,7 @@ mod tests {
let cache = test_cache_manager(tmp.path());
let auth_method = mgr.inner.fetch_auth.read().cache_auth_method();
cache.persist(
&make_prefetched(&["grok-other-origin"]),
&make_prefetched(&["kigi-other-origin"]),
Some("etag-y"),
auth_method,
"http://127.0.0.1:49953/v1/models",
@@ -3125,7 +3125,7 @@ mod tests {
mgr.reload_from_cache_manager(&cache);
assert!(!mgr.models().contains_key("grok-other-origin"));
assert!(!mgr.models().contains_key("kigi-other-origin"));
assert!(mgr.inner.etag.read().is_none());
}
@@ -3144,13 +3144,13 @@ mod tests {
auth_method: Some(auth_method),
origin: None,
etag: Some("etag-legacy".into()),
models: make_prefetched(&["grok-legacy"]),
models: make_prefetched(&["kigi-legacy"]),
};
cache.atomic_write(&legacy);
mgr.reload_from_cache_manager(&cache);
assert!(!mgr.models().contains_key("grok-legacy"));
assert!(!mgr.models().contains_key("kigi-legacy"));
}
// ── clear() resets has_fetched_real_catalog ──────────────────────
@@ -3159,9 +3159,9 @@ mod tests {
fn clear_resets_has_fetched_real_catalog() {
let mgr = test_manager();
let mut cfg = config::Config::default();
cfg.models.default = Some("grok-3".to_string());
cfg.models.default = Some("kigi-3".to_string());
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
assert!(mgr.has_fetched_real_catalog());
@@ -3169,7 +3169,7 @@ mod tests {
assert!(!mgr.has_fetched_real_catalog());
// New identity fetch — resolves default via reselect_default_model.
let prefetched = make_prefetched(&["grok-4.5", "grok-4.3"]);
let prefetched = make_prefetched(&["kigi-4.5", "kigi-4.3"]);
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
let first_available = mgr.available().keys().next().unwrap().clone();
assert_eq!(
@@ -3610,25 +3610,25 @@ mod tests {
#[test]
fn build_prefetched_map_distinct_ids_same_slug() {
let entries = vec![
make_entry_config_with_id(Some("auto"), "grok-build", Some("Auto")),
make_entry_config_with_id(Some("grok-build"), "grok-build", Some("Grok Build")),
make_entry_config_with_id(Some("auto"), "kigi", Some("Auto")),
make_entry_config_with_id(Some("kigi"), "kigi", Some("Kigi")),
make_entry_config_with_id(
Some("grok-composer-2.5-fast"),
"grok-composer-2.5-fast",
Some("Grok Fast"),
Some("kigi-composer-2.5-fast"),
"kigi-composer-2.5-fast",
Some("Kigi Fast"),
),
];
let map = build_prefetched_map(entries);
assert_eq!(map.len(), 3, "all three entries should survive");
assert!(map.contains_key("auto"));
assert!(map.contains_key("grok-build"));
assert!(map.contains_key("grok-composer-2.5-fast"));
assert!(map.contains_key("kigi"));
assert!(map.contains_key("kigi-composer-2.5-fast"));
assert_eq!(
map["auto"].info.model, "grok-build",
"auto entry should still route to grok-build"
map["auto"].info.model, "kigi",
"auto entry should still route to kigi"
);
assert_eq!(map["grok-build"].info.model, "grok-build");
assert_eq!(map["kigi"].info.model, "kigi");
}
/// No id field — falls back to model slug as key.
@@ -3649,13 +3649,13 @@ mod tests {
#[test]
fn build_prefetched_map_duplicate_id_overwrites() {
let entries = vec![
make_entry_config_with_id(Some("grok-build"), "grok-build", Some("First")),
make_entry_config_with_id(Some("grok-build"), "grok-build", Some("Second")),
make_entry_config_with_id(Some("kigi"), "kigi", Some("First")),
make_entry_config_with_id(Some("kigi"), "kigi", Some("Second")),
];
let map = build_prefetched_map(entries);
assert_eq!(map.len(), 1, "duplicate id: second overwrites first");
assert_eq!(map["grok-build"].info.name.as_deref(), Some("Second"));
assert_eq!(map["kigi"].info.name.as_deref(), Some("Second"));
}
/// Regression: resolve_default_model must match by id before scanning
@@ -3664,31 +3664,24 @@ mod tests {
#[test]
fn resolve_default_model_prefers_id_over_model_slug() {
let mut catalog: IndexMap<String, ModelEntry> = IndexMap::new();
catalog.insert(
"auto-grok-build".to_string(),
make_model_entry("grok-build"),
);
catalog.insert("grok-build".to_string(), make_model_entry("grok-build"));
catalog.insert("auto-kigi".to_string(), make_model_entry("kigi"));
catalog.insert("kigi".to_string(), make_model_entry("kigi"));
let mut cfg = config::Config::default();
cfg.models.default = Some("grok-build".to_string());
cfg.models.default = Some("kigi".to_string());
let (key, _, _) = resolve_default_model(&cfg, &catalog, true);
assert_eq!(key, "grok-build", "must match id, not first slug hit");
assert_eq!(key, "kigi", "must match id, not first slug hit");
}
/// No id field — falls back to slug as key.
#[test]
fn build_prefetched_map_none_id_falls_back_to_slug() {
let entries = vec![make_entry_config_with_id(
None,
"grok-build",
Some("Grok Build"),
)];
let entries = vec![make_entry_config_with_id(None, "kigi", Some("Kigi"))];
let map = build_prefetched_map(entries);
assert_eq!(map.len(), 1);
assert!(map.contains_key("grok-build"));
assert!(map.contains_key("kigi"));
}
// ── persisted model id → catalog key (session resume) ─────────────
@@ -3696,94 +3689,79 @@ mod tests {
#[test]
fn resolve_catalog_key_maps_routing_slug_to_config_key() {
let mut models = IndexMap::new();
models.insert(
"enterprise-grok-build".to_string(),
make_model_entry("grok-4.5"),
);
models.insert("grok-4.3".to_string(), make_model_entry("grok-4.3"));
models.insert("enterprise-kigi".to_string(), make_model_entry("kigi-4.5"));
models.insert("kigi-4.3".to_string(), make_model_entry("kigi-4.3"));
let persisted = acp::ModelId::new("grok-4.5");
let persisted = acp::ModelId::new("kigi-4.5");
let key = resolve_catalog_key(&models, &persisted).expect("slug must resolve");
assert_eq!(key.0.as_ref(), "enterprise-grok-build");
assert_eq!(key.0.as_ref(), "enterprise-kigi");
}
#[test]
fn resolve_catalog_key_prefers_exact_key_match() {
let mut models = IndexMap::new();
models.insert("grok-4.5".to_string(), make_model_entry("grok-4.5"));
models.insert("kigi-4.5".to_string(), make_model_entry("kigi-4.5"));
let persisted = acp::ModelId::new("grok-4.5");
let persisted = acp::ModelId::new("kigi-4.5");
let key = resolve_catalog_key(&models, &persisted).expect("exact key must resolve");
assert_eq!(key.0.as_ref(), "grok-4.5");
assert_eq!(key.0.as_ref(), "kigi-4.5");
}
#[test]
fn resolve_catalog_key_last_slug_match_wins() {
let mut models = IndexMap::new();
models.insert(
"default-grok-build".to_string(),
make_model_entry("grok-4.5"),
);
models.insert("user-grok-build".to_string(), make_model_entry("grok-4.5"));
models.insert("default-kigi".to_string(), make_model_entry("kigi-4.5"));
models.insert("user-kigi".to_string(), make_model_entry("kigi-4.5"));
let persisted = acp::ModelId::new("grok-4.5");
let persisted = acp::ModelId::new("kigi-4.5");
let key = resolve_catalog_key(&models, &persisted).expect("slug must resolve");
assert_eq!(key.0.as_ref(), "user-grok-build");
assert_eq!(key.0.as_ref(), "user-kigi");
}
#[test]
fn selectable_catalog_key_for_persisted_none_when_resolved_not_available() {
let mut models = IndexMap::new();
models.insert(
"enterprise-grok-build".to_string(),
make_model_entry("grok-4.5"),
);
models.insert("enterprise-kigi".to_string(), make_model_entry("kigi-4.5"));
let available: IndexMap<_, _> = IndexMap::new();
let persisted = acp::ModelId::new("grok-4.5");
let persisted = acp::ModelId::new("kigi-4.5");
assert!(selectable_catalog_key_for_persisted(&models, &available, &persisted).is_none());
}
#[test]
fn selectable_prefers_available_identity_over_non_selectable_exact_key() {
let mut models = IndexMap::new();
models.insert("grok-build".to_string(), make_model_entry("grok-build"));
models.insert(
"enterprise-grok-build".to_string(),
make_model_entry("grok-build"),
);
models.insert("grok-4.3".to_string(), make_model_entry("grok-4.3"));
models.insert("kigi".to_string(), make_model_entry("kigi"));
models.insert("enterprise-kigi".to_string(), make_model_entry("kigi"));
models.insert("kigi-4.3".to_string(), make_model_entry("kigi-4.3"));
let available = test_available_keys(&["enterprise-grok-build", "grok-4.3"]);
let available = test_available_keys(&["enterprise-kigi", "kigi-4.3"]);
let persisted = acp::ModelId::new("grok-build");
let persisted = acp::ModelId::new("kigi");
assert_eq!(
resolve_catalog_key(&models, &persisted)
.expect("exact key exists")
.0
.as_ref(),
"grok-build"
"kigi"
);
let key = selectable_catalog_key_for_persisted(&models, &available, &persisted)
.expect("must resolve to selectable section");
assert_eq!(key.0.as_ref(), "enterprise-grok-build");
assert_eq!(key.0.as_ref(), "enterprise-kigi");
}
#[test]
fn selectable_matches_routing_slug_when_no_exact_key() {
let mut models = IndexMap::new();
models.insert(
"enterprise-grok-build".to_string(),
make_model_entry("grok-build"),
);
models.insert("grok-4.3".to_string(), make_model_entry("grok-4.3"));
models.insert("enterprise-kigi".to_string(), make_model_entry("kigi"));
models.insert("kigi-4.3".to_string(), make_model_entry("kigi-4.3"));
let available = test_available_keys(&["enterprise-grok-build", "grok-4.3"]);
let available = test_available_keys(&["enterprise-kigi", "kigi-4.3"]);
let persisted = acp::ModelId::new("grok-build");
let persisted = acp::ModelId::new("kigi");
let key = selectable_catalog_key_for_persisted(&models, &available, &persisted)
.expect("slug must resolve to selectable key");
assert_eq!(key.0.as_ref(), "enterprise-grok-build");
assert_eq!(key.0.as_ref(), "enterprise-kigi");
}
/// A persisted *selectable* catalog key binds to itself even when a later
@@ -3791,15 +3769,15 @@ mod tests {
#[test]
fn selectable_prefers_exact_key_over_later_slug_match() {
let mut models = IndexMap::new();
models.insert("grok-build".to_string(), make_model_entry("grok-4.5"));
models.insert("other".to_string(), make_model_entry("grok-build"));
models.insert("kigi".to_string(), make_model_entry("kigi-4.5"));
models.insert("other".to_string(), make_model_entry("kigi"));
let available = test_available_keys(&["grok-build", "other"]);
let available = test_available_keys(&["kigi", "other"]);
let persisted = acp::ModelId::new("grok-build");
let persisted = acp::ModelId::new("kigi");
let key = selectable_catalog_key_for_persisted(&models, &available, &persisted)
.expect("exact selectable key must win");
assert_eq!(key.0.as_ref(), "grok-build");
assert_eq!(key.0.as_ref(), "kigi");
}
fn test_available_keys(keys: &[&str]) -> IndexMap<acp::ModelId, acp::ModelInfo> {
@@ -657,13 +657,13 @@ mod tests {
#[test]
fn parse_openai_format_uses_id_field() {
let value = serde_json::json!(
{ "id" : "grok-3", "object" : "model", "owned_by" : "xai", "context_window" :
{ "id" : "kigi-3", "object" : "model", "owned_by" : "xai", "context_window" :
131072 }
);
let result = parse_remote_model_value(&value, "https://api.x.ai/v1").unwrap();
assert_eq!(result.model, "grok-3");
assert_eq!(result.base_url, "https://api.x.ai/v1");
assert_eq!(result.name.as_deref(), Some("grok-3"));
let result = parse_remote_model_value(&value, "https://byok.example/v1").unwrap();
assert_eq!(result.model, "kigi-3");
assert_eq!(result.base_url, "https://byok.example/v1");
assert_eq!(result.name.as_deref(), Some("kigi-3"));
}
#[test]
fn parse_model_field_takes_priority_over_id() {
@@ -753,14 +753,14 @@ mod tests {
fn parse_reads_reasoning_effort_fields() {
use kigi_sampling_types::ReasoningEffort;
let value = serde_json::json!(
{ "model" : "grok-4.5", "context_window" : 1_000_000,
{ "model" : "kigi-4.5", "context_window" : 1_000_000,
"supports_reasoning_effort" : true, "reasoning_effort" : "high" }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
assert!(result.supports_reasoning_effort);
assert_eq!(result.reasoning_effort, Some(ReasoningEffort::High));
let value = serde_json::json!(
{ "model" : "grok-4.5", "contextWindow" : 1_000_000,
{ "model" : "kigi-4.5", "contextWindow" : 1_000_000,
"supportsReasoningEffort" : true, "reasoningEffort" : "xhigh" }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
@@ -775,7 +775,7 @@ mod tests {
fn parse_reads_reasoning_efforts_list() {
use kigi_sampling_types::ReasoningEffort;
let value = serde_json::json!(
{ "model" : "grok-4.5", "context_window" : 1_000_000, "reasoning_efforts" :
{ "model" : "kigi-4.5", "context_window" : 1_000_000, "reasoning_efforts" :
[{ "id" : "deep", "value" : "xhigh", "label" : "Deep" }, { "value" :
"quantum" }, "low",] }
);
@@ -819,7 +819,7 @@ mod tests {
#[test]
fn parse_remote_model_value_no_laziness_detector_block_yields_default() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, }
{ "model" : "kigi-4", "context_window" : 256_000, }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
assert_eq!(
@@ -830,7 +830,7 @@ mod tests {
#[test]
fn parse_remote_model_value_parses_camelcase_key() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
"enabled" : true, "max_nudges_per_session" : 2, "idle_threshold_ms" : 12_000,
"min_confidence" : 0.75, }, }
);
@@ -847,7 +847,7 @@ mod tests {
#[test]
fn parse_remote_model_value_parses_snake_case_laziness_detector() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, "laziness_detector" : {
{ "model" : "kigi-4", "context_window" : 256_000, "laziness_detector" : {
"enabled" : true, "max_nudges_per_session" : 3, "idle_threshold_ms" : 8_000,
"min_confidence" : 0.6, }, }
);
@@ -864,7 +864,7 @@ mod tests {
#[test]
fn parse_remote_model_value_parses_meta_laziness_detector() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, "_meta" : {
{ "model" : "kigi-4", "context_window" : 256_000, "_meta" : {
"lazinessDetector" : { "enabled" : true, "max_nudges_per_session" : 1,
"idle_threshold_ms" : 15_000, "min_confidence" : 0.9, }, }, }
);
@@ -881,7 +881,7 @@ mod tests {
#[test]
fn parse_remote_model_value_partial_block_uses_field_defaults() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
"enabled" : true, }, }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
@@ -897,7 +897,7 @@ mod tests {
#[test]
fn parse_remote_model_value_malformed_block_falls_back_to_default() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
"enabled" : true, "max_nudges_per_session" : "abc", }, }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
@@ -909,7 +909,7 @@ mod tests {
#[test]
fn parse_remote_model_value_non_object_value_falls_back_to_default() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" :
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" :
"not-an-object", }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
@@ -921,7 +921,7 @@ mod tests {
#[test]
fn parse_remote_model_value_top_level_camelcase_wins_over_snake_case() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
"enabled" : true, "max_nudges_per_session" : 7, }, "laziness_detector" : {
"enabled" : false, "max_nudges_per_session" : 99, }, }
);
@@ -942,7 +942,7 @@ mod tests {
#[test]
fn parse_remote_model_value_parses_include_reasoning_under_camelcase_wrapper() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
"enabled" : true, "include_reasoning" : false, }, }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
@@ -951,7 +951,7 @@ mod tests {
#[test]
fn parse_remote_model_value_parses_include_reasoning_under_snake_case_wrapper() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, "laziness_detector" : {
{ "model" : "kigi-4", "context_window" : 256_000, "laziness_detector" : {
"enabled" : true, "include_reasoning" : true, }, }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
@@ -960,7 +960,7 @@ mod tests {
#[test]
fn parse_remote_model_value_omitted_include_reasoning_defaults_to_none() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
"enabled" : true, "max_nudges_per_session" : 2, }, }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
@@ -972,7 +972,7 @@ mod tests {
#[test]
fn parse_remote_model_value_top_level_wins_over_meta() {
let value = serde_json::json!(
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
"enabled" : true, "max_nudges_per_session" : 5, }, "_meta" : {
"lazinessDetector" : { "enabled" : false, "max_nudges_per_session" : 99, },
}, }
@@ -990,19 +990,19 @@ mod tests {
#[test]
fn parse_reads_show_model_fingerprint_field() {
let value = serde_json::json!(
{ "model" : "grok-build", "context_window" : 256_000,
{ "model" : "kigi", "context_window" : 256_000,
"show_model_fingerprint" : true }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
assert!(result.show_model_fingerprint);
let value = serde_json::json!(
{ "model" : "grok-build", "contextWindow" : 256_000, "showModelFingerprint" :
{ "model" : "kigi", "contextWindow" : 256_000, "showModelFingerprint" :
true }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
assert!(result.show_model_fingerprint);
let value = serde_json::json!(
{ "model" : "grok-build", "context_window" : 256_000, "_meta" : {
{ "model" : "kigi", "context_window" : 256_000, "_meta" : {
"showModelFingerprint" : true } }
);
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
@@ -1087,10 +1087,13 @@ mod tests {
fn list_url_derived_from_base_url() {
let ep = endpoints(
"https://proxy.kigi.com/v1",
Some("https://api.x.ai/v1"),
Some("https://byok.example/v1"),
None,
);
assert_eq!(ep.resolve_models_list_url(), "https://api.x.ai/v1/models");
assert_eq!(
ep.resolve_models_list_url(),
"https://byok.example/v1/models"
);
}
#[test]
fn list_url_explicit_overrides_derivation() {
@@ -65,10 +65,10 @@ impl acp::Agent for MvpAgent {
}
if client_type == ClientType::Generic {
match client_identifier.as_deref() {
Some("grok-web") => client_type = ClientType::GrokWeb,
Some("kigi-web") => client_type = ClientType::KigiWeb,
Some("nebula") => client_type = ClientType::Nebula,
Some("grok-code-extension") => client_type = ClientType::Extension,
Some("grok-desktop") => client_type = ClientType::Desktop,
Some("kigi-code-extension") => client_type = ClientType::Extension,
Some("kigi-desktop") => client_type = ClientType::Desktop,
_ => {}
}
}
@@ -80,7 +80,7 @@ impl acp::Agent for MvpAgent {
code_nav_enabled, client_type = ? client_type, event =
"code_nav_capability_parsed",
"code-nav capability initialized from initialize request; \
index will start lazily on first x.ai/code/* request if eligible"
index will start lazily on first kigi/code/* request if eligible"
);
let interactive_trust_client = Self::parse_interactive_trust_capability(
&arguments,
@@ -279,7 +279,7 @@ impl acp::Agent for MvpAgent {
.load_session(true)
.meta(
serde_json::json!(
{ "x.ai/fs_notify" : true, "x.ai/hooks" : { "blockingEvents"
{ "kigi/fs_notify" : true, "kigi/hooks" : { "blockingEvents"
: [kigi_hooks::event::HookEventName::PreToolUse],
"decisions" : ["deny"], }, }
)
@@ -297,7 +297,7 @@ impl acp::Agent for MvpAgent {
.meta({
let metadata = parse_json_object_env("KIGI_AGENT_METADATA");
serde_json::json!(
{ "grokShell" : true, "defaultAuthMethodId" :
{ "kigiShell" : true, "defaultAuthMethodId" :
default_auth_method_id_wire, (kigi_mcp::wire::MCP_SDK) :
true, (SESSION_PLUGIN_DIRS_CAPABILITY_KEY) : true,
"currentWorkingDirectory" : current_working_directory
@@ -372,7 +372,7 @@ impl acp::Agent for MvpAgent {
return self
.authenticate(
acp::AuthenticateRequest::new(
acp::AuthMethodId::new(auth_method::KIGI_COM_METHOD_ID),
acp::AuthMethodId::new(auth_method::KIMI_CODE_METHOD_ID),
)
.meta(arguments.meta),
)
@@ -425,7 +425,7 @@ impl acp::Agent for MvpAgent {
emit_login_span(true, "cached_token", uid.as_deref(), None);
Ok(self.auth_response_with_meta())
}
auth_method::KIGI_COM_METHOD_ID => {
auth_method::KIMI_CODE_METHOD_ID => {
let kimi_ctx = self.auth_manager.kimi_code_config().clone();
let auth_meta = AuthRequestMeta::from_json(arguments.meta.as_ref());
tracing::info!(
@@ -488,10 +488,10 @@ impl acp::Agent for MvpAgent {
let mut sampling_config = self.sampling_config.borrow_mut();
sampling_config.api_key = Some(auth.key.clone());
tracing::debug!(
"auth: grok.com/oidc handler set api_key (SessionToken)"
"auth: kimi.com/oidc handler set api_key (SessionToken)"
);
kigi_log::unified_log::debug(
"auth: grok.com/oidc handler set api_key (SessionToken)",
"auth: kimi.com/oidc handler set api_key (SessionToken)",
None,
None,
);
@@ -827,7 +827,7 @@ impl acp::Agent for MvpAgent {
Some(serde_json::json!({ "cwd" : cwd.as_str() })),
);
let models = if is_chat_kind {
// The grok.com chat-mode model picker was removed with the xAI
// The kimi.com chat-mode model picker was removed with the xAI
// proxy; a chat-kind session has no managed catalog to offer.
chat_new_session_model_state(
acp::SessionModelState::new(acp::ModelId::from(String::new()), Vec::new()),
@@ -846,8 +846,8 @@ impl acp::Agent for MvpAgent {
feedback_enabled, }
);
if let Some(obj) = meta.as_object_mut() {
obj.insert("x.ai/sessionConfig".to_string(), session_config_value);
obj.insert("x.ai/sessionDetail".to_string(), session_detail_value);
obj.insert("kigi/sessionConfig".to_string(), session_config_value);
obj.insert("kigi/sessionDetail".to_string(), session_detail_value);
}
Ok(
acp::NewSessionResponse::new(session_id)
@@ -874,12 +874,12 @@ impl acp::Agent for MvpAgent {
let persist_data = arguments
.meta
.as_ref()
.and_then(|m| m.get("x.ai/persist"))
.and_then(|m| m.get("kigi/persist"))
.cloned();
let target_client_id = arguments
.meta
.as_ref()
.and_then(|m| m.get("x.ai/leaderClientId"))
.and_then(|m| m.get("kigi/leaderClientId"))
.cloned();
let acp::LoadSessionRequest {
session_id,
@@ -1010,7 +1010,7 @@ impl acp::Agent for MvpAgent {
);
let restore_code_requested = request_meta
.as_ref()
.and_then(|m| m.get("x.ai/restore_code"))
.and_then(|m| m.get("kigi/restore_code"))
.and_then(|v| v.as_bool())
.unwrap_or(self.restore_code);
let registry_client_for_restore = self.session_registry_client();
@@ -1030,7 +1030,7 @@ impl acp::Agent for MvpAgent {
target : kigi_workspace::session::git::RESTORE_CODE_LOG, session_id =
% session_id.0, supplied_cwd = % cwd.as_str(), persisted_cwd = % summary
.info.cwd, target_sha = % target_sha,
"restore_code: skipping session HEAD checkout — supplied cwd is neither a grok worktree nor the session's persisted cwd (refusing to detach the source repo)"
"restore_code: skipping session HEAD checkout — supplied cwd is neither a kigi worktree nor the session's persisted cwd (refusing to detach the source repo)"
);
kigi_log::unified_log::warn(
"restore_code: skipped session HEAD checkout (unsafe cwd)",
@@ -1075,7 +1075,7 @@ impl acp::Agent for MvpAgent {
let load_envrc = {
let skip_envrc = request_meta
.as_ref()
.and_then(|m| m.get("x.ai/skip_envrc"))
.and_then(|m| m.get("kigi/skip_envrc"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
if skip_envrc {
@@ -1156,7 +1156,7 @@ impl acp::Agent for MvpAgent {
);
let prompt_display_cwd = request_meta
.as_ref()
.and_then(|m| m.get("x.ai/display_cwd"))
.and_then(|m| m.get("kigi/display_cwd"))
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.or_else(|| summary.prompt_display_cwd.clone());
@@ -1462,7 +1462,7 @@ impl acp::Agent for MvpAgent {
let mut response_meta_map = serde_json::Map::new();
response_meta_map.insert("sessionId".to_string(), serde_json::json!(session_id));
if let Some(persist) = persist_data {
response_meta_map.insert("x.ai/persist".to_string(), persist);
response_meta_map.insert("kigi/persist".to_string(), persist);
}
let session_cwd = self
.sessions
@@ -1517,7 +1517,7 @@ impl acp::Agent for MvpAgent {
{
response_meta_map
.insert(
"x.ai/runningPromptId".to_string(),
"kigi/runningPromptId".to_string(),
serde_json::json!(running_prompt_id),
);
}
@@ -1529,8 +1529,8 @@ impl acp::Agent for MvpAgent {
summary.display_title_opt(),
&model_state,
);
response_meta_map.insert("x.ai/sessionConfig".to_string(), session_config_value);
response_meta_map.insert("x.ai/sessionDetail".to_string(), session_detail_value);
response_meta_map.insert("kigi/sessionConfig".to_string(), session_config_value);
response_meta_map.insert("kigi/sessionDetail".to_string(), session_detail_value);
let response_meta = serde_json::Value::Object(response_meta_map);
kigi_log::unified_log::info(
"session loaded",
@@ -1846,7 +1846,7 @@ impl acp::Agent for MvpAgent {
self.gateway
.forward_fire_and_forget(
acp::ExtNotification::new(
"x.ai/session/prompt_complete",
"kigi/session/prompt_complete",
params.into(),
),
);
@@ -2045,116 +2045,116 @@ impl acp::Agent for MvpAgent {
let mut backend_no_bridge_err: Option<acp::Error> = None;
let method = args.method.clone();
let result = match method.as_ref() {
"x.ai/getApiKey" | "x.ai/setApiKey" => {
"kigi/getApiKey" | "kigi/setApiKey" => {
crate::extensions::auth::handle(self, &args).await
}
"x.ai/session/info" | "x.ai/session/close" | "x.ai/session/list"
| "x.ai/sessions/list" => {
"kigi/session/info" | "kigi/session/close" | "kigi/session/list"
| "kigi/sessions/list" => {
crate::agent::handlers::session::handle(self, &args).await
}
"x.ai/session/updates" => {
"kigi/session/updates" => {
crate::extensions::session_updates::handle(&args, &self.gateway).await
}
"x.ai/session/load_history" => {
"kigi/session/load_history" => {
crate::extensions::chat_conversation_history::handle(self, &args).await
}
"x.ai/session/search" => {
"kigi/session/search" => {
crate::extensions::session_search::handle(&args).await
}
"x.ai/session/resolve_local_for_worktree_resume"
| "x.ai/session/rehydrate" => {
"kigi/session/resolve_local_for_worktree_resume"
| "kigi/session/rehydrate" => {
let ops = self.resolve_workspace_ops()?;
crate::extensions::worktree::handle(self, &ops, &args).await
}
"x.ai/session/rename" | "x.ai/session/delete"
| "x.ai/session/update_mcp_servers" | "x.ai/session/fork"
| "x.ai/internal/reload_all_mcp_servers"
| "x.ai/internal/reload_project_mcp_servers" | "x.ai/internal/reload_skills"
| "x.ai/internal/reload_models" | "x.ai/internal/reload_models_cache"
| "x.ai/plugins/reload"
| "x.ai/commands/list" => {
"kigi/session/rename" | "kigi/session/delete"
| "kigi/session/update_mcp_servers" | "kigi/session/fork"
| "kigi/internal/reload_all_mcp_servers"
| "kigi/internal/reload_project_mcp_servers" | "kigi/internal/reload_skills"
| "kigi/internal/reload_models" | "kigi/internal/reload_models_cache"
| "kigi/plugins/reload"
| "kigi/commands/list" => {
crate::extensions::session_admin::handle(self, &args).await
}
"x.ai/session/repair" => crate::extensions::repair::handle(self, &args).await,
"x.ai/billing" => crate::extensions::billing::handle(self, &args).await,
"x.ai/memory/flush" | "x.ai/memory/rewrite" => {
"kigi/session/repair" => crate::extensions::repair::handle(self, &args).await,
"kigi/billing" => crate::extensions::billing::handle(self, &args).await,
"kigi/memory/flush" | "kigi/memory/rewrite" => {
crate::extensions::memory::handle(self, &args).await
}
"x.ai/skills/refresh-baseline" => {
"kigi/skills/refresh-baseline" => {
self.refresh_skill_baseline_for_all_sessions();
crate::extensions::to_ext_response(
Ok(serde_json::json!({ "ok" : true })),
)
}
"x.ai/interject" => crate::extensions::interject::handle(self, &args).await,
"x.ai/feedback" | "x.ai/feedback/dismiss" | "x.ai/btw" => {
"kigi/interject" => crate::extensions::interject::handle(self, &args).await,
"kigi/feedback" | "kigi/feedback/dismiss" | "kigi/btw" => {
crate::extensions::feedback::handle(self, &args).await
}
"x.ai/recap" => crate::extensions::recap::handle(self, &args).await,
"x.ai/rollout/survey" => {
"kigi/recap" => crate::extensions::recap::handle(self, &args).await,
"kigi/rollout/survey" => {
crate::extensions::rollout::handle(self, &args).await
}
"x.ai/prompt_history" => {
"kigi/prompt_history" => {
crate::extensions::prompt_history::handle(self, &args).await
}
"x.ai/suggest" => crate::extensions::suggest::handle(self, &args).await,
"x.ai/suggestPrompt" => crate::extensions::suggest::handle(self, &args).await,
s if s.starts_with("x.ai/auth/") => {
"kigi/suggest" => crate::extensions::suggest::handle(self, &args).await,
"kigi/suggestPrompt" => crate::extensions::suggest::handle(self, &args).await,
s if s.starts_with("kigi/auth/") => {
crate::extensions::auth::handle(self, &args).await
}
s if s.starts_with("x.ai/session_summaries/") => {
s if s.starts_with("kigi/session_summaries/") => {
crate::agent::handlers::session::handle(self, &args).await
}
s if s.starts_with("x.ai/git/worktree/") => {
s if s.starts_with("kigi/git/worktree/") => {
let ops = self.resolve_workspace_ops()?;
crate::extensions::worktree::handle(self, &ops, &args).await
}
s if s.starts_with("x.ai/git/") => {
s if s.starts_with("kigi/git/") => {
let ops = self.resolve_workspace_ops()?;
crate::extensions::git::handle(self, &ops, &args).await
}
s if s.starts_with("x.ai/compact_conversation") => {
s if s.starts_with("kigi/compact_conversation") => {
crate::extensions::memory::handle(self, &args).await
}
s if s.starts_with("x.ai/plugins/") => {
s if s.starts_with("kigi/plugins/") => {
crate::extensions::plugins::handle(self, &args).await
}
s if s.starts_with("x.ai/hooks/") => {
s if s.starts_with("kigi/hooks/") => {
crate::extensions::hooks::handle(self, &args).await
}
s if s.starts_with("x.ai/hunk-tracker/") => {
s if s.starts_with("kigi/hunk-tracker/") => {
let ops = self.resolve_workspace_ops()?;
crate::extensions::hunk_tracker::handle(self, &ops, &args).await
}
s if s.starts_with("x.ai/pr/") => {
s if s.starts_with("kigi/pr/") => {
crate::extensions::pr::handle(self, &args).await
}
s if s.starts_with(crate::extensions::mcp::mcp_methods::PREFIX) => {
crate::extensions::mcp::handle(self, &args).await
}
s if s.starts_with("x.ai/task/") => {
s if s.starts_with("kigi/task/") => {
crate::extensions::task::handle(self, &args).await
}
s if s.starts_with("x.ai/scheduler/") => {
s if s.starts_with("kigi/scheduler/") => {
crate::extensions::task::handle_scheduler(self, &args).await
}
s if s.starts_with("x.ai/subagent/") => {
s if s.starts_with("kigi/subagent/") => {
crate::extensions::task::handle_subagent(self, &args).await
}
s if s.starts_with("x.ai/terminal/") => {
s if s.starts_with("kigi/terminal/") => {
crate::extensions::terminal::handle(self, &args).await
}
s if crate::extensions::fs::is_fs_method(s) => {
crate::extensions::fs::handle(self, &args).await
}
s if s.starts_with("x.ai/search/") => {
s if s.starts_with("kigi/search/") => {
crate::extensions::search::handle(self, &args).await
}
s if s.starts_with("x.ai/code/") => {
s if s.starts_with("kigi/code/") => {
let ops = self.resolve_workspace_ops()?;
crate::extensions::code_nav::handle(self, &ops, &args).await
}
s if s.starts_with("x.ai/skills/") => {
s if s.starts_with("kigi/skills/") => {
let compat = self.cfg.borrow().compat_resolved;
crate::extensions::skills::handle(
&args,
@@ -2163,13 +2163,13 @@ impl acp::Agent for MvpAgent {
)
.await
}
s if s.starts_with("x.ai/review") => {
s if s.starts_with("kigi/review") => {
crate::extensions::feedback::handle(self, &args).await
}
s if s.starts_with("x.ai/debug/") => {
s if s.starts_with("kigi/debug/") => {
crate::extensions::debug::handle(self, &args).await
}
s if s.starts_with("x.ai/rewind") => {
s if s.starts_with("kigi/rewind") => {
crate::extensions::rewind::handle(self, &args).await
}
other => {
@@ -2193,7 +2193,7 @@ impl acp::Agent for MvpAgent {
args: acp::ExtNotification,
) -> Result<(), acp::Error> {
tracing::info!("Received extension notification: method={}", args.method);
if args.method.as_ref() == "x.ai/yolo_mode_changed"
if args.method.as_ref() == "kigi/yolo_mode_changed"
&& let Ok(params) = serde_json::from_str::<
serde_json::Value,
>(args.params.get())
@@ -2257,7 +2257,7 @@ impl acp::Agent for MvpAgent {
);
}
}
if args.method.as_ref() == "x.ai/permissions/reset" {
if args.method.as_ref() == "kigi/permissions/reset" {
let sessions = self.sessions.borrow();
let updated = sessions
.values()
@@ -2273,10 +2273,10 @@ impl acp::Agent for MvpAgent {
"Permission state reset for matching sessions"
);
}
if args.method.as_ref() == "x.ai/internal/evict_sessions" {
if args.method.as_ref() == "kigi/internal/evict_sessions" {
self.handle_evict_sessions(&args.params).await;
}
if args.method.as_ref() == "x.ai/toggle_plan_mode"
if args.method.as_ref() == "kigi/toggle_plan_mode"
&& let Ok(params) = serde_json::from_str::<
serde_json::Value,
>(args.params.get())
@@ -2317,8 +2317,8 @@ impl acp::Agent for MvpAgent {
}
}
if matches!(
args.method.as_ref(), "x.ai/queue/remove" | "x.ai/queue/reorder" |
"x.ai/queue/clear" | "x.ai/queue/edit" | "x.ai/queue/interject"
args.method.as_ref(), "kigi/queue/remove" | "kigi/queue/reorder" |
"kigi/queue/clear" | "kigi/queue/edit" | "kigi/queue/interject"
)
&& let Ok(params) = serde_json::from_str::<
serde_json::Value,
@@ -2358,14 +2358,14 @@ impl acp::Agent for MvpAgent {
);
}
}
if args.method.as_ref() == "x.ai/terminal/pty/input"
if args.method.as_ref() == "kigi/terminal/pty/input"
&& let Ok(params) = serde_json::from_str::<
serde_json::Value,
>(args.params.get())
{
crate::extensions::terminal::handle_pty_input(&params).await;
}
if args.method.as_ref() == "_x.ai/session/update" {
if args.method.as_ref() == "_kigi/session/update" {
if let Ok(notification) = serde_json::from_str::<
SessionNotification,
>(args.params.get()) {
@@ -2393,7 +2393,7 @@ impl acp::Agent for MvpAgent {
tracing::warn!("Failed to parse xAI session notification params");
}
}
if args.method.as_ref() == "x.ai/telemetry/non_git_decision" {
if args.method.as_ref() == "kigi/telemetry/non_git_decision" {
#[derive(serde::Deserialize)]
struct NonGitDecisionParams {
decision: String,
@@ -2412,7 +2412,7 @@ impl acp::Agent for MvpAgent {
tracing::warn!("Failed to parse non_git_decision telemetry params");
}
}
if args.method.as_ref() == "x.ai/telemetry/multi_agent_followup" {
if args.method.as_ref() == "kigi/telemetry/multi_agent_followup" {
#[derive(serde::Deserialize)]
struct MultiAgentFollowupParams {
preferred_agent_label: char,
@@ -2433,7 +2433,7 @@ impl acp::Agent for MvpAgent {
tracing::warn!("Failed to parse multi-agent followup telemetry params");
}
}
if args.method.as_ref() == "x.ai/telemetry/multi_agent_apply" {
if args.method.as_ref() == "kigi/telemetry/multi_agent_apply" {
#[derive(serde::Deserialize)]
struct MultiAgentApplyParams {
applied_agent_label: char,
@@ -2454,7 +2454,7 @@ impl acp::Agent for MvpAgent {
tracing::warn!("Failed to parse multi-agent apply telemetry params");
}
}
if args.method.as_ref() == "x.ai/telemetry/multi_agent_discard" {
if args.method.as_ref() == "kigi/telemetry/multi_agent_discard" {
#[derive(serde::Deserialize)]
struct MultiAgentDiscardParams {
/// (label, session_id, model_id)
@@ -108,7 +108,7 @@ impl MvpAgent {
}
/// Resolve folder trust and load launch-dir MCP configs after `initialize`
/// returns. The walks are synchronous and expensive in large monorepos; they
/// must not block the ACP response (grok-desktop sends `initialize` immediately).
/// must not block the ACP response (kigi-desktop sends `initialize` immediately).
pub(super) fn spawn_initialize_launch_mcp_setup(&self) {
let cwd = self.launch_cwd.clone();
let compat = self.cfg.borrow().compat_resolved;
@@ -151,7 +151,7 @@ impl MvpAgent {
/// Build the launch-dir plugin registry snapshot on first use.
///
/// Boot-time discovery was deferred past ACP `initialize` (the cwd→git-root
/// plus user/marketplace walks stalled grok-desktop's first `initialize`),
/// plus user/marketplace walks stalled kigi-desktop's first `initialize`),
/// leaving `plugin_registry_handle` empty. That shared snapshot still backs
/// the launch-dir plugin MCP/LSP merges read in `resolve_mcp_servers` and
/// the session LSP build, so populate it lazily — off the `initialize`
@@ -292,7 +292,7 @@ impl MvpAgent {
}
/// Pre-session command availability snapshot.
///
/// Used by the `x.ai/commands/list` ext method and the
/// Used by the `kigi/commands/list` ext method and the
/// `InitializeResponse._meta` path (`builtin_commands()`), both of
/// which fire before any session exists. The eventual agent's toolset
/// is unknown (depends on the model the user picks), so we fail-closed
@@ -346,7 +346,7 @@ impl MvpAgent {
///
/// Deferred past ACP wiring so `initialize` can respond before folder-trust
/// scans and `WorkspaceHandle::new_minimal` run (same boot stall as plugin
/// discovery on grok-desktop Windows).
/// discovery on kigi-desktop Windows).
fn ensure_local_workspace_ops(
&self,
) -> Result<kigi_workspace::WorkspaceOps, acp::Error> {
@@ -404,7 +404,7 @@ impl MvpAgent {
/// Returns `SessionToken` when EITHER:
/// - `auth_manager` currently has a live (non-expired) credential, OR
/// - the active auth method is session-based (`cached_token`,
/// `grok.com`, `oidc`) -- even if the in-memory token is currently
/// `kimi-code`, `oidc`) -- even if the in-memory token is currently
/// expired or missing.
///
/// Returns `ApiKey` only when the auth method is BYOK (`xai.api_key`) or
@@ -493,7 +493,7 @@ impl MvpAgent {
let _ = self
.gateway
.ext_notification(
acp::ExtNotification::new("x.ai/session_notification", params.into()),
acp::ExtNotification::new("kigi/session_notification", params.into()),
)
.await;
}
@@ -621,8 +621,8 @@ impl MvpAgent {
/// Build deploy-service config. The tool talks directly to the deployer service.
pub(super) fn prepare_app_builder_deployer_config(
&self,
) -> kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig {
use kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig;
) -> kigi_tools::implementations::kigi::deploy_app::AppBuilderDeployerConfig {
use kigi_tools::implementations::kigi::deploy_app::AppBuilderDeployerConfig;
AppBuilderDeployerConfig::Disabled
}
/// Web search config (PRD F5). The Kimi search service exists only on
@@ -677,8 +677,8 @@ impl MvpAgent {
/// OAuth sessions (PRD F5) > None (local pipeline only)
pub(super) fn prepare_web_fetch_config(
&self,
) -> kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig {
use kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig;
) -> kigi_tools::implementations::kigi::web_fetch::WebFetchConfig {
use kigi_tools::implementations::kigi::web_fetch::WebFetchConfig;
let cfg = self.cfg.borrow();
if cfg.disable_web_search {
return WebFetchConfig::Disabled;
@@ -816,7 +816,7 @@ impl MvpAgent {
subagent_event_tx,
subagent_event_rx: RefCell::new(Some(subagent_event_rx)),
subagent_coordinator: RefCell::new(subagent_coordinator),
monitor_event_buffer: kigi_tools::implementations::grok_build::task::types::MonitorEventBuffer::default(),
monitor_event_buffer: kigi_tools::implementations::kigi::task::types::MonitorEventBuffer::default(),
workspace_ops: RefCell::new(None),
require_gateway_sessions: Rc::new(
RefCell::new(std::collections::HashSet::new()),
@@ -833,7 +833,7 @@ impl MvpAgent {
instance.auth_manager.configure_refresher();
instance
}
/// Handle `x.ai/internal/evict_sessions` — the leader server tells us a
/// Handle `kigi/internal/evict_sessions` — the leader server tells us a
/// client disconnected and these sessions lost their IPC owner.
///
/// **This is the no-evict keystone.** A disconnect must
@@ -1082,12 +1082,12 @@ impl MvpAgent {
}
}
/// Cancel a subagent by id, returning a typed outcome that backs the pager's
/// `x.ai/subagent/cancel`. Active/pending → cancelled (a finish follows);
/// `kigi/subagent/cancel`. Active/pending → cancelled (a finish follows);
/// already-finished → its terminal status; unknown id → `NotFound`.
pub fn cancel_subagent(
&self,
subagent_id: &str,
) -> kigi_tools::implementations::grok_build::task::types::SubagentCancelOutcome {
) -> kigi_tools::implementations::kigi::task::types::SubagentCancelOutcome {
self.subagent_coordinator.borrow_mut().cancel_with_outcome(subagent_id)
}
/// List running subagent seeds for a given parent session.
@@ -1183,7 +1183,7 @@ impl MvpAgent {
let sessions = self.sessions.borrow();
sessions.get(session_id).cloned()
}
/// Get hooks list for a session (for `x.ai/hooks/list` extension).
/// Get hooks list for a session (for `kigi/hooks/list` extension).
pub async fn list_hooks(
&self,
session_id: &acp::SessionId,
@@ -1191,7 +1191,7 @@ impl MvpAgent {
let handle = self.get_session_handle(session_id)?;
handle.get_hooks_list().await
}
/// Execute a hooks management action (for `x.ai/hooks/action`).
/// Execute a hooks management action (for `kigi/hooks/action`).
pub async fn execute_hooks_action(
&self,
session_id: &acp::SessionId,
@@ -1207,7 +1207,7 @@ impl MvpAgent {
let handle = self.get_session_handle(session_id)?;
handle.execute_hooks_action(action).await
}
/// Execute a plugins management action (for `x.ai/plugins/action`).
/// Execute a plugins management action (for `kigi/plugins/action`).
pub async fn execute_plugins_action(
&self,
session_id: &acp::SessionId,
@@ -1225,7 +1225,7 @@ impl MvpAgent {
}
outcome
}
/// Get a snapshot of the shared plugin registry (for `x.ai/plugins/list`).
/// Get a snapshot of the shared plugin registry (for `kigi/plugins/list`).
pub fn plugin_registry_snapshot(
&self,
) -> Option<std::sync::Arc<kigi_agent::plugins::PluginRegistry>> {
@@ -1352,7 +1352,7 @@ impl MvpAgent {
current_effort,
)
}
/// Build the `x.ai/sessionConfig` and `x.ai/sessionDetail` `_meta` values
/// Build the `kigi/sessionConfig` and `kigi/sessionDetail` `_meta` values
/// shared by `new_session` and `load_session`, returned as
/// `(sessionConfig, sessionDetail)`. Keeping both response paths on this one
/// builder stops them drifting.
@@ -1364,7 +1364,7 @@ impl MvpAgent {
model_state: &acp::SessionModelState,
) -> (serde_json::Value, serde_json::Value) {
let config_options = self.session_config_options(Some(session_id), model_state);
let detail = session_config::GrokSessionDetail::build(
let detail = session_config::KigiSessionDetail::build(
session_id.0.to_string(),
cwd,
model_state.current_model_id.0.to_string(),
@@ -1455,13 +1455,13 @@ impl MvpAgent {
model_agent_type: Option<&str>,
) -> kigi_agent::AgentDefinition {
use kigi_agent::AgentDefinition;
let grok_agent_env_set = std::env::var("KIGI_AGENT")
let kigi_agent_env_set = std::env::var("KIGI_AGENT")
.ok()
.is_some_and(|s| !s.trim().is_empty());
let config_agent_explicitly_set = agent_config.name.is_some();
let model_requires_strict_harness = model_agent_type
.is_some_and(kigi_agent::config::is_strict_harness_agent_type);
if !grok_agent_env_set && !config_agent_explicitly_set
if !kigi_agent_env_set && !config_agent_explicitly_set
&& model_requires_strict_harness && let Some(required) = model_agent_type
&& let Some(def) = kigi_agent::discovery::by_name_in_cwd(required, cwd)
{
@@ -1527,8 +1527,8 @@ impl MvpAgent {
let agent_name = std::env::var("KIGI_AGENT").ok();
let resolved = match agent_name.as_deref() {
Some("browser-use") | Some("browser_use") => AgentDefinition::browser_use(),
Some("grok-build-concise") | Some("grok_build_concise") => {
AgentDefinition::grok_build_concise()
Some("kigi-concise") | Some("kigi_concise") => {
AgentDefinition::kigi_concise()
}
Some(path) if std::path::Path::new(path).is_absolute() => {
match AgentDefinition::from_file(path) {
@@ -1538,17 +1538,17 @@ impl MvpAgent {
path = path, error = % e,
"Failed to load agent definition from file, falling back to default"
);
AgentDefinition::grok_build_plan()
AgentDefinition::kigi_plan()
}
}
}
Some(name) => {
kigi_agent::discovery::by_name_in_cwd(name, cwd)
.unwrap_or_else(AgentDefinition::grok_build_plan)
.unwrap_or_else(AgentDefinition::kigi_plan)
}
None => AgentDefinition::grok_build_plan(),
None => AgentDefinition::kigi_plan(),
};
if !grok_agent_env_set && !config_agent_explicitly_set
if !kigi_agent_env_set && !config_agent_explicitly_set
&& model_requires_strict_harness && let Some(required) = model_agent_type
&& resolved.name != required
{
@@ -1638,7 +1638,7 @@ impl MvpAgent {
.client_capabilities
.meta
.as_ref()
.and_then(|m| m.get("x.ai/fs_notify"))
.and_then(|m| m.get("kigi/fs_notify"))
.and_then(|v| {
use crate::session::{ClientFsConfig, ClientFsMode};
use kigi_fsnotify::FsConfig;
@@ -1712,7 +1712,7 @@ impl MvpAgent {
.client_capabilities
.meta
.as_ref()
.and_then(|m| m.get("x.ai/hunkTracker"))
.and_then(|m| m.get("kigi/hunkTracker"))
.and_then(|v| v.get("mode"))
.and_then(|v| v.as_str()),
);
@@ -1720,14 +1720,14 @@ impl MvpAgent {
.client_capabilities
.meta
.as_ref()
.and_then(|m| m.get("x.ai/incrementalBashOutput"))
.and_then(|m| m.get("kigi/incrementalBashOutput"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
let no_color = init
.client_capabilities
.meta
.as_ref()
.and_then(|m| m.get("x.ai/bashOutputNoColor"))
.and_then(|m| m.get("kigi/bashOutputNoColor"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
let hunk_tracking_enabled = hunk_plan.enabled();
@@ -2200,7 +2200,7 @@ impl MvpAgent {
.client_capabilities
.meta
.as_ref()
.and_then(|m| m.get("x.ai/gitHeadChanged"))
.and_then(|m| m.get("kigi/gitHeadChanged"))
.and_then(|v| v.as_bool());
let fs_watch_caps = crate::session::fs_watch::FsWatchCapabilities::resolve(crate::session::fs_watch::CapabilityInputs {
client_notify: fs_notify_config.is_some(),
@@ -4,13 +4,13 @@
use super::*;
impl MvpAgent {
/// Parse the `x.ai/codeNavigation.enabled` capability from an initialize
/// Parse the `kigi/codeNavigation.enabled` capability from an initialize
/// request. Returns `false` if the field is absent or not `true`.
pub(crate) fn parse_code_nav_capability(init: &acp::InitializeRequest) -> bool {
init.client_capabilities
.meta
.as_ref()
.and_then(|m| m.get("x.ai/codeNavigation"))
.and_then(|m| m.get("kigi/codeNavigation"))
.and_then(|v| v.get("enabled"))
.and_then(|v| v.as_bool())
.unwrap_or(false)
@@ -56,7 +56,7 @@ impl MvpAgent {
use crate::agent::config::CodebaseIndexingSetting;
// Gate 1: client type
if !matches!(client_type, ClientType::GrokWeb) {
if !matches!(client_type, ClientType::KigiWeb) {
tracing::info!(
client_type = ?client_type,
gate = "client_type",
@@ -71,7 +71,7 @@ impl MvpAgent {
tracing::info!(
gate = "capability",
skip_reason = "capability_not_advertised",
"code-nav eligibility check: skipping (x.ai/codeNavigation.enabled not advertised)"
"code-nav eligibility check: skipping (kigi/codeNavigation.enabled not advertised)"
);
return Err(CodeNavEligibility::CapabilityNotAdvertised);
}
@@ -138,7 +138,7 @@ impl MvpAgent {
Some(sid) => sid,
// No session_id: per-client capability cannot be determined without a
// session. Reject with SessionRequired rather than fall back to shared
// global state. Callers must provide sessionId for x.ai/code/* requests.
// global state. Callers must provide sessionId for kigi/code/* requests.
None => return Err(CodeNavEligibility::SessionRequired),
};
@@ -1,10 +1,10 @@
//! Interactive folder-trust prompt: a dormant agent→GUI-client ACP round-trip
//! (`x.ai/folder_trust/request`) that asks a GUI client (grok-desktop) to decide
//! (`kigi/folder_trust/request`) that asks a GUI client (kigi-desktop) to decide
//! trust for an untrusted-with-configs workspace, then grants + reloads the
//! now-trusted project servers without a restart.
//!
//! DORMANT in production: it only fires when the connected client advertised
//! `x.ai/folderTrust.interactive` AND the folder-trust feature flag is on AND the
//! `kigi/folderTrust.interactive` AND the folder-trust feature flag is on AND the
//! verdict is [`kigi_workspace::folder_trust::TrustOutcome::Prompt`]. No
//! client advertises the capability until the desktop UI ships — so this is
//! inert by default even with the feature flag on. The TUI/headless clients never
@@ -29,7 +29,7 @@ use super::*;
/// the whole connection lifetime.
const TRUST_PROMPT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30 * 60);
/// ACP `x.ai/folder_trust/request` payload (agent → GUI client). Serialized as
/// ACP `kigi/folder_trust/request` payload (agent → GUI client). Serialized as
/// `camelCase` for the ACP JSON-RPC wire format.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -62,21 +62,21 @@ pub(crate) enum FolderTrustOutcome {
Reject,
}
/// ACP `x.ai/folder_trust/request` response (GUI client → agent).
/// ACP `kigi/folder_trust/request` response (GUI client → agent).
#[derive(Debug, Clone, serde::Deserialize)]
pub(crate) struct FolderTrustResponse {
pub outcome: FolderTrustOutcome,
}
impl MvpAgent {
/// Parse the `x.ai/folderTrust.interactive` capability from an initialize
/// Parse the `kigi/folderTrust.interactive` capability from an initialize
/// request. Returns `false` if absent or not `true`. Mirrors
/// [`Self::parse_code_nav_capability`].
pub(crate) fn parse_interactive_trust_capability(init: &acp::InitializeRequest) -> bool {
init.client_capabilities
.meta
.as_ref()
.and_then(|m| m.get("x.ai/folderTrust"))
.and_then(|m| m.get("kigi/folderTrust"))
.and_then(|v| v.get("interactive"))
.and_then(|v| v.as_bool())
.unwrap_or(false)
@@ -84,7 +84,7 @@ impl MvpAgent {
/// Ask a GUI client to decide trust for `session_id`'s workspace, then grant
/// + reload on accept. DORMANT no-op unless the client advertised
/// `x.ai/folderTrust.interactive` AND [`folder_trust::prompt_warranted`]
/// `kigi/folderTrust.interactive` AND [`folder_trust::prompt_warranted`]
/// (feature on + untrusted + repo configs present).
///
/// Non-blocking: the session was already created with project servers GATED
@@ -181,7 +181,7 @@ impl MvpAgent {
return;
}
};
let ext_request = acp::ExtRequest::new("x.ai/folder_trust/request", raw_params.into());
let ext_request = acp::ExtRequest::new("kigi/folder_trust/request", raw_params.into());
use agent_client_protocol::Client as _;
let outcome = match tokio::time::timeout(
@@ -374,7 +374,7 @@ mod tests {
fn parse_interactive_trust_capability_present_and_true() {
let mut meta = serde_json::Map::new();
meta.insert(
"x.ai/folderTrust".to_string(),
"kigi/folderTrust".to_string(),
serde_json::json!({ "interactive": true }),
);
let init = init_with_meta(Some(serde_json::Value::Object(meta)));
@@ -391,7 +391,7 @@ mod tests {
fn parse_interactive_trust_capability_false_returns_false() {
let mut meta = serde_json::Map::new();
meta.insert(
"x.ai/folderTrust".to_string(),
"kigi/folderTrust".to_string(),
serde_json::json!({ "interactive": false }),
);
let init = init_with_meta(Some(serde_json::Value::Object(meta)));
@@ -85,7 +85,7 @@ use tokio_util::sync::CancellationToken;
use kigi_paths::AbsPathBuf;
use kigi_workspace::session::git::GitDiscoveryResult;
use kigi_hunk_tracker::HunkTrackerActor;
/// Hard-error message for legacy Direct hub-bind sessions (`x.ai/cloud_server_id`).
/// Hard-error message for legacy Direct hub-bind sessions (`kigi/cloud_server_id`).
pub(crate) const DIRECT_HUB_CLOUD_REMOVED_MSG: &str = "Direct hub cloud removed; use Gateway (envId or existing-workspace attach)";
/// Reject session `_meta` that still requests Direct hub bind (D8).
///
@@ -93,7 +93,7 @@ pub(crate) const DIRECT_HUB_CLOUD_REMOVED_MSG: &str = "Direct hub cloud removed;
pub(crate) fn reject_direct_hub_cloud_meta(
session_meta: Option<&acp::Meta>,
) -> Result<(), acp::Error> {
if session_meta.and_then(|m| m.get("x.ai/cloud_server_id")).is_some() {
if session_meta.and_then(|m| m.get("kigi/cloud_server_id")).is_some() {
return Err(acp::Error::invalid_params().data(DIRECT_HUB_CLOUD_REMOVED_MSG));
}
Ok(())
@@ -147,13 +147,13 @@ impl BridgeAttach {
!matches!(self, Self::NotAttached)
}
}
/// `_meta["x.ai/session"].kind` → [`SessionKind`]; absent/unknown/malformed → `Build`.
/// `_meta["kigi/session"].kind` → [`SessionKind`]; absent/unknown/malformed → `Build`.
fn parse_session_kind(
meta: Option<&acp::Meta>,
) -> crate::session::unified_list::SessionKind {
use crate::session::unified_list::SessionKind;
use serde::Deserialize;
meta.and_then(|m| m.get("x.ai/session"))
meta.and_then(|m| m.get("kigi/session"))
.and_then(|s| s.get("kind"))
.and_then(|k| SessionKind::deserialize(k).ok())
.unwrap_or(SessionKind::Build)
@@ -191,7 +191,7 @@ fn chat_new_session_model_state(
/// `session/new` / `session/load` `_meta` key carrying per-session plugin roots.
pub(crate) const SESSION_PLUGIN_DIRS_META_KEY: &str = "pluginDirs";
/// `initialize` response `_meta` key advertising [`SESSION_PLUGIN_DIRS_META_KEY`] support.
pub(crate) const SESSION_PLUGIN_DIRS_CAPABILITY_KEY: &str = "x.ai/pluginDirs";
pub(crate) const SESSION_PLUGIN_DIRS_CAPABILITY_KEY: &str = "kigi/pluginDirs";
/// Per-session plugin roots from `session/new` / `session/load` `_meta.pluginDirs`,
/// loaded at CliOverride scope (always trusted) into this session's registry only.
/// Paths must be absolute (the SDKs resolve before sending); anything else is
@@ -271,7 +271,7 @@ fn parse_no_replay(meta: Option<&acp::Meta>) -> bool {
meta.and_then(|m| m.get("noReplay")).and_then(|v| v.as_bool()).unwrap_or(false)
}
/// Insert `key`/`value` into a notification's `_meta`, creating the map if absent.
/// Used to stamp `x.ai/leaderClientId` onto replay notifications so the leader can
/// Used to stamp `kigi/leaderClientId` onto replay notifications so the leader can
/// unicast them to the loading client only (see `forward_raw_replay_line`).
fn stamp_meta_value(meta: &mut Option<acp::Meta>, key: &str, value: &serde_json::Value) {
meta.get_or_insert_with(acp::Meta::new).insert(key.to_string(), value.clone());
@@ -284,7 +284,7 @@ fn mark_as_replay(
let obj = meta.get_or_insert_with(acp::Meta::new);
obj.insert("isReplay".to_string(), is_replay);
if let Some(persist) = persist_data {
obj.insert("x.ai/persist".to_string(), persist.clone());
obj.insert("kigi/persist".to_string(), persist.clone());
}
}
/// Resolve a session's REQUESTED auto flag from `_meta`: an explicit `autoMode`
@@ -393,7 +393,7 @@ pub(crate) fn build_prompt_response_meta(
};
serde_json::to_value(meta).expect("PromptResponseMeta is always serializable")
}
/// Typed payload for the `x.ai/settings/update` notification sent to pager
/// Typed payload for the `kigi/settings/update` notification sent to pager
/// clients after remote settings settings are refreshed on `/new`.
///
/// Keeping this as a `#[derive(Serialize)]` struct gives compile-time
@@ -413,13 +413,13 @@ struct SettingsUpdateNotification {
/// Reason why a client is not eligible to use codebase indexing.
///
/// Returned by [`MvpAgent::code_nav_eligibility`] when one of the policy
/// gates fails. Used in `x.ai/code/status` responses and to generate
/// gates fails. Used in `kigi/code/status` responses and to generate
/// clear error messages on code-nav requests from ineligible clients.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodeNavEligibility {
/// Client type is not web (web-only for initial rollout).
ClientNotWeb,
/// Client did not advertise `x.ai/codeNavigation.enabled`.
/// Client did not advertise `kigi/codeNavigation.enabled`.
CapabilityNotAdvertised,
/// `codebase_indexing` feature is disabled in config (or excluded by glob).
DisabledByConfig,
@@ -519,12 +519,12 @@ pub struct MvpAgent {
/// attribution would require threading `clientIdentifier` from `_meta` through
/// every session handler, which is deferred to future work.
client_type: RefCell<ClientType>,
/// Whether the current client advertised `x.ai/codeNavigation.enabled`.
/// Whether the current client advertised `kigi/codeNavigation.enabled`.
/// Updated on every `initialize()` call — same last-client-wins semantics
/// as `client_type`. Using `Cell<bool>` (not `RefCell`) so `.get()` is a
/// plain copy with no borrow that could be held across an await point.
code_nav_enabled: std::cell::Cell<bool>,
/// Whether the current client advertised `x.ai/folderTrust.interactive` (it
/// Whether the current client advertised `kigi/folderTrust.interactive` (it
/// can render the interactive folder-trust prompt). Set on every
/// `initialize()` (last-client-wins, like `code_nav_enabled`); gates the
/// DORMANT agent→client trust round-trip in `new_session`/`load_session`.
@@ -606,14 +606,14 @@ pub struct MvpAgent {
/// Unified sender for all subagent coordinator events.
/// LEADER-SAFE(shared): channel is multi-producer, coordinator drains.
subagent_event_tx: tokio::sync::mpsc::UnboundedSender<
kigi_tools::implementations::grok_build::task::types::SubagentEvent,
kigi_tools::implementations::kigi::task::types::SubagentEvent,
>,
/// Receiver for subagent events. Taken once by `start_subagent_coordinator()`.
/// `None` after the coordinator drain task has been spawned.
subagent_event_rx: RefCell<
Option<
tokio::sync::mpsc::UnboundedReceiver<
kigi_tools::implementations::grok_build::task::types::SubagentEvent,
kigi_tools::implementations::kigi::task::types::SubagentEvent,
>,
>,
>,
@@ -624,7 +624,7 @@ pub struct MvpAgent {
/// Pushed by the `InjectNotification` handler when a turn is active and the
/// notification has `Next` priority. Drained by the session turn loop
/// (`inject_pending_monitor_events`) into a hidden synthetic user message.
monitor_event_buffer: kigi_tools::implementations::grok_build::task::types::MonitorEventBuffer,
monitor_event_buffer: kigi_tools::implementations::kigi::task::types::MonitorEventBuffer,
/// Per-subagent model ID overrides from config.toml `[subagents.models]`.
/// Populated from `SubagentsConfig.models` during `with_models()`.
subagent_model_overrides: std::collections::HashMap<String, String>,
@@ -653,7 +653,7 @@ pub struct MvpAgent {
/// `plugin_registry_handle`.
///
/// Boot-time plugin discovery is deferred past ACP `initialize` (it walks
/// cwd→git root plus user/marketplace dirs and stalled grok-desktop's first
/// cwd→git root plus user/marketplace dirs and stalled kigi-desktop's first
/// `initialize`), so the shared snapshot starts empty. It is built once on
/// the first session-creating call via [`Self::ensure_plugin_registry`];
/// this flag keeps that to a single discovery walk.
@@ -753,8 +753,8 @@ pub(crate) fn inherited_harness_template(
///
/// When a zero-turn switch rebuilds the harness (`did_rebuild`), the handle
/// must adopt the rebuilt harness's agent type. Otherwise the name is left
/// unchanged — compatible stock switches (e.g. `grok-build` →
/// `grok-build-plan`) intentionally preserve the session's original ACP
/// unchanged — compatible stock switches (e.g. `kigi` →
/// `kigi-plan`) intentionally preserve the session's original ACP
/// `agentProfile`.
pub(crate) fn agent_name_after_model_switch(
did_rebuild: bool,
@@ -770,8 +770,8 @@ pub(crate) fn agent_name_after_model_switch(
/// Harness compatibility for zero-turn / mid-turn model switching.
///
/// Two stock (non-strict) agents are interchangeable — they share the
/// default wire format and toolset, so switching e.g. `grok-build` →
/// `grok-build-plan` doesn't require rebuilding the harness and would
/// default wire format and toolset, so switching e.g. `kigi` →
/// `kigi-plan` doesn't require rebuilding the harness and would
/// destroy a client-supplied `_meta.agentProfile` if it did.
///
/// Strict harnesses (`codex`, …) are only compatible with
@@ -927,7 +927,7 @@ fn resolve_inference_idle_timeout_secs(
let remote = remote_settings.and_then(|s| s.inference_idle_timeout_secs);
per_model.or(remote).unwrap_or(600).max(10)
}
/// Parse the client-advertised `x.ai/hunkTracker.mode` string. Case-insensitive
/// Parse the client-advertised `kigi/hunkTracker.mode` string. Case-insensitive
/// and trimmed. Absent/blank/`off`/`disabled` => `None`; unknown => `AllDirty`.
fn resolve_hunk_tracking_mode(
mode_str: Option<&str>,
@@ -1025,7 +1025,7 @@ impl MvpAgent {
/// Dispatches by on-disk method name:
/// - ACP updates (`"session/update"`) → typed `SessionNotification` for correct
/// TUI dispatch (direct dispatch preserves Rust types, not method strings).
/// - xAI updates (`"_x.ai/session/update"`) → `ExtNotification`.
/// - xAI updates (`"_kigi/session/update"`) → `ExtNotification`.
///
/// When `mark_replay` is true, the notification is tagged with
/// `_meta.isReplay: true` so the client knows it's historical data.
@@ -1058,7 +1058,7 @@ impl MvpAgent {
tracing::debug!("replay: skipping JSONL line with no params");
return;
};
let is_xai = method == "_x.ai/session/update";
let is_xai = method == "_kigi/session/update";
if is_xai {
if target_client_id.is_none() && !mark_replay {
if let Ok(owned) = serde_json::value::RawValue::from_string(
@@ -1070,7 +1070,7 @@ impl MvpAgent {
.gateway
.forward_with_completion(
acp::ExtNotification::new(
"x.ai/session/update",
"kigi/session/update",
std::sync::Arc::from(owned),
),
),
@@ -1094,10 +1094,10 @@ impl MvpAgent {
m.insert("isReplay".to_string(), serde_json::json!(true));
}
if let Some(pd) = persist_data {
m.insert("x.ai/persist".to_string(), pd.clone());
m.insert("kigi/persist".to_string(), pd.clone());
}
if let Some(tid) = target_client_id {
m.insert("x.ai/leaderClientId".to_string(), tid.clone());
m.insert("kigi/leaderClientId".to_string(), tid.clone());
}
}
}
@@ -1108,7 +1108,7 @@ impl MvpAgent {
.gateway
.forward_with_completion(
acp::ExtNotification::new(
"x.ai/session/update",
"kigi/session/update",
std::sync::Arc::from(raw_val),
),
),
@@ -1161,7 +1161,7 @@ impl MvpAgent {
mark_as_replay(&mut notification.meta, persist_data);
}
if let Some(tid) = target_client_id {
stamp_meta_value(&mut notification.meta, "x.ai/leaderClientId", tid);
stamp_meta_value(&mut notification.meta, "kigi/leaderClientId", tid);
}
completions.push(self.gateway.forward_with_completion(notification));
}
@@ -1425,7 +1425,7 @@ impl MvpAgent {
.gateway
.forward_with_completion(
acp::ExtNotification::new(
"x.ai/task_completed",
"kigi/task_completed",
params.into(),
),
),
@@ -1507,7 +1507,7 @@ impl MvpAgent {
});
AuthenticateResponse::new().meta(meta)
}
/// Fire-and-forget `x.ai/settings/update` from the current remote snapshot.
/// Fire-and-forget `kigi/settings/update` from the current remote snapshot.
pub(super) fn emit_settings_update_notification(&self) {
let payload = {
let cfg = self.cfg.borrow();
@@ -1528,7 +1528,7 @@ impl MvpAgent {
if let Ok(params) = serde_json::value::to_raw_value(&payload) {
self.gateway
.forward_fire_and_forget(
acp::ExtNotification::new("x.ai/settings/update", params.into()),
acp::ExtNotification::new("kigi/settings/update", params.into()),
);
}
}
@@ -23,12 +23,12 @@ fn args<'a>(
#[test]
fn includes_baseline_keys_without_usage() {
let meta = build_prompt_response_meta(args("sess-1", "prompt-1", 42_000, "grok-4.5"));
let meta = build_prompt_response_meta(args("sess-1", "prompt-1", 42_000, "kigi-4.5"));
assert_eq!(meta["sessionId"], "sess-1");
assert_eq!(meta["requestId"], "prompt-1");
assert_eq!(meta["promptId"], "prompt-1");
assert_eq!(meta["totalTokens"], 42_000);
assert_eq!(meta["modelId"], "grok-4.5");
assert_eq!(meta["modelId"], "kigi-4.5");
// No per-turn keys when usage is absent.
assert!(meta.get("inputTokens").is_none());
assert!(meta.get("outputTokens").is_none());
@@ -46,7 +46,7 @@ fn enriches_meta_with_camelcase_token_keys() {
};
let meta = build_prompt_response_meta(PromptResponseMetaArgs {
last_turn_usage: Some(&usage),
..args("sess-1", "prompt-1", 1_700, "grok-4.5")
..args("sess-1", "prompt-1", 1_700, "kigi-4.5")
});
// Bot's _META_TOKEN_KEY_MAP expects exactly these camelCase keys.
assert_eq!(meta["inputTokens"], 1500);
@@ -12,7 +12,7 @@ impl MvpAgent {
/// Finalize the cloud session replica (fire-and-forget, "Hook 4").
///
/// Marks the session **done** upstream, so this MUST only run on a genuine
/// session end — a terminal/explicit close (`x.ai/session/close`). It must
/// session end — a terminal/explicit close (`kigi/session/close`). It must
/// NOT run on a mere client disconnect or a dead-actor reap: those leave the
/// conversation resumable on disk, and finalizing would wrongly mark a still
/// running/resumable session "done".
@@ -58,7 +58,7 @@ impl MvpAgent {
.clone()
}
/// Close a session in response to an **explicit** terminal close
/// (`x.ai/session/close`). Finalizes the cloud replica (genuine session
/// (`kigi/session/close`). Finalizes the cloud replica (genuine session
/// end), then removes the session terminally as `Completed`.
pub(crate) fn close_session_explicit(&self, id: &acp::SessionId) {
self.finalize_session_replica(id);
@@ -76,7 +76,7 @@ impl MvpAgent {
self.session_live_state.borrow().get(id).copied()
}
/// Roster-delta hook for a terminally removed session. Broadcasts an
/// `x.ai/sessions/changed` notification with the session in `removed` so
/// `kigi/sessions/changed` notification with the session in `removed` so
/// every attached dashboard drops the row promptly. Also
/// records the call site (and the terminal state) for test observability,
/// since the `session_live_state` entry is dropped on removal.
@@ -91,14 +91,14 @@ impl MvpAgent {
self.emit_roster_changed(Vec::new(), vec![id.0.to_string()]);
}
/// Roster-delta hook for a newly-resident / changed session. Broadcasts an
/// `x.ai/sessions/changed` notification with the current entry in
/// `kigi/sessions/changed` notification with the current entry in
/// `upserted` so dashboards add/refresh the row.
pub(crate) fn push_roster_delta_upserted(&self, id: &acp::SessionId) {
if let Some(entry) = self.resident_roster_entry(id) {
self.emit_roster_changed(vec![entry], Vec::new());
}
}
/// Emit an `x.ai/sessions/changed` upsert for a resident session with an
/// Emit an `kigi/sessions/changed` upsert for a resident session with an
/// explicit `activity`, so every attached dashboard reflects a
/// turn-boundary transition (Working / Idle / NeedsInput) *immediately*
/// rather than waiting for the ≤1s roster poll (deltas are emitted
@@ -124,11 +124,11 @@ impl MvpAgent {
self.emit_roster_changed(vec![entry], Vec::new());
}
}
/// Fan an `x.ai/sessions/changed` delta out to every attached client.
/// Fan an `kigi/sessions/changed` delta out to every attached client.
///
/// This is a roster-wide notification (no `sessionId`), so the leader IPC
/// server broadcasts it to all clients rather than routing by session (see
/// the `x.ai/sessions/changed` special-case in `leader/server.rs`).
/// the `kigi/sessions/changed` special-case in `leader/server.rs`).
pub(super) fn emit_roster_changed(
&self,
upserted: Vec<crate::agent::roster::RosterEntry>,
@@ -16,7 +16,7 @@ impl MvpAgent {
};
let agent_ref = LocalRef::new(self);
use crate::agent::subagent::{BlockWaitSlot, is_running, resolve_snapshot};
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentCancelOutcome, SubagentCancelTarget, SubagentEvent,
};
tokio::task::spawn_local({
@@ -208,7 +208,7 @@ impl MvpAgent {
SubagentEvent::DescribeType(request) => {
let agent_ref = agent_ref.clone();
tokio::task::spawn_local(async move {
use kigi_tools::implementations::grok_build::task::types::SubagentDescribeOutcome;
use kigi_tools::implementations::kigi::task::types::SubagentDescribeOutcome;
let this = agent_ref.get();
let outcome = match this
.try_build_subagent_spawn_context(&request.parent_session_id)
@@ -224,7 +224,7 @@ fn allocate_turn_number_advances_counter() {
/// With no overrides and model_agent_type = None, the default agent is used.
#[test]
#[serial_test::serial]
fn resolve_agent_definition_defaults_to_grok_build() {
fn resolve_agent_definition_defaults_to_kigi() {
let prev = std::env::var("KIGI_AGENT").ok();
unsafe {
std::env::remove_var("KIGI_AGENT");
@@ -243,7 +243,7 @@ fn resolve_agent_definition_defaults_to_grok_build() {
}
}
/// When model_agent_type = Some("codex"), the codex agent is selected even
/// though the default chain would return grok-build.
/// though the default chain would return kigi.
#[test]
#[serial_test::serial]
fn resolve_agent_definition_model_agent_type_overrides_default() {
@@ -320,13 +320,13 @@ fn resolve_agent_definition_acp_profile_wins_when_model_agent_type_is_default()
}
}
/// Regression: after `DEFAULT_AGENT_TYPE` flipped to
/// `grok-build-plan`, models in the catalog that still declare
/// `agent_type = "grok-build"` explicitly must NOT preempt an ACP
/// profile. Any value in the `grok-build*` family is the stock harness
/// `kigi-plan`, models in the catalog that still declare
/// `agent_type = "kigi"` explicitly must NOT preempt an ACP
/// profile. Any value in the `kigi*` family is the stock harness
/// with no strict requirement.
#[test]
#[serial_test::serial]
fn resolve_agent_definition_acp_profile_wins_for_explicit_grok_build_family() {
fn resolve_agent_definition_acp_profile_wins_for_explicit_kigi_family() {
let prev = std::env::var("KIGI_AGENT").ok();
unsafe {
std::env::remove_var("KIGI_AGENT");
@@ -337,7 +337,7 @@ fn resolve_agent_definition_acp_profile_wins_for_explicit_grok_build_family() {
"Custom devbox profile", }
))
.expect("agent definition must parse");
for family_variant in ["grok-build", "grok-build-plan", "grok-build-concise"] {
for family_variant in ["kigi", "kigi-plan", "kigi-concise"] {
let def = MvpAgent::resolve_agent_definition(
tmp.path(),
None,
@@ -347,7 +347,7 @@ fn resolve_agent_definition_acp_profile_wins_for_explicit_grok_build_family() {
);
assert_eq!(
def.name, "custom-devbox-profile",
"ACP profile must win for grok-build family variant `{family_variant}`"
"ACP profile must win for kigi family variant `{family_variant}`"
);
}
if let Some(v) = prev {
@@ -543,62 +543,62 @@ fn enqueue_replace_system_prompt_override_noop_when_absent_or_empty() {
);
}
/// Regression for the web-client `_meta.agentProfile` -> `set_session_model`
/// flow: a zero-turn switch from `grok-build` (a client profile name) to
/// `grok-build-plan` (the default model agent_type) must be
/// flow: a zero-turn switch from `kigi` (a client profile name) to
/// `kigi-plan` (the default model agent_type) must be
/// treated as compatible so the harness rebuild is skipped and the
/// custom prompt body is preserved.
#[test]
fn harnesses_are_compatible_for_stock_family_pairs() {
assert!(harnesses_are_compatible("grok-build", "grok-build-plan"));
assert!(harnesses_are_compatible("grok-build-plan", "grok-build"));
assert!(harnesses_are_compatible("grok-build", "grok-build"));
assert!(harnesses_are_compatible("kigi", "kigi-plan"));
assert!(harnesses_are_compatible("kigi-plan", "kigi"));
assert!(harnesses_are_compatible("kigi", "kigi"));
assert!(harnesses_are_compatible(
"grok-build-concise",
"grok-build-plan"
"kigi-concise",
"kigi-plan"
));
assert!(harnesses_are_compatible(
"remote-sidebar",
"grok-build-plan"
"kigi-plan"
));
}
#[test]
fn harnesses_are_compatible_rejects_strict_mismatches() {
assert!(harnesses_are_compatible("codex", "codex"));
assert!(!harnesses_are_compatible("grok-build-plan", "codex"));
assert!(!harnesses_are_compatible("kigi-plan", "codex"));
}
#[test]
fn explicit_agent_type_wins_over_session_default() {
assert_eq!(
resolve_required_agent_type(Some("cursor"), "grok-build-plan"),
resolve_required_agent_type(Some("cursor"), "kigi-plan"),
"cursor"
);
}
#[test]
fn null_agent_type_falls_back_to_session_default_grok_build_plan() {
fn null_agent_type_falls_back_to_session_default_kigi_plan() {
assert_eq!(
resolve_required_agent_type(None, "grok-build-plan"),
"grok-build-plan"
resolve_required_agent_type(None, "kigi-plan"),
"kigi-plan"
);
}
#[test]
fn null_agent_type_falls_back_to_session_default_grok_build() {
fn null_agent_type_falls_back_to_session_default_kigi() {
assert_eq!(
resolve_required_agent_type(None, "grok-build"),
"grok-build"
resolve_required_agent_type(None, "kigi"),
"kigi"
);
}
#[test]
fn null_agent_type_returns_to_session_default_after_cursor_switch() {
let session_default = "grok-build-plan";
let session_default = "kigi-plan";
let required_after_null = resolve_required_agent_type(None, session_default);
assert_eq!(required_after_null, "grok-build-plan");
assert_eq!(required_after_null, "kigi-plan");
assert_ne!(required_after_null, "cursor");
}
/// Compatible stock switches (no rebuild) must NOT mutate `agent_name`,
/// preserving the session's original ACP `agentProfile`.
#[test]
fn agent_name_unchanged_without_harness_rebuild() {
let unchanged = agent_name_after_model_switch(false, "grok-build-plan", "remote-sidebar");
let unchanged = agent_name_after_model_switch(false, "kigi-plan", "remote-sidebar");
assert_eq!(
unchanged, "remote-sidebar",
"a compatible stock switch must preserve the original agent profile name"
@@ -648,7 +648,7 @@ async fn file_toolset_override_e2e_to_finalized_toolset() {
web_search_config: kigi_tools::implementations::web_search::WebSearchConfig::default(),
web_fetch_config: Default::default(),
lsp: None,
app_builder_deployer_config: kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig::default(),
app_builder_deployer_config: kigi_tools::implementations::kigi::deploy_app::AppBuilderDeployerConfig::default(),
api_key_provider: None,
attribution_callback: None,
system_reminder_tag: kigi_tools::reminders::DEFAULT_REMINDER_TAG,
@@ -741,7 +741,7 @@ fn make_test_handle(
force_compact: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
permission_handle: kigi_workspace::permission::PermissionHandle::allow_all(),
attribution_callback: None,
agent_name: "grok-build".to_string(),
agent_name: "kigi".to_string(),
session_default_agent_profile: None,
allowed_subagent_types: None,
hook_registry: None,
@@ -758,7 +758,7 @@ async fn lookup_session_model_returns_per_session_model() {
let sid_b = acp::SessionId::new("sess-b");
let default_model = acp::ModelId::new("default-model");
let sessions: HashMap<acp::SessionId, crate::session::SessionHandle> = [
(sid_a.clone(), make_test_handle("grok-3-fast", false, None)),
(sid_a.clone(), make_test_handle("kigi-3-fast", false, None)),
(sid_b.clone(), make_test_handle("codex-mini", false, None)),
]
.into();
@@ -766,7 +766,7 @@ async fn lookup_session_model_returns_per_session_model() {
lookup_session_model(&sessions, Some(&sid_a), &default_model)
.0
.as_ref(),
"grok-3-fast"
"kigi-3-fast"
);
assert_eq!(
lookup_session_model(&sessions, Some(&sid_b), &default_model)
@@ -778,13 +778,13 @@ async fn lookup_session_model_returns_per_session_model() {
/// lookup_session_model falls back to the default when session_id is None.
#[tokio::test]
async fn lookup_session_model_fallback_no_session() {
let default_model = acp::ModelId::new("grok-3");
let default_model = acp::ModelId::new("kigi-3");
let sessions: HashMap<acp::SessionId, crate::session::SessionHandle> = HashMap::new();
assert_eq!(
lookup_session_model(&sessions, None, &default_model)
.0
.as_ref(),
"grok-3"
"kigi-3"
);
}
/// Mutating session A's model_id via the handle does not affect session B.
@@ -794,8 +794,8 @@ async fn set_session_model_does_not_cross_contaminate() {
let sid_b = acp::SessionId::new("sess-b");
let default_model = acp::ModelId::new("default");
let mut sessions: HashMap<acp::SessionId, crate::session::SessionHandle> = [
(sid_a.clone(), make_test_handle("grok-3", false, None)),
(sid_b.clone(), make_test_handle("grok-3", false, None)),
(sid_a.clone(), make_test_handle("kigi-3", false, None)),
(sid_b.clone(), make_test_handle("kigi-3", false, None)),
]
.into();
sessions.get_mut(&sid_a).unwrap().model_id = acp::ModelId::new("codex-mini");
@@ -809,7 +809,7 @@ async fn set_session_model_does_not_cross_contaminate() {
lookup_session_model(&sessions, Some(&sid_b), &default_model)
.0
.as_ref(),
"grok-3",
"kigi-3",
"Session B's model must not be affected by session A's model change"
);
}
@@ -903,15 +903,15 @@ async fn yolo_toggle_scoped_by_client_identifier() {
let mut sessions: HashMap<acp::SessionId, crate::session::SessionHandle> = [
(
sid_tui.clone(),
make_test_handle("grok-3", false, Some("grok-tui")),
make_test_handle("kigi-3", false, Some("kigi-tui")),
),
(
sid_vscode.clone(),
make_test_handle("grok-3", false, Some("grok-code-extension")),
make_test_handle("kigi-3", false, Some("kigi-code-extension")),
),
]
.into();
let updated = apply_yolo_mode_to_matching_sessions(&mut sessions, Some("grok-tui"), true);
let updated = apply_yolo_mode_to_matching_sessions(&mut sessions, Some("kigi-tui"), true);
assert_eq!(updated, 1, "exactly one matching session should be updated");
assert!(
sessions[&sid_tui].yolo_mode,
@@ -931,15 +931,15 @@ async fn yolo_toggle_can_disable_session_started_with_yolo_enabled() {
let mut sessions: HashMap<acp::SessionId, crate::session::SessionHandle> = [
(
sid_tui.clone(),
make_test_handle("grok-3", true, Some("grok-tui")),
make_test_handle("kigi-3", true, Some("kigi-tui")),
),
(
sid_other.clone(),
make_test_handle("grok-3", true, Some("grok-code-extension")),
make_test_handle("kigi-3", true, Some("kigi-code-extension")),
),
]
.into();
let updated = apply_yolo_mode_to_matching_sessions(&mut sessions, Some("grok-tui"), false);
let updated = apply_yolo_mode_to_matching_sessions(&mut sessions, Some("kigi-tui"), false);
assert_eq!(updated, 1, "only the sender's session should be updated");
assert!(
!sessions[&sid_tui].yolo_mode,
@@ -1030,7 +1030,7 @@ async fn drain_respects_deadline() {
fn parse_code_nav_capability_present_and_true() {
let mut meta = serde_json::Map::new();
meta.insert(
"x.ai/codeNavigation".to_string(),
"kigi/codeNavigation".to_string(),
serde_json::json!({ "enabled" : true }),
);
let init = acp::InitializeRequest::new(acp::ProtocolVersion::V1).client_capabilities(
@@ -1054,7 +1054,7 @@ fn parse_code_nav_capability_absent_returns_false() {
fn parse_code_nav_capability_false_returns_false() {
let mut meta = serde_json::Map::new();
meta.insert(
"x.ai/codeNavigation".to_string(),
"kigi/codeNavigation".to_string(),
serde_json::json!({ "enabled" : false }),
);
let init = acp::InitializeRequest::new(acp::ProtocolVersion::V1).client_capabilities(
@@ -1074,18 +1074,18 @@ fn parse_code_nav_capability_false_returns_false() {
#[tokio::test]
async fn test_per_session_code_nav_isolation() {
let web_handle = {
let mut h = make_test_handle("model", false, Some("grok-web"));
let mut h = make_test_handle("model", false, Some("kigi-web"));
h.code_nav_enabled = true;
h
};
let tui_handle = {
let mut h = make_test_handle("model", false, Some("grok-tui"));
let mut h = make_test_handle("model", false, Some("kigi-tui"));
h.code_nav_enabled = false;
h
};
let check = |handle: &crate::session::SessionHandle| {
let ct = crate::http::client_type_from_origin(handle.origin_client.as_ref());
if !matches!(ct, ClientType::GrokWeb) {
if !matches!(ct, ClientType::KigiWeb) {
return Err(CodeNavEligibility::ClientNotWeb);
}
if !handle.code_nav_enabled {
@@ -1322,7 +1322,7 @@ async fn resident_activity_reports_needs_input_when_pending() {
use crate::agent::roster::RosterActivity;
let agent = build_minimal_agent_for_tests();
let sid = acp::SessionId::new("sess-pending");
let handle = make_test_handle("grok-3", false, None);
let handle = make_test_handle("kigi-3", false, None);
let pending = handle.pending_interactions.clone();
let prompt_id = handle.current_prompt_id.clone();
agent.sessions.borrow_mut().insert(sid.clone(), handle);
@@ -1339,7 +1339,7 @@ async fn resident_activity_reports_needs_input_when_pending() {
pending.lock().unwrap().clear();
assert_eq!(agent.resident_activity(&sid), RosterActivity::Working);
}
/// Drain the agent gateway, returning the first `x.ai/sessions/changed`
/// Drain the agent gateway, returning the first `kigi/sessions/changed`
/// payload that carries an upserted entry (ignoring any unrelated
/// notifications, which parse into an empty `RosterChanged`).
fn drain_roster_changed(
@@ -1362,7 +1362,7 @@ fn drain_roster_changed(
found
}
/// A turn-boundary activity delta (`push_roster_activity_delta`) broadcasts
/// an `x.ai/sessions/changed` upsert carrying the *overridden* activity, so
/// an `kigi/sessions/changed` upsert carrying the *overridden* activity, so
/// every attached dashboard reflects Working/Idle immediately instead of
/// waiting for the ≤1s roster poll (turn-start/turn-end). The
/// override matters because at turn-start the actor has not yet published
@@ -1384,7 +1384,7 @@ async fn push_roster_activity_delta_broadcasts_overridden_activity() {
agent
.sessions
.borrow_mut()
.insert(sid.clone(), make_test_handle("grok-3", false, None));
.insert(sid.clone(), make_test_handle("kigi-3", false, None));
agent.push_roster_activity_delta(&sid, RosterActivity::Working);
let changed = drain_roster_changed(&mut rx).expect("turn-start delta emitted");
assert_eq!(changed.upserted.len(), 1);
@@ -1427,7 +1427,7 @@ fn check_nav_eligibility_from_sessions(
return Err(CodeNavEligibility::SessionRequired);
};
let ct = crate::http::client_type_from_origin(handle.origin_client.as_ref());
if !matches!(ct, ClientType::GrokWeb) {
if !matches!(ct, ClientType::KigiWeb) {
return Err(CodeNavEligibility::ClientNotWeb);
}
if !handle.code_nav_enabled {
@@ -1442,7 +1442,7 @@ fn check_nav_eligibility_from_sessions(
#[tokio::test]
async fn test_web_session_with_capability_is_eligible() {
let sid = acp::SessionId::new("sess-web");
let mut handle = make_test_handle("model", false, Some("grok-web"));
let mut handle = make_test_handle("model", false, Some("kigi-web"));
handle.code_nav_enabled = true;
let sessions = [(sid.clone(), handle)].into();
assert!(
@@ -1454,7 +1454,7 @@ async fn test_web_session_with_capability_is_eligible() {
#[tokio::test]
async fn test_tui_session_is_rejected() {
let sid = acp::SessionId::new("sess-tui");
let mut handle = make_test_handle("model", false, Some("grok-tui"));
let mut handle = make_test_handle("model", false, Some("kigi-tui"));
handle.code_nav_enabled = true;
let sessions = [(sid.clone(), handle)].into();
assert_eq!(
@@ -1467,7 +1467,7 @@ async fn test_tui_session_is_rejected() {
#[tokio::test]
async fn test_web_session_without_capability_is_rejected() {
let sid = acp::SessionId::new("sess-web-no-cap");
let mut handle = make_test_handle("model", false, Some("grok-web"));
let mut handle = make_test_handle("model", false, Some("kigi-web"));
handle.code_nav_enabled = false;
let sessions = [(sid.clone(), handle)].into();
assert_eq!(
@@ -1482,9 +1482,9 @@ async fn test_web_session_without_capability_is_rejected() {
async fn test_leader_mode_two_sessions_stay_isolated() {
let web_sid = acp::SessionId::new("web");
let tui_sid = acp::SessionId::new("tui");
let mut web_handle = make_test_handle("model", false, Some("grok-web"));
let mut web_handle = make_test_handle("model", false, Some("kigi-web"));
web_handle.code_nav_enabled = true;
let mut tui_handle = make_test_handle("model", false, Some("grok-tui"));
let mut tui_handle = make_test_handle("model", false, Some("kigi-tui"));
tui_handle.code_nav_enabled = false;
let sessions = [(web_sid.clone(), web_handle), (tui_sid.clone(), tui_handle)].into();
assert!(
@@ -1505,7 +1505,7 @@ async fn test_leader_mode_two_sessions_stay_isolated() {
#[tokio::test]
async fn test_unknown_session_id_returns_session_required() {
let known_sid = acp::SessionId::new("known");
let mut known_handle = make_test_handle("model", false, Some("grok-web"));
let mut known_handle = make_test_handle("model", false, Some("kigi-web"));
known_handle.code_nav_enabled = true;
let sessions = [(known_sid.clone(), known_handle)].into();
let stale_sid = acp::SessionId::new("stale-or-evicted");
@@ -1567,7 +1567,7 @@ mod eligibility_gates {
code_nav_enabled: bool,
indexing_enabled: bool,
) -> Result<(), CodeNavEligibility> {
if !matches!(client_type, ClientType::GrokWeb) {
if !matches!(client_type, ClientType::KigiWeb) {
return Err(CodeNavEligibility::ClientNotWeb);
}
if !code_nav_enabled {
@@ -1588,27 +1588,27 @@ mod eligibility_gates {
#[test]
fn tui_client_rejected() {
assert_eq!(
check_gates(ClientType::GrokTUI, true, true),
check_gates(ClientType::KigiTUI, true, true),
Err(CodeNavEligibility::ClientNotWeb)
);
}
#[test]
fn web_client_no_capability_rejected() {
assert_eq!(
check_gates(ClientType::GrokWeb, false, true),
check_gates(ClientType::KigiWeb, false, true),
Err(CodeNavEligibility::CapabilityNotAdvertised)
);
}
#[test]
fn web_client_with_capability_config_disabled_rejected() {
assert_eq!(
check_gates(ClientType::GrokWeb, true, false),
check_gates(ClientType::KigiWeb, true, false),
Err(CodeNavEligibility::DisabledByConfig)
);
}
#[test]
fn web_client_with_capability_and_config_passes_first_three_gates() {
assert!(check_gates(ClientType::GrokWeb, true, true).is_ok());
assert!(check_gates(ClientType::KigiWeb, true, true).is_ok());
}
}
#[test]
@@ -1672,12 +1672,12 @@ fn write_updates(dir: &std::path::Path, lines: &[&str]) -> PathBuf {
}
fn bg_line(task_id: &str) -> String {
format!(
r#"{{"timestamp":1,"method":"_x.ai/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"task_backgrounded","task_id":"{task_id}","command":"sleep 99","cwd":"/tmp"}}}}}}"#
r#"{{"timestamp":1,"method":"_kigi/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"task_backgrounded","task_id":"{task_id}","command":"sleep 99","cwd":"/tmp"}}}}}}"#
)
}
fn completed_line(task_id: &str) -> String {
format!(
r#"{{"timestamp":2,"method":"_x.ai/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"task_completed","task_snapshot":{{"task_id":"{task_id}","completed":true}}}}}}}}"#
r#"{{"timestamp":2,"method":"_kigi/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"task_completed","task_snapshot":{{"task_id":"{task_id}","completed":true}}}}}}}}"#
)
}
fn orphaned_ids(tasks: &[OrphanedTask]) -> std::collections::HashSet<&str> {
@@ -1751,7 +1751,7 @@ fn orphaned_tasks_skips_malformed_lines() {
fn orphaned_tasks_ignores_unrelated_updates() {
let tmp = tempfile::tempdir().unwrap();
let bg = bg_line("t1");
let unrelated = r#"{"timestamp":1,"method":"_x.ai/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"auto_compact_started","percentage":80}}}"#;
let unrelated = r#"{"timestamp":1,"method":"_kigi/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"auto_compact_started","percentage":80}}}"#;
let path = write_updates(tmp.path(), &[&bg, unrelated]);
let result = MvpAgent::find_orphaned_background_tasks(&Some(path));
assert_eq!(result.len(), 1);
@@ -1761,7 +1761,7 @@ fn orphaned_tasks_filters_rewind_dead_branches() {
let tmp = tempfile::tempdir().unwrap();
let user_msg = r#"{"timestamp":0,"method":"session/update","params":{"sessionId":"s","update":{"sessionUpdate":"user_message_chunk","content":{"type":"text","text":"hello"}}}}"#;
let bg_before_rewind = bg_line("t-dead");
let rewind = r#"{"timestamp":3,"method":"_x.ai/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"rewind_marker","target_prompt_index":0,"created_at":"2025-01-01T00:00:00Z"}}}"#;
let rewind = r#"{"timestamp":3,"method":"_kigi/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"rewind_marker","target_prompt_index":0,"created_at":"2025-01-01T00:00:00Z"}}}"#;
let user_msg2 = r#"{"timestamp":4,"method":"session/update","params":{"sessionId":"s","update":{"sessionUpdate":"user_message_chunk","content":{"type":"text","text":"retry"}}}}"#;
let bg_after_rewind = bg_line("t-alive");
let path = write_updates(
@@ -1804,7 +1804,7 @@ fn on_demand_enabled_from_remote_settings() {
async fn auth_type_session_based_no_current_returns_session_token() {
for method_id in [
crate::agent::auth_method::CACHED_TOKEN_AUTH_METHOD_ID,
crate::agent::auth_method::KIGI_COM_METHOD_ID,
crate::agent::auth_method::KIMI_CODE_METHOD_ID,
] {
let agent = build_minimal_agent_for_tests();
agent.set_auth_method(acp::AuthMethodId::new(method_id));
@@ -1848,7 +1848,7 @@ async fn auth_type_session_based_with_current_returns_session_token() {
use crate::auth::KimiAuth;
let agent = build_minimal_agent_for_tests();
agent.set_auth_method(acp::AuthMethodId::new(
crate::agent::auth_method::KIGI_COM_METHOD_ID,
crate::agent::auth_method::KIMI_CODE_METHOD_ID,
));
agent.auth_manager.hot_swap(KimiAuth::test_default());
assert!(agent.auth_manager.current().is_some());
@@ -1899,12 +1899,12 @@ async fn cached_token_fallthrough_prefers_api_key_for_deployment_key() {
);
}
/// No advertiseable credentials at all (no env key, no kill switch): the user
/// genuinely needs to log in, so the fallthrough is interactive `grok.com`.
/// genuinely needs to log in, so the fallthrough is interactive `kimi-code`.
#[tokio::test(flavor = "current_thread")]
#[serial_test::serial]
async fn cached_token_fallthrough_falls_to_grok_com_without_credentials() {
async fn cached_token_fallthrough_falls_to_kigi_com_without_credentials() {
use crate::agent::auth_method::{
KIGI_COM_METHOD_ID, LEGACY_XAI_API_KEY_ENV_VAR, XAI_API_KEY_ENV_VAR,
KIMI_CODE_METHOD_ID, LEGACY_XAI_API_KEY_ENV_VAR, XAI_API_KEY_ENV_VAR,
};
use kigi_test_support::EnvGuard;
let _lockdown = EnvGuard::unset("KIGI_DISABLE_API_KEY_AUTH");
@@ -1913,8 +1913,8 @@ async fn cached_token_fallthrough_falls_to_grok_com_without_credentials() {
let agent = build_minimal_agent_for_tests();
assert_eq!(
agent.cached_token_fallthrough_method_id().0.as_ref(),
KIGI_COM_METHOD_ID,
"no API-key creds and no kill switch -> interactive grok.com login",
KIMI_CODE_METHOD_ID,
"no API-key creds and no kill switch -> interactive kimi.com login",
);
}
/// `parse_session_kind` routes `session/load` to the gateway Chat path vs. the
@@ -1926,22 +1926,22 @@ fn parse_session_kind_matrix() {
let cases: &[(&str, serde_json::Value, SessionKind)] = &[
(
"chat",
json!({ "x.ai/session" : { "kind" : "chat" } }),
json!({ "kigi/session" : { "kind" : "chat" } }),
SessionKind::Chat,
),
(
"build",
json!({ "x.ai/session" : { "kind" : "build" } }),
json!({ "kigi/session" : { "kind" : "build" } }),
SessionKind::Build,
),
(
"chat_malformed_sibling",
json!({ "x.ai/session" : { "kind" : "chat", "facets" : "not-a-map" } }),
json!({ "kigi/session" : { "kind" : "chat", "facets" : "not-a-map" } }),
SessionKind::Chat,
),
(
"unknown_kind",
json!({ "x.ai/session" : { "kind" : "frob" } }),
json!({ "kigi/session" : { "kind" : "frob" } }),
SessionKind::Build,
),
("absent", json!({}), SessionKind::Build),
@@ -1954,9 +1954,9 @@ fn parse_session_kind_matrix() {
#[test]
fn chat_initial_model_matrix() {
let cases: &[(&str, bool, Option<&str>, Option<&str>)] = &[
("chat_with_model", true, Some("grok-4.5"), Some("grok-4.5")),
("chat_with_model", true, Some("kigi-4.5"), Some("kigi-4.5")),
("chat_without_model", true, None, None),
("build_with_model", false, Some("grok-4.5"), None),
("build_with_model", false, Some("kigi-4.5"), None),
("build_without_model", false, None, None),
];
for (label, is_chat_kind, custom_model_id, expected) in cases {
@@ -1983,27 +1983,27 @@ fn chat_new_session_model_state_matrix() {
let cases: &[(&str, acp::SessionModelState, Option<&str>, &str)] = &[
(
"requested_in_catalog",
state_with("auto", &["auto", "grok-4"]),
Some("grok-4"),
"grok-4",
state_with("auto", &["auto", "kigi-4"]),
Some("kigi-4"),
"kigi-4",
),
(
"no_request_keeps_catalog_default",
state_with("auto", &["auto", "grok-4"]),
state_with("auto", &["auto", "kigi-4"]),
None,
"auto",
),
(
"requested_not_in_catalog",
state_with("auto", &["auto"]),
Some("grok-4.5"),
"grok-4.5",
Some("kigi-4.5"),
"kigi-4.5",
),
(
"requested_with_empty_catalog",
state_with("", &[]),
Some("grok-4"),
"grok-4",
Some("kigi-4"),
"kigi-4",
),
];
for (label, state, requested, expected) in cases {
@@ -2069,7 +2069,7 @@ fn ext_method_rewind_uses_local_dispatch_without_bridge() {
let params = serde_json::json!({ "sessionId" : "sess-local" });
let err = agent
.ext_method(acp::ExtRequest::new(
"x.ai/rewind/points",
"kigi/rewind/points",
std::sync::Arc::from(serde_json::value::to_raw_value(&params).unwrap()),
))
.await
@@ -2116,7 +2116,7 @@ fn make_live_session_handle(
tokio::sync::mpsc::UnboundedReceiver<TestSessionCommand>,
) {
let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
let mut handle = make_test_handle("test-model", false, Some("grok-tui"));
let mut handle = make_test_handle("test-model", false, Some("kigi-tui"));
handle.cmd_tx = cmd_tx.clone();
handle.info = crate::session::info::Info {
id: sid.clone(),
@@ -2149,14 +2149,14 @@ fn spawn_fake_actor(
});
observed_rx
}
/// Drive `x.ai/internal/evict_sessions` through the real `ext_notification`
/// Drive `kigi/internal/evict_sessions` through the real `ext_notification`
/// handler path (not the internal helper) — matches how the leader server
/// signals a client disconnect.
async fn drive_disconnect(agent: &MvpAgent, sid: &acp::SessionId) {
drive_disconnect_many(agent, &[sid]).await;
}
/// Like `drive_disconnect`, but evicts several sessions in a single
/// `x.ai/internal/evict_sessions` notification — the realistic shape of a
/// `kigi/internal/evict_sessions` notification — the realistic shape of a
/// real client disconnect, and the path that exercises `handle_evict_sessions`'
/// concurrent `join_all` check pass followed by the sequential act pass.
async fn drive_disconnect_many(agent: &MvpAgent, sids: &[&acp::SessionId]) {
@@ -2166,13 +2166,13 @@ async fn drive_disconnect_many(agent: &MvpAgent, sids: &[&acp::SessionId]) {
let raw = serde_json::value::to_raw_value(&params).unwrap();
agent
.ext_notification(acp::ExtNotification::new(
"x.ai/internal/evict_sessions",
"kigi/internal/evict_sessions",
raw.into(),
))
.await
.expect("evict_sessions notification must be handled");
}
/// Drive `x.ai/session/close` through the real `ext_method` dispatch
/// Drive `kigi/session/close` through the real `ext_method` dispatch
/// (`ext_method` → `handlers::session::handle` → `handle_session_close`),
/// exercising the exact production path that finalizes the replica.
async fn drive_close(agent: &MvpAgent, session_id: &str) -> Result<acp::ExtResponse, acp::Error> {
@@ -2181,7 +2181,7 @@ async fn drive_close(agent: &MvpAgent, session_id: &str) -> Result<acp::ExtRespo
let raw = serde_json::value::to_raw_value(&params).unwrap();
agent
.ext_method(acp::ExtRequest::new(
"x.ai/session/close",
"kigi/session/close",
std::sync::Arc::from(raw),
))
.await
@@ -2423,7 +2423,7 @@ fn disconnect_keeps_resident_when_plan_approval_parked() {
);
});
}
/// Mixed batch in a *single* `x.ai/internal/evict_sessions` notification —
/// Mixed batch in a *single* `kigi/internal/evict_sessions` notification —
/// the realistic disconnect shape and the path that exercises
/// `handle_evict_sessions`' `join_all` two-pass (concurrent `IsBusy` checks,
/// then sequential act). One session's actor reports busy (→ kept resident,
@@ -2512,7 +2512,7 @@ fn session_live_state_map_is_bounded_across_cycles() {
});
}
/// Finalize fires on a genuine terminal close — driven through the **real**
/// `x.ai/session/close` dispatch (`ext_method` → `handle_session_close`),
/// `kigi/session/close` dispatch (`ext_method` → `handle_session_close`),
/// not the internal helper. Proves finalize was *moved* (not removed) and
/// guards the handler's `existed` gate. (Finalize assertion is
/// invocation-level; see note in `finalize_session_replica`.)
@@ -2663,7 +2663,7 @@ fn reload_after_terminal_removal_starts_clean() {
}
/// Build an agent whose gateway is wired to a live receiver, so a test can
/// observe (and answer) agent→client reverse-requests like the dormant
/// `x.ai/folder_trust/request` round-trip.
/// `kigi/folder_trust/request` round-trip.
fn build_agent_with_gateway_rx() -> (
MvpAgent,
tokio::sync::mpsc::UnboundedReceiver<kigi_acp_lib::AcpClientMessage>,
@@ -2698,7 +2698,7 @@ fn folder_trust_on() -> crate::util::config::RemoteSettings {
..Default::default()
}
}
/// Pull the next `x.ai/folder_trust/request` reverse-request off the gateway and
/// Pull the next `kigi/folder_trust/request` reverse-request off the gateway and
/// answer it with `outcome`. Returns the request's decoded params.
async fn answer_folder_trust_request(
gw_rx: &mut tokio::sync::mpsc::UnboundedReceiver<kigi_acp_lib::AcpClientMessage>,
@@ -2711,7 +2711,7 @@ async fn answer_folder_trust_request(
let kigi_acp_lib::AcpClientMessage::ExtMethod(args) = msg else {
panic!("expected an ext_method reverse-request, got a different message");
};
assert_eq!(args.request.method.as_ref(), "x.ai/folder_trust/request");
assert_eq!(args.request.method.as_ref(), "kigi/folder_trust/request");
let params: serde_json::Value = serde_json::from_str(args.request.params.get()).unwrap();
let resp: acp::ExtResponse = acp::ExtResponse::new(std::sync::Arc::from(
serde_json::value::to_raw_value(&serde_json::json!({ "outcome" : outcome })).unwrap(),
@@ -3142,21 +3142,21 @@ mod direct_hub_cloud_removed {
}
#[test]
fn cloud_server_id_meta_is_hard_error() {
let meta = serde_json::json!({ "x.ai/cloud_server_id" : "srv-123" });
let meta = serde_json::json!({ "kigi/cloud_server_id" : "srv-123" });
let err = reject_direct_hub_cloud_meta(meta.as_object()).expect_err("must reject");
assert_direct_hub_error(err);
}
#[test]
fn cloud_server_id_null_still_present_is_hard_error() {
let meta = serde_json::json!({ "x.ai/cloud_server_id" : null });
let meta = serde_json::json!({ "kigi/cloud_server_id" : null });
let err = reject_direct_hub_cloud_meta(meta.as_object()).expect_err("must reject");
assert_direct_hub_error(err);
}
#[test]
fn cloud_server_id_with_gateway_meta_still_hard_error() {
let meta = serde_json::json!(
{ "x.ai/cloud_server_id" : "srv-legacy", "envId" : "env-1",
"x.ai/cloud_existing_workspace" : { "server_id" : "ws-1", "cwd" :
{ "kigi/cloud_server_id" : "srv-legacy", "envId" : "env-1",
"kigi/cloud_existing_workspace" : { "server_id" : "ws-1", "cwd" :
"/workspace" } }
);
let err = reject_direct_hub_cloud_meta(meta.as_object()).expect_err("Direct stamp wins");
@@ -3173,7 +3173,7 @@ mod direct_hub_cloud_removed {
assert!(
reject_direct_hub_cloud_meta(
serde_json::json!({
"x.ai/cloud_existing_workspace" : { "server_id" : "ws-1", "cwd" :
"kigi/cloud_existing_workspace" : { "server_id" : "ws-1", "cwd" :
"/workspace" } })
.as_object()
)
@@ -3212,7 +3212,7 @@ mod soft_default_settings_emit {
let kigi_acp_lib::AcpClientMessage::ExtNotification(args) = msg else {
panic!("expected ExtNotification, got {msg:?}");
};
assert_eq!(args.request.method.as_ref(), "x.ai/settings/update");
assert_eq!(args.request.method.as_ref(), "kigi/settings/update");
let params: serde_json::Value =
serde_json::from_str(args.request.params.get()).expect("parse params");
assert_eq!(
@@ -5,8 +5,8 @@
//! actors) plus recently-touched on-disk (`Dormant`) sessions. Clients read it
//! two ways:
//!
//! - request/response `x.ai/sessions/list` → `{ "sessions": [RosterEntry, …] }`
//! - broadcast notification `x.ai/sessions/changed` →
//! - request/response `kigi/sessions/list` → `{ "sessions": [RosterEntry, …] }`
//! - broadcast notification `kigi/sessions/changed` →
//! `{ "upserted": [RosterEntry, …], "removed": ["sess-abc", …] }`
//!
//! The wire shape is intentionally small and current-state only — no event
@@ -76,13 +76,13 @@ pub struct RosterEntry {
pub origin: RosterOrigin,
}
/// Response payload for `x.ai/sessions/list`.
/// Response payload for `kigi/sessions/list`.
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct RosterListResponse {
pub sessions: Vec<RosterEntry>,
}
/// Params payload for the `x.ai/sessions/changed` broadcast notification.
/// Params payload for the `kigi/sessions/changed` broadcast notification.
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct RosterChanged {
#[serde(default)]
@@ -92,8 +92,8 @@ pub struct RosterChanged {
}
/// JSON-RPC method names for the roster API.
pub const SESSIONS_LIST_METHOD: &str = "x.ai/sessions/list";
pub const SESSIONS_CHANGED_METHOD: &str = "x.ai/sessions/changed";
pub const SESSIONS_LIST_METHOD: &str = "kigi/sessions/list";
pub const SESSIONS_CHANGED_METHOD: &str = "kigi/sessions/changed";
/// Merge live `resident` rows with on-disk `summaries` into the sorted roster.
/// Pure, so it is unit-testable without disk or a live actor.
@@ -173,7 +173,7 @@ mod merge_roster_tests {
title: None,
cwd: format!("/live/{id}"),
is_worktree: false,
model_id: Some("grok-4".into()),
model_id: Some("kigi-4".into()),
reasoning_effort: None,
yolo: false,
activity,
@@ -1,7 +1,7 @@
//! WebSocket server for remote agent connections.
//!
//! This module provides a WebSocket server that allows remote TUI clients to
//! connect to a grok agent running on a different machine.
//! connect to a kigi agent running on a different machine.
//!
//! The agent persists across WebSocket reconnections: a single MvpAgent instance
//! is created on first connection and reused for all subsequent connections. This
@@ -25,7 +25,7 @@ pub struct SessionConfigOption {
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GrokSessionDetail {
pub struct KigiSessionDetail {
pub session_id: String,
pub kind: String,
pub cwd: String,
@@ -34,7 +34,7 @@ pub struct GrokSessionDetail {
pub title: Option<String>,
}
impl GrokSessionDetail {
impl KigiSessionDetail {
pub fn build(
session_id: String,
cwd: String,
@@ -124,11 +124,8 @@ mod tests {
#[test]
fn options_have_one_selected_model_and_a_mode_per_effort() {
let models = [
model("grok-build", "Grok Build"),
model("grok-4.5", "Grok 4.5"),
];
let current = acp::ModelId::from("grok-build");
let models = [model("kigi", "Kigi"), model("kigi-4.5", "Kigi 4.5")];
let current = acp::ModelId::from("kigi");
let opts = build_session_config_options(
&models,
&current,
@@ -140,7 +137,7 @@ mod tests {
assert_eq!(model_opts.len(), 2);
let selected_models: Vec<_> = model_opts.iter().filter(|o| o.selected).collect();
assert_eq!(selected_models.len(), 1);
assert_eq!(selected_models[0].id, "grok-build");
assert_eq!(selected_models[0].id, "kigi");
let mode_opts: Vec<_> = opts.iter().filter(|o| o.category == "mode").collect();
assert_eq!(mode_opts.len(), SELECTABLE_REASONING_EFFORTS.len());
@@ -153,8 +150,8 @@ mod tests {
#[test]
fn none_effort_is_not_a_user_selectable_mode() {
assert!(!SELECTABLE_REASONING_EFFORTS.contains(&ReasoningEffort::None));
let models = [model("grok-build", "Grok Build")];
let current = acp::ModelId::from("grok-build");
let models = [model("kigi", "Kigi")];
let current = acp::ModelId::from("kigi");
let opts = build_session_config_options(
&models,
&current,
@@ -168,8 +165,8 @@ mod tests {
#[test]
fn no_mode_options_when_model_lacks_effort_support() {
let models = [model("grok-build", "Grok Build")];
let current = acp::ModelId::from("grok-build");
let models = [model("kigi", "Kigi")];
let current = acp::ModelId::from("kigi");
let opts = build_session_config_options(&models, &current, &[], None);
assert_eq!(opts.len(), 1);
assert!(opts.iter().all(|o| o.category == "model"));
@@ -177,42 +174,42 @@ mod tests {
#[test]
fn model_label_falls_back_to_id_when_name_empty() {
let models = [model("grok-build", "")];
let current = acp::ModelId::from("grok-build");
let models = [model("kigi", "")];
let current = acp::ModelId::from("kigi");
let opts = build_session_config_options(&models, &current, &[], None);
assert_eq!(opts[0].label, "grok-build");
assert_eq!(opts[0].label, "kigi");
}
#[test]
fn session_config_option_serializes_camel_case() {
let opt = SessionConfigOption {
id: "grok-build".to_string(),
id: "kigi".to_string(),
category: "model".to_string(),
label: "Grok Build".to_string(),
label: "Kigi".to_string(),
description: None,
selected: true,
};
let v = serde_json::to_value(&opt).expect("serialize");
assert_eq!(v["id"], "grok-build");
assert_eq!(v["id"], "kigi");
assert_eq!(v["category"], "model");
assert_eq!(v["label"], "Grok Build");
assert_eq!(v["label"], "Kigi");
assert_eq!(v["selected"], true);
assert!(v.get("description").is_none());
}
#[test]
fn grok_session_detail_serializes_camel_case() {
let detail = GrokSessionDetail::build(
fn kigi_session_detail_serializes_camel_case() {
let detail = KigiSessionDetail::build(
"sess-1".to_string(),
"/Users/me/xai".to_string(),
"grok-build".to_string(),
"kigi".to_string(),
None,
);
let v = serde_json::to_value(&detail).expect("serialize");
assert_eq!(v["sessionId"], "sess-1");
assert_eq!(v["kind"], "build");
assert_eq!(v["cwd"], "/Users/me/xai");
assert_eq!(v["currentModelId"], "grok-build");
assert_eq!(v["currentModelId"], "kigi");
assert!(v.get("title").is_none());
}
}
@@ -15,7 +15,7 @@ use crate::session::{
use crate::terminal::AsyncTerminalRunner;
use crate::tools::ToolContext;
use kigi_acp_lib::AcpAgentGatewaySender as GatewaySender;
use kigi_tools::implementations::grok_build::task::types::*;
use kigi_tools::implementations::kigi::task::types::*;
use kigi_workspace::file_system::AsyncFileSystem;
use kigi_hunk_tracker::HunkTrackerHandle;
use super::*;
@@ -156,8 +156,8 @@ impl SubagentCoordinator {
pub fn outstanding_reply_for_prompt(
&self,
prompt_id: &str,
) -> kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply {
kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply {
) -> kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply {
kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply {
live_ids: self.outstanding_for_prompt(prompt_id),
background_live: self.background_live_for_prompt(prompt_id),
subagent_usage_not_applied: self.subagent_usage_not_applied(prompt_id),
@@ -15,7 +15,7 @@ use crate::session::{
use crate::terminal::AsyncTerminalRunner;
use crate::tools::ToolContext;
use kigi_acp_lib::AcpAgentGatewaySender as GatewaySender;
use kigi_tools::implementations::grok_build::task::types::*;
use kigi_tools::implementations::kigi::task::types::*;
use kigi_workspace::file_system::AsyncFileSystem;
use kigi_hunk_tracker::HunkTrackerHandle;
use super::*;
@@ -323,7 +323,7 @@ impl SubagentCoordinator {
/// asynchronously after dropping the coordinator borrow.
///
/// Returns an empty `Vec` if no active subagents match the given
/// parent session ID. Callers (e.g. the `x.ai/subagent/list_running`
/// parent session ID. Callers (e.g. the `kigi/subagent/list_running`
/// ACP handler) should treat an empty result as a normal "no running
/// subagents" response, not an error.
pub(crate) fn list_running_for_parent(
@@ -15,7 +15,7 @@ use crate::session::{
use crate::terminal::AsyncTerminalRunner;
use crate::tools::ToolContext;
use kigi_acp_lib::AcpAgentGatewaySender as GatewaySender;
use kigi_tools::implementations::grok_build::task::types::*;
use kigi_tools::implementations::kigi::task::types::*;
use kigi_workspace::file_system::AsyncFileSystem;
use kigi_hunk_tracker::HunkTrackerHandle;
use super::*;
@@ -306,7 +306,7 @@ pub(crate) async fn handle_subagent_request(
subagent_id = % request.id, error = % e,
"Could not resolve worktree base dir, using temp dir for subagent worktree"
);
std::env::temp_dir().join("grok-subagent-worktrees").join(&request.id)
std::env::temp_dir().join("kigi-subagent-worktrees").join(&request.id)
}
};
let source_clone = source_cwd;
@@ -396,7 +396,7 @@ pub(crate) async fn handle_subagent_request(
);
}
{
use kigi_tools::implementations::grok_build::task::MAX_SUBAGENT_DEPTH;
use kigi_tools::implementations::kigi::task::MAX_SUBAGENT_DEPTH;
use kigi_tools::types::tool::ToolKind;
let child_depth = ctx.parent_depth + 1;
if child_depth >= MAX_SUBAGENT_DEPTH {
@@ -754,10 +754,10 @@ pub(crate) async fn handle_subagent_request(
}
}
if let Some(scope) = agent_memory_scope {
use kigi_tools::implementations::grok_build;
use kigi_tools::implementations::kigi;
use kigi_tools::implementations::opencode;
let memory_tools: Vec<kigi_tools::registry::types::ToolConfig> = vec![
(& grok_build::ReadFileTool).into(), (& grok_build::SearchReplaceTool)
(& kigi::ReadFileTool).into(), (& kigi::SearchReplaceTool)
.into(), (& opencode::OpenCodeWriteTool).into(),
];
for tc in memory_tools {
@@ -1570,7 +1570,7 @@ pub(crate) async fn handle_subagent_request(
let mut worktree_removed = false;
if let Some(ref wt_path) = worktree_path {
if snapshot_dispose_enabled {
let ref_name = format!("refs/grok/subagents/{}", request.id);
let ref_name = format!("refs/kigi/subagents/{}", request.id);
let source_repo = resolve_subagent_source_repo(&ctx);
match crate::session::worktree::snapshot_subagent_worktree(
wt_path,
@@ -23,7 +23,7 @@ use crate::tools::ToolContext;
use agent_client_protocol as acp;
use kigi_acp_lib::AcpAgentGatewaySender as GatewaySender;
use kigi_hunk_tracker::HunkTrackerHandle;
use kigi_tools::implementations::grok_build::task::types::*;
use kigi_tools::implementations::kigi::task::types::*;
use kigi_workspace::file_system::AsyncFileSystem;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
@@ -197,7 +197,7 @@ pub(crate) struct SubagentSpawnContext {
/// Parent's scheduler handle. When `Some`, the subagent reuses the
/// parent's scheduler actor so scheduled tasks survive subagent exit.
pub parent_scheduler_handle:
Option<kigi_tools::implementations::grok_build::scheduler::types::SchedulerHandle>,
Option<kigi_tools::implementations::kigi::scheduler::types::SchedulerHandle>,
/// Parent's session environment variables (.envrc + color settings).
/// Shared so the child inherits the same env without re-loading.
pub session_env: Arc<HashMap<String, String>>,
@@ -207,10 +207,10 @@ pub(crate) struct SubagentSpawnContext {
/// Resolved sampling config for web_search.
pub web_search_config: kigi_tools::implementations::WebSearchConfig,
/// Resolved config for web fetch.
pub web_fetch_config: kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig,
pub web_fetch_config: kigi_tools::implementations::kigi::web_fetch::WebFetchConfig,
/// Resolved config for the deploy service.
pub app_builder_deployer_config:
kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig,
kigi_tools::implementations::kigi::deploy_app::AppBuilderDeployerConfig,
/// Whether the write_file tool is enabled.
pub write_file_enabled: bool,
/// Whether goal mode (`/goal`) is enabled.
@@ -301,7 +301,7 @@ pub(crate) struct SubagentSpawnContext {
/// goes through `agent/config.rs::sampling_config_for_model`
/// which always sets that field to `None`.
pub attribution_callback: Option<kigi_sampler::SharedAttributionCallback>,
/// Parent session's agent name (e.g. "grok-build").
/// Parent session's agent name (e.g. "kigi").
pub parent_agent_name: Option<String>,
/// `agent_type` of the parent's current model — the harness-flavor fallback
/// when `parent_agent_name` is not a recognized harness, e.g. a custom
@@ -1639,12 +1639,12 @@ pub(crate) fn subagent_harness_flavor_is_representable(_agent_type: &str) -> boo
/// Apply the harness-dependent toolset/prompt re-selection to a resolved
/// agent definition.
///
/// The harness flavor (alternate vs grok-build) normally follows the PARENT
/// agent: `GrokBuildOrchestrator` parents give children
/// The harness flavor (alternate vs kigi) normally follows the PARENT
/// agent: `KigiOrchestrator` parents give children
/// the alternate harness; the orchestrator keeps children lean, and other parents
/// inherit the file-tool override (hashline vs standard). A `/goal` role may
/// pass `harness_agent_type` to OVERRIDE that flavor regardless of the parent
/// (so a grok-build session can run an alternate-harness verifier and vice-versa);
/// (so a kigi session can run an alternate-harness verifier and vice-versa);
/// `None` for every non-goal spawn ⇒ the parent decides (unchanged). The base
/// toolset stays role-dependent on `subagent_type` (general-purpose →
/// implementer, else explorer), so the role keeps a capable toolset on the
@@ -2170,7 +2170,7 @@ fn emit_subagent_notification(
.ok();
if let Some(params) = params {
let ext_notification =
acp::ExtNotification::new("x.ai/session_notification", params.into());
acp::ExtNotification::new("kigi/session_notification", params.into());
gateway.forward_fire_and_forget(ext_notification);
}
}
@@ -2224,7 +2224,7 @@ fn goal_tick_cmd_tx(
///
/// Notifications are **not** persisted to JSONL — they are transient UI
/// hints, not authoritative lifecycle events. The TUI can resync via
/// `x.ai/subagent/list_running` on reconnect.
/// `kigi/subagent/list_running` on reconnect.
fn spawn_progress_publisher(
signals_handle: crate::session::signals::SessionSignalsHandle,
gateway: GatewaySender,
@@ -2289,7 +2289,7 @@ fn spawn_progress_publisher(
}
if let Some(params) = params {
let ext_notification =
acp::ExtNotification::new("x.ai/session_notification", params.into());
acp::ExtNotification::new("kigi/session_notification", params.into());
gateway.forward_fire_and_forget(ext_notification);
}
}
@@ -119,11 +119,11 @@ fn subagent_max_turns_definition_wins_else_inherits_parent() {
fn resume_worktree_action_covers_three_outcomes() {
use super::{ResumeWorktreeAction, resume_worktree_action};
assert_eq!(
resume_worktree_action(true, Some("refs/grok/subagents/x")),
resume_worktree_action(true, Some("refs/kigi/subagents/x")),
ResumeWorktreeAction::Rehydrate
);
assert_eq!(
resume_worktree_action(false, Some("refs/grok/subagents/x")),
resume_worktree_action(false, Some("refs/kigi/subagents/x")),
ResumeWorktreeAction::Rehydrate
);
assert_eq!(resume_worktree_action(true, None), ResumeWorktreeAction::Reuse);
@@ -1099,7 +1099,7 @@ fn dummy_tracker(
force_compact: Arc::new(AtomicBool::new(false)),
permission_handle: kigi_workspace::permission::PermissionHandle::allow_all(),
attribution_callback: None,
agent_name: "grok-build".to_string(),
agent_name: "kigi".to_string(),
session_default_agent_profile: None,
allowed_subagent_types: None,
hook_registry: None,
@@ -1938,7 +1938,7 @@ async fn bootstrap_fork_live_parent_chat_state_is_forked_with_marker() {
const MARKER: &str = "UNIQUE_LIVE_FORK_MARKER_xyz789";
let req = bootstrap_test_request(true);
let mut ctx = ctx_with_toggle(HashMap::new());
let chat = spawn_test_parent_chat_state("grok-4.5");
let chat = spawn_test_parent_chat_state("kigi-4.5");
chat.replace_conversation(
vec![
ConversationItem::system("parent system"),
@@ -2127,7 +2127,7 @@ async fn handle_subagent_request_rejects_nonexistent_cwd() {
#[tokio::test]
async fn handle_subagent_request_rejects_file_as_cwd() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp_file = tmp_dir.path().join("grok-test-cwd-file");
let tmp_file = tmp_dir.path().join("kigi-test-cwd-file");
std::fs::write(&tmp_file, b"not a directory").unwrap();
let ctx = ctx_with_toggle(HashMap::new());
let coordinator = std::cell::RefCell::new(SubagentCoordinator::new());
@@ -2359,7 +2359,7 @@ fn subagent_await_budget_default_and_override() {
fn summarize_tool_config_uses_name_override_and_strips_namespace() {
use kigi_tools::registry::types::{ToolConfig, ToolServerConfig};
use kigi_tools::types::tool::ToolKind;
let mut read = ToolConfig::from_id("GrokBuild:read_file");
let mut read = ToolConfig::from_id("Kigi:read_file");
read.kind = Some(ToolKind::Read);
let mut read_dup = ToolConfig::from_id("Codex:read_file");
read_dup.kind = Some(ToolKind::Read);
@@ -2410,7 +2410,7 @@ fn describe_subagent_type_not_allowed_outside_allow_list() {
other => panic!("expected NotAllowed, got {other:?}"),
}
}
/// Regression: on the DEFAULT grok-build host —
/// Regression: on the DEFAULT kigi host —
/// the primary `/goal` host — the `general-purpose` toolset's only
/// file-mutator is `search_replace` (`ToolKind::Edit`); the `write`
/// tool (`ToolKind::Write`) is injection-only and absent from the
@@ -2465,7 +2465,7 @@ fn subagent_keeps_default_flavor_when_parent_model_is_non_strict() {
let mut ctx = ctx_with_toggle(HashMap::new());
ctx.parent_agent_name = Some("ai-oncall-bot".to_string());
ctx.parent_model_agent_type = Some(
BuiltinAgentName::GrokBuildPlan.as_ref().to_string(),
BuiltinAgentName::KigiPlan.as_ref().to_string(),
);
let mut def = resolve_agent_definition("general-purpose", &ctx).expect("resolves");
resolve_subagent_toolset("general-purpose", None, &ctx, &mut def);
@@ -2696,7 +2696,7 @@ async fn background_unknown_type_emits_subagent_finished_notification() {
while let Ok(msg) = gateway_rx.try_recv() {
if let kigi_acp_lib::AcpClientMessage::ExtNotification(args) = msg {
let req: &acp::ExtNotification = &args.request;
assert_eq!(req.method.as_ref(), "x.ai/session_notification");
assert_eq!(req.method.as_ref(), "kigi/session_notification");
let body = req.params.get();
assert!(body.contains("subagent_finished"));
assert!(body.contains(& subagent_id));
@@ -3090,8 +3090,8 @@ fn subagent_auth_type_rule() {
use kigi_chat_state::AuthType;
let session = acp::AuthMethodId::new(CACHED_TOKEN_AUTH_METHOD_ID);
let api_key = acp::AuthMethodId::new(XAI_API_KEY_METHOD_ID);
let byok = byok_model_entry("grok-byok");
let plain = test_model_entry("grok-plain");
let byok = byok_model_entry("kigi-byok");
let plain = test_model_entry("kigi-plain");
assert_eq!(super::subagent_auth_type(Some(& byok), & session), AuthType::ApiKey);
assert_eq!(super::subagent_auth_type(Some(& byok), & api_key), AuthType::ApiKey);
assert_eq!(
@@ -3104,14 +3104,14 @@ fn subagent_auth_type_rule() {
#[test]
fn fresh_tool_model_accepts_visible_key_and_internal_id() {
let mut models = indexmap::IndexMap::new();
models.insert("grok-3".to_string(), test_model_entry("grok-3-2025-02-15"));
models.insert("kigi-3".to_string(), test_model_entry("kigi-3-2025-02-15"));
assert!(
super::handle_request::task_model_override_error(Some("grok-3"),
super::handle_request::task_model_override_error(Some("kigi-3"),
ModelOverrideProvenance::Tool, false, & models, false,).is_none(),
"key lookup should succeed"
);
assert!(
super::handle_request::task_model_override_error(Some("grok-3-2025-02-15"),
super::handle_request::task_model_override_error(Some("kigi-3-2025-02-15"),
ModelOverrideProvenance::Tool, false, & models, false,).is_none(),
"info().model lookup should succeed"
);
@@ -3182,7 +3182,7 @@ fn fresh_tool_model_rejects_unknown_and_nonavailable_entries() {
format!("Unknown Task.model slug '{requested}'. Valid model slugs: alpha, zeta. \
Omit `model` to inherit the parent model.")
);
assert!(! error.contains("grok models"));
assert!(! error.contains("kigi models"));
}
assert!(
super::handle_request::task_model_override_error(Some("oauth-only"),
@@ -343,7 +343,7 @@ fn resumable_source_returns_info_for_completed_subagent() {
child_cwd: "/workspace".into(),
worktree_path: Some(PathBuf::from("/tmp/worktree-1")),
snapshot_ref: None,
effective_model_id: "grok-3".into(),
effective_model_id: "kigi-3".into(),
block_waited: false,
explicitly_killed: false,
},
@@ -479,16 +479,16 @@ fn snapshot_ref_field_in_meta_roundtrips() {
persona: None,
resumed_from: None,
child_cwd: None,
worktree_path: Some("/tmp/grok-wt/sa-snap".into()),
snapshot_ref: Some("refs/grok/subagent-snapshots/sa-snap".into()),
worktree_path: Some("/tmp/kigi-wt/sa-snap".into()),
snapshot_ref: Some("refs/kigi/subagent-snapshots/sa-snap".into()),
effective_model_id: None,
};
let json = serde_json::to_string(&meta).unwrap();
assert!(json.contains("snapshot_ref"));
assert!(json.contains("refs/grok/subagent-snapshots/sa-snap"));
assert!(json.contains("refs/kigi/subagent-snapshots/sa-snap"));
let parsed: SubagentMeta = serde_json::from_str(&json).unwrap();
assert_eq!(
parsed.snapshot_ref.as_deref(), Some("refs/grok/subagent-snapshots/sa-snap")
parsed.snapshot_ref.as_deref(), Some("refs/kigi/subagent-snapshots/sa-snap")
);
}
#[test]
@@ -528,7 +528,7 @@ fn snapshot_test_meta(id: &str) -> SubagentMeta {
persona: None,
resumed_from: None,
child_cwd: None,
worktree_path: Some("/tmp/grok-wt/subagent-x".into()),
worktree_path: Some("/tmp/kigi-wt/subagent-x".into()),
snapshot_ref: None,
effective_model_id: None,
}
@@ -540,14 +540,14 @@ fn update_subagent_meta_snapshot_ref_persists_to_disk() {
let dir = tempfile::TempDir::new().unwrap();
assert!(write_subagent_meta(dir.path(), & snapshot_test_meta("sa-write")));
assert!(
update_subagent_meta_snapshot_ref(dir.path(), "refs/grok/subagents/sa-write",
update_subagent_meta_snapshot_ref(dir.path(), "refs/kigi/subagents/sa-write",
"completed"), "persisting the ref into an existing meta.json must report success"
);
let data = std::fs::read_to_string(dir.path().join("meta.json")).unwrap();
let reread: SubagentMeta = serde_json::from_str(&data).unwrap();
assert_eq!(reread.snapshot_ref.as_deref(), Some("refs/grok/subagents/sa-write"));
assert_eq!(reread.snapshot_ref.as_deref(), Some("refs/kigi/subagents/sa-write"));
assert_eq!(reread.status, "completed");
assert_eq!(reread.worktree_path.as_deref(), Some("/tmp/grok-wt/subagent-x"));
assert_eq!(reread.worktree_path.as_deref(), Some("/tmp/kigi-wt/subagent-x"));
}
/// Missing meta.json → the writer reports failure (it `warn!`s), so the
/// completion path keeps the worktree instead of removing it ref-less.
@@ -555,7 +555,7 @@ fn update_subagent_meta_snapshot_ref_persists_to_disk() {
fn update_subagent_meta_snapshot_ref_reports_failure_when_meta_missing() {
let dir = tempfile::TempDir::new().unwrap();
assert!(
! update_subagent_meta_snapshot_ref(dir.path(), "refs/grok/subagents/sa-missing",
! update_subagent_meta_snapshot_ref(dir.path(), "refs/kigi/subagents/sa-missing",
"completed")
);
}
@@ -569,12 +569,12 @@ fn snapshot_ref_write_promotes_nonterminal_status_to_terminal() {
meta.status = "running".into();
assert!(write_subagent_meta(dir.path(), & meta));
assert!(
update_subagent_meta_snapshot_ref(dir.path(), "refs/grok/subagents/x",
update_subagent_meta_snapshot_ref(dir.path(), "refs/kigi/subagents/x",
"completed")
);
let data = std::fs::read_to_string(dir.path().join("meta.json")).unwrap();
let reread: SubagentMeta = serde_json::from_str(&data).unwrap();
assert_eq!(Some("refs/grok/subagents/x"), reread.snapshot_ref.as_deref());
assert_eq!(Some("refs/kigi/subagents/x"), reread.snapshot_ref.as_deref());
assert_eq!("completed", reread.status);
}
/// The coordinator setter stamps the snapshot ref onto the in-memory
@@ -600,17 +600,17 @@ async fn set_completed_snapshot_ref_updates_in_memory_entry() {
.unwrap();
assert!(before.snapshot_ref.is_none());
coordinator
.set_completed_snapshot_ref("sa-mem", "refs/grok/subagents/sa-mem".into());
.set_completed_snapshot_ref("sa-mem", "refs/kigi/subagents/sa-mem".into());
let after = coordinator
.resumable_source_for("sa-mem", "session-A", Path::new("/tmp"))
.unwrap();
assert_eq!(after.snapshot_ref.as_deref(), Some("refs/grok/subagents/sa-mem"));
assert_eq!(after.snapshot_ref.as_deref(), Some("refs/kigi/subagents/sa-mem"));
}
/// Unknown id is a no-op (entry already TTL-evicted; meta.json still holds it).
#[test]
fn set_completed_snapshot_ref_unknown_id_is_noop() {
let mut coordinator = SubagentCoordinator::new();
coordinator.set_completed_snapshot_ref("ghost", "refs/grok/subagents/ghost".into());
coordinator.set_completed_snapshot_ref("ghost", "refs/kigi/subagents/ghost".into());
assert!(
coordinator.resumable_source_for("ghost", "session-A", Path::new("/tmp"))
.is_none()
@@ -719,7 +719,7 @@ async fn completion_snapshot_sequence_persists_ref_then_removes_worktree() {
let meta_dir = temp.path().join("meta");
write_subagent_meta(&meta_dir, &snapshot_test_meta("glue-1"));
let mut coordinator = coordinator_with_completed("glue-1");
let ref_name = "refs/grok/subagents/glue-1";
let ref_name = "refs/kigi/subagents/glue-1";
let snapshot_ref = crate::session::worktree::snapshot_subagent_worktree(
&wt,
&repo,
@@ -744,7 +744,7 @@ async fn completion_snapshot_sequence_persists_ref_then_removes_worktree() {
/// the tracker-retained direct `worktree_path` plus the snapshot_ref.
#[tokio::test]
async fn gate_on_completion_clears_model_facing_worktree_path_but_resume_retains_it() {
let wt = PathBuf::from("/tmp/grok-wt/subagent-disp-1");
let wt = PathBuf::from("/tmp/kigi-wt/subagent-disp-1");
let mut coordinator = SubagentCoordinator::new();
let mut tracker = dummy_tracker("disp-1", "session-A", "explore", "task");
tracker.worktree_path = Some(wt.clone());
@@ -762,21 +762,21 @@ async fn gate_on_completion_clears_model_facing_worktree_path_but_resume_retains
}
coordinator.move_to_completed("disp-1", "task".into(), "explore".into(), result);
coordinator
.set_completed_snapshot_ref("disp-1", "refs/grok/subagents/disp-1".into());
.set_completed_snapshot_ref("disp-1", "refs/kigi/subagents/disp-1".into());
let listed = coordinator.completed.get("disp-1").expect("completed entry");
assert_eq!(None, listed.result.worktree_path);
let src = coordinator
.resumable_source_for("disp-1", "session-A", Path::new("/tmp"))
.unwrap();
assert_eq!(Some(wt), src.worktree_path);
assert_eq!(Some("refs/grok/subagents/disp-1"), src.snapshot_ref.as_deref());
assert_eq!(Some("refs/kigi/subagents/disp-1"), src.snapshot_ref.as_deref());
}
/// Gate on but the worktree was NOT removed (snapshot/persist/remove failed):
/// the model-facing `result.worktree_path` is RETAINED so the parent can still
/// locate the preserved dir.
#[tokio::test]
async fn gate_on_completion_retains_worktree_path_when_not_removed() {
let wt = PathBuf::from("/tmp/grok-wt/subagent-keep-1");
let wt = PathBuf::from("/tmp/kigi-wt/subagent-keep-1");
let mut coordinator = SubagentCoordinator::new();
coordinator.insert(dummy_tracker("keep-1", "session-A", "explore", "task"));
let mut result = SubagentResult {
@@ -817,7 +817,7 @@ async fn disposal_completes_before_subagent_is_observable() {
write_subagent_meta(&meta_dir, &snapshot_test_meta("order-1"));
let mut coordinator = SubagentCoordinator::new();
coordinator.insert(dummy_tracker("order-1", "session-A", "explore", "task"));
let ref_name = "refs/grok/subagents/order-1";
let ref_name = "refs/kigi/subagents/order-1";
let snapshot_ref = crate::session::worktree::snapshot_subagent_worktree(
&wt,
&repo,
@@ -989,7 +989,7 @@ fn resume_inherited_cwd_requires_existing_non_worktree_dir() {
};
assert_eq!(resume_inherited_cwd(Some(& present)), Some(existing.as_str()));
let missing = ResumeSourceData {
child_cwd: "/no/such/dir/grok-missing".into(),
child_cwd: "/no/such/dir/kigi-missing".into(),
..present.clone()
};
assert_eq!(resume_inherited_cwd(Some(& missing)), None);
@@ -1120,7 +1120,7 @@ fn token_estimation_accounts_for_images() {
#[test]
fn durable_fallback_roundtrips_child_cwd_and_worktree() {
let dir = std::env::temp_dir()
.join("grok-test-durable-resume")
.join("kigi-test-durable-resume")
.join(uuid::Uuid::now_v7().to_string());
let _ = std::fs::create_dir_all(&dir);
let meta = SubagentMeta {
@@ -1143,22 +1143,22 @@ fn durable_fallback_roundtrips_child_cwd_and_worktree() {
persona: Some("implementer".into()),
resumed_from: None,
child_cwd: Some("/workspace/project".into()),
worktree_path: Some("/tmp/grok-wt/sa-dur".into()),
worktree_path: Some("/tmp/kigi-wt/sa-dur".into()),
snapshot_ref: None,
effective_model_id: Some("grok-3".into()),
effective_model_id: Some("kigi-3".into()),
};
write_subagent_meta(&dir, &meta);
let data = std::fs::read_to_string(dir.join("meta.json")).unwrap();
let loaded: SubagentMeta = serde_json::from_str(&data).unwrap();
assert_eq!(loaded.child_cwd.as_deref(), Some("/workspace/project"));
assert_eq!(loaded.worktree_path.as_deref(), Some("/tmp/grok-wt/sa-dur"));
assert_eq!(loaded.worktree_path.as_deref(), Some("/tmp/kigi-wt/sa-dur"));
assert_eq!(loaded.status, "completed");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn durable_fallback_rejects_running_status() {
let dir = std::env::temp_dir()
.join("grok-test-durable-status")
.join("kigi-test-durable-status")
.join(uuid::Uuid::now_v7().to_string());
let parent_dir = dir.join("subagents").join("sa-running");
let _ = std::fs::create_dir_all(&parent_dir);
@@ -1227,7 +1227,7 @@ fn drain_cancelled_finish_broadcasts(
let kigi_acp_lib::AcpClientMessage::ExtNotification(args) = msg else {
continue;
};
assert_eq!(args.request.method.as_ref(), "x.ai/session_notification");
assert_eq!(args.request.method.as_ref(), "kigi/session_notification");
let notification: SessionNotification = serde_json::from_str(
args.request.params.get(),
)
@@ -1666,11 +1666,11 @@ fn resume_allows_matching_identity() {
snapshot_ref: None,
subagent_type: "general-purpose".into(),
persona: Some("implementer".into()),
model_id: Some("grok-3".into()),
model_id: Some("kigi-3".into()),
};
assert_eq!("general-purpose", source.subagent_type);
assert_eq!(Some("implementer"), source.persona.as_deref());
assert_eq!(Some("grok-3"), source.model_id.as_deref());
assert_eq!(Some("kigi-3"), source.model_id.as_deref());
}
#[test]
fn resume_identity_does_not_gate_on_model() {
@@ -1682,21 +1682,21 @@ fn resume_identity_does_not_gate_on_model() {
snapshot_ref: None,
subagent_type: "general-purpose".into(),
persona: None,
model_id: Some("grok-3".into()),
model_id: Some("kigi-3".into()),
};
assert!(
kigi_subagent_resolution::validate_resume_identity("general-purpose", None, &
source,).is_ok()
);
assert_eq!(
source.model_id.as_deref(), Some("grok-3"),
source.model_id.as_deref(), Some("kigi-3"),
"source model remains available for pinning"
);
}
#[test]
fn durable_meta_roundtrips_effective_model_id() {
let dir = std::env::temp_dir()
.join("grok-test-model-roundtrip")
.join("kigi-test-model-roundtrip")
.join(uuid::Uuid::now_v7().to_string());
let _ = std::fs::create_dir_all(&dir);
let meta = SubagentMeta {
@@ -1721,24 +1721,24 @@ fn durable_meta_roundtrips_effective_model_id() {
child_cwd: Some("/workspace".into()),
worktree_path: None,
snapshot_ref: None,
effective_model_id: Some("grok-3".into()),
effective_model_id: Some("kigi-3".into()),
};
write_subagent_meta(&dir, &meta);
let data = std::fs::read_to_string(dir.join("meta.json")).unwrap();
let loaded: SubagentMeta = serde_json::from_str(&data).unwrap();
assert_eq!(
loaded.effective_model_id.as_deref(), Some("grok-3"),
loaded.effective_model_id.as_deref(), Some("kigi-3"),
"model ID should round-trip through meta.json"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn resume_model_pinning_overrides_default_resolution() {
let source_model = Some("grok-3".to_string());
let resolved_model = "grok-light";
let source_model = Some("kigi-3".to_string());
let resolved_model = "kigi-light";
let needs_pin = source_model.as_deref() != Some(resolved_model);
assert!(needs_pin, "resolved model differs from source — pinning should trigger");
let resolved_same = "grok-3";
let resolved_same = "kigi-3";
let no_pin = source_model.as_deref() == Some(resolved_same);
assert!(no_pin, "same model — no pinning needed");
}
@@ -2108,19 +2108,19 @@ fn ctx_with_parent_chat_state(
#[tokio::test]
async fn read_parent_sampling_config_keeps_auto_catalog_id_with_routing_slug() {
let mut models = indexmap::IndexMap::new();
models.insert("auto".to_string(), test_model_entry("grok-4.5"));
let ctx = ctx_with_parent_chat_state("auto", "grok-4.5", "composer-2-fast", models);
models.insert("auto".to_string(), test_model_entry("kigi-4.5"));
let ctx = ctx_with_parent_chat_state("auto", "kigi-4.5", "composer-2-fast", models);
let (config, model_id) = read_parent_sampling_config(&ctx).await;
assert_eq!(config.model, "grok-4.5");
assert_eq!(config.model, "kigi-4.5");
assert_eq!(model_id.0.as_ref(), "auto");
}
#[tokio::test]
async fn read_parent_sampling_config_keeps_auto_when_catalog_has_slug_key_only() {
let mut models = indexmap::IndexMap::new();
models.insert("grok-4.5".to_string(), test_model_entry("grok-4.5"));
let ctx = ctx_with_parent_chat_state("auto", "grok-4.5", "auto", models);
models.insert("kigi-4.5".to_string(), test_model_entry("kigi-4.5"));
let ctx = ctx_with_parent_chat_state("auto", "kigi-4.5", "auto", models);
let (config, model_id) = read_parent_sampling_config(&ctx).await;
assert_eq!(config.model, "grok-4.5");
assert_eq!(config.model, "kigi-4.5");
assert_eq!(model_id.0.as_ref(), "auto");
}
#[tokio::test]
@@ -2161,11 +2161,11 @@ async fn read_parent_sampling_config_ignores_global_default() {
}
#[tokio::test]
async fn read_parent_sampling_config_resolves_backend_search_from_catalog() {
let mut entry = test_model_entry("grok-4.5");
let mut entry = test_model_entry("kigi-4.5");
entry.info.supports_backend_search = true;
let mut models = indexmap::IndexMap::new();
models.insert("auto".to_string(), entry);
let mut ctx = ctx_with_parent_chat_state("auto", "grok-4.5", "auto", models);
let mut ctx = ctx_with_parent_chat_state("auto", "kigi-4.5", "auto", models);
ctx.sampling_config.supports_backend_search = false;
let (config, _model_id) = read_parent_sampling_config(&ctx).await;
assert!(
@@ -2201,11 +2201,11 @@ async fn read_parent_sampling_config_fallback_resolves_backend_search_from_catal
#[tokio::test]
async fn read_parent_sampling_config_resolves_compactions_remaining_from_catalog() {
use kigi_sampling_types::CompactionsRemaining;
let mut entry = test_model_entry("grok-4.5");
let mut entry = test_model_entry("kigi-4.5");
entry.info.compactions_remaining = Some(CompactionsRemaining::Dynamic(true));
let mut models = indexmap::IndexMap::new();
models.insert("auto".to_string(), entry);
let mut ctx = ctx_with_parent_chat_state("auto", "grok-4.5", "auto", models);
let mut ctx = ctx_with_parent_chat_state("auto", "kigi-4.5", "auto", models);
ctx.sampling_config.compactions_remaining = None;
let (config, _model_id) = read_parent_sampling_config(&ctx).await;
assert_eq!(
@@ -2360,7 +2360,7 @@ async fn fork_context_pins_parent_model_over_overrides() {
#[tokio::test]
async fn resolve_subagent_inherits_parent_model_without_pins() {
use kigi_agent::config::ModelOverride;
for parent_model in ["grok-4.5", "composer-2-fast", "my-custom-byok-model"] {
for parent_model in ["kigi-4.5", "composer-2-fast", "my-custom-byok-model"] {
let mut ctx = ctx_with_toggle(HashMap::new());
ctx.sampling_config.model = parent_model.to_string();
ctx.model_id = acp::ModelId::new(parent_model);
@@ -2379,12 +2379,12 @@ async fn resolve_subagent_inherits_parent_model_without_pins() {
}
/// An explicit `[subagents.models]` pin routes the subagent to that
/// model regardless of the parent model — both a light parent
/// (`grok-4.5`) and a custom parent (`composer-2-fast`)
/// (`kigi-4.5`) and a custom parent (`composer-2-fast`)
/// honor the pin identically now that the heavy-model gate is gone.
#[tokio::test]
async fn resolve_subagent_config_override_pin_applies_for_any_parent() {
use kigi_agent::config::ModelOverride;
for parent_model in ["grok-4.5", "composer-2-fast"] {
for parent_model in ["kigi-4.5", "composer-2-fast"] {
let mut ctx = ctx_with_toggle(HashMap::new());
ctx.sampling_config.model = parent_model.to_string();
ctx.model_id = acp::ModelId::new(parent_model);
@@ -2411,8 +2411,8 @@ async fn resolve_subagent_config_override_pin_applies_for_any_parent() {
async fn resolve_subagent_agent_definition_pin_applies_for_light_parent() {
use kigi_agent::config::ModelOverride;
let mut ctx = ctx_with_toggle(HashMap::new());
ctx.sampling_config.model = "grok-4.5".to_string();
ctx.model_id = acp::ModelId::new("grok-4.5");
ctx.sampling_config.model = "kigi-4.5".to_string();
ctx.model_id = acp::ModelId::new("kigi-4.5");
ctx.available_models
.insert("pinned-model".to_string(), test_model_entry("pinned-model"));
let agent_model = ModelOverride::Override("pinned-model".to_string());
@@ -2431,8 +2431,8 @@ async fn resolve_subagent_agent_definition_pin_applies_for_light_parent() {
async fn resolve_subagent_config_override_wins_over_agent_definition() {
use kigi_agent::config::ModelOverride;
let mut ctx = ctx_with_toggle(HashMap::new());
ctx.sampling_config.model = "grok-4.5".to_string();
ctx.model_id = acp::ModelId::new("grok-4.5");
ctx.sampling_config.model = "kigi-4.5".to_string();
ctx.model_id = acp::ModelId::new("kigi-4.5");
ctx.available_models
.insert("config-pin".to_string(), test_model_entry("config-pin"));
ctx.available_models
@@ -2454,8 +2454,8 @@ async fn resolve_subagent_config_override_wins_over_agent_definition() {
async fn resolve_subagent_config_override_unknown_model_falls_through_to_inherit() {
use kigi_agent::config::ModelOverride;
let mut ctx = ctx_with_toggle(HashMap::new());
ctx.sampling_config.model = "grok-4.5".to_string();
ctx.model_id = acp::ModelId::new("grok-4.5");
ctx.sampling_config.model = "kigi-4.5".to_string();
ctx.model_id = acp::ModelId::new("kigi-4.5");
ctx.subagent_model_overrides
.insert("explore".to_string(), "does-not-exist".to_string());
let (config, model_id) = resolve_subagent_sampling_config(
@@ -2464,8 +2464,8 @@ async fn resolve_subagent_config_override_unknown_model_falls_through_to_inherit
&ctx,
)
.await;
assert_eq!(config.model, "grok-4.5");
assert_eq!(model_id.0.as_ref(), "grok-4.5");
assert_eq!(config.model, "kigi-4.5");
assert_eq!(model_id.0.as_ref(), "kigi-4.5");
}
/// An unresolvable `AgentDefinition.model` pin (model absent from
/// `available_models`) falls through to inherit the parent model.
@@ -2473,8 +2473,8 @@ async fn resolve_subagent_config_override_unknown_model_falls_through_to_inherit
async fn resolve_subagent_agent_definition_unknown_model_falls_through_to_inherit() {
use kigi_agent::config::ModelOverride;
let mut ctx = ctx_with_toggle(HashMap::new());
ctx.sampling_config.model = "grok-4.5".to_string();
ctx.model_id = acp::ModelId::new("grok-4.5");
ctx.sampling_config.model = "kigi-4.5".to_string();
ctx.model_id = acp::ModelId::new("kigi-4.5");
let agent_model = ModelOverride::Override("does-not-exist".to_string());
let (config, model_id) = resolve_subagent_sampling_config(
"explore",
@@ -2482,8 +2482,8 @@ async fn resolve_subagent_agent_definition_unknown_model_falls_through_to_inheri
&ctx,
)
.await;
assert_eq!(config.model, "grok-4.5");
assert_eq!(model_id.0.as_ref(), "grok-4.5");
assert_eq!(config.model, "kigi-4.5");
assert_eq!(model_id.0.as_ref(), "kigi-4.5");
}
#[test]
fn key_prefix_truncates_to_8_chars() {