§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
@@ -1,6 +1,6 @@
//! Auto permission mode: LLM transcript classifier with safe fast-paths.
//!
//! Port of common agent auto-permission classifier semantics adapted to Grok's
//! Port of common agent auto-permission classifier semantics adapted to Kigi's
//! `AccessKind` permission gate (classifier blocks prompt the user; upstream
//! denial-limit tracking is intentionally not ported).
@@ -874,7 +874,7 @@ pub fn access_requires_user_interaction(tool_name: &str, access: &AccessKind) ->
pub type SharedClassifier = Arc<dyn PermissionClassifier>;
/// Tools / access kinds that never need a classifier call (safe allowlist
/// mapped to Grok access kinds + known names).
/// mapped to Kigi access kinds + known names).
pub fn is_auto_mode_allowlisted_access(access: &AccessKind) -> bool {
matches!(
access,
@@ -21,7 +21,7 @@ pub struct ClaudeSettings {
pub permissions: Option<ParsedPermissions>,
/// Raw `defaultMode` string when present (canonical under `permissions`, or
/// grok-only root legacy). Recognized values: `acceptEdits`,
/// kigi-only root legacy). Recognized values: `acceptEdits`,
/// `bypassPermissions`, `default`, `plan`, `dontAsk`, `auto`.
#[serde(default)]
pub default_mode: Option<String>,
@@ -85,7 +85,7 @@ impl ParsedPermissions {
/// from `serde_json::Value`. Non-string entries are skipped with warnings.
/// - This enables partial success when some entries are malformed.
/// - `defaultMode` / `additionalDirectories` prefer the canonical location
/// under `permissions.*`. Root-level keys are **grok legacy only** (not in
/// under `permissions.*`. Root-level keys are **kigi legacy only** (not in
/// the vendor schema) and are used only when the nested key is **absent** —
/// not when it is present but the wrong type.
pub fn load_claude_settings(path: &Path) -> Option<ClaudeSettings> {
@@ -123,7 +123,7 @@ pub fn load_claude_settings(path: &Path) -> Option<ClaudeSettings> {
}
});
// Canonical vendor settings store these under `permissions`; root is grok legacy only.
// Canonical vendor settings store these under `permissions`; root is kigi legacy only.
let default_mode = extract_default_mode(&value, path);
let additional_directories = extract_additional_directories(&value, path);
@@ -140,7 +140,7 @@ pub fn load_claude_settings(path: &Path) -> Option<ClaudeSettings> {
/// Canonical key is `permissions.defaultMode`.
///
/// Root `defaultMode` is grok-only back-compat for older tests / hand-written
/// Root `defaultMode` is kigi-only back-compat for older tests / hand-written
/// configs. Fall back to root only when the nested key is **absent**. If nested
/// is present but not a string, do not resurrect a root value (malformed
/// canonical key must not revive stale legacy).
@@ -161,7 +161,7 @@ pub(crate) fn extract_default_mode(value: &serde_json::Value, path: &Path) -> Op
};
}
// Nested key absent — optional grok legacy root.
// Nested key absent — optional kigi legacy root.
match value.get("defaultMode") {
Some(dm) => match dm.as_str() {
Some(s) => Some(s.to_string()),
@@ -169,7 +169,7 @@ pub(crate) fn extract_default_mode(value: &serde_json::Value, path: &Path) -> Op
warn!(
path = %path.display(),
actual_type = %dm.type_of(),
"root defaultMode (grok legacy): expected string, ignoring"
"root defaultMode (kigi legacy): expected string, ignoring"
);
None
}
@@ -182,7 +182,7 @@ pub(crate) fn extract_default_mode(value: &serde_json::Value, path: &Path) -> Op
/// legacy/compat. Nested wins when both are present.
fn extract_additional_directories(value: &serde_json::Value, path: &Path) -> Option<Vec<String>> {
// Mirror `extract_default_mode`: prefer the Claude-canonical nested key, and
// when it is present but the wrong type, do *not* resurrect the grok-legacy
// when it is present but the wrong type, do *not* resurrect the kigi-legacy
// root value (a malformed canonical key must not revive stale legacy).
let arr = if let Some(nested) = value
.get("permissions")
@@ -514,7 +514,7 @@ pub fn is_claude_import_marked() -> bool {
// lives in kigi-shell (inaccessible from here at runtime). They also
// set this env var so the workspace-resident gate honours the override
// without a cross-crate dependency.
if std::env::var("_GROK_CLAUDE_MARKER_OVERRIDE").as_deref() == Ok("1") {
if std::env::var("_KIGI_CLAUDE_MARKER_OVERRIDE").as_deref() == Ok("1") {
return true;
}
let Some(config_path) = kigi_config::user_kigi_home().map(|g| g.join("config.toml")) else {
@@ -18,7 +18,7 @@ use crate::permission::types::{
AccessKind, ClientType, Decision, EditPolicy, PermissionCommand, PermissionEvent, PromptPolicy,
};
use kigi_paths::AbsPathBuf;
use kigi_tools::implementations::grok_build::web_fetch::{DomainMatcher, domain::normalize_domain};
use kigi_tools::implementations::kigi::web_fetch::{DomainMatcher, domain::normalize_domain};
/// Canonical `decision_reason` triggers for the uploaded artifact. Single source
/// so the emit sites can't drift or misspell (the field doc lists these values).
@@ -911,7 +911,7 @@ fn spawn_permission_manager_with_pin(
//
// Prior to this change, that choice would set edit_policy=Allow and
// persist it to ~/.kigi/sessions/<cwd>/permission.toml. This caused
// the allow to survive full restarts (new grok process, new agent
// the allow to survive full restarts (new kigi process, new agent
// session in the same directory), which did not match the label or
// user expectation (and did not match upstream session-scoped
// behavior).
@@ -1579,7 +1579,7 @@ fn spawn_permission_manager_with_pin(
(Decision::Allow, "allow_edits_for_session")
}
PromptOutcome::AllowAlways => {
// Fallback clients (Generic / GrokWeb /
// Fallback clients (Generic / KigiWeb /
// Extension) submit the legacy `"always-allow"` option
// id, which the prompter maps to plain `AllowAlways`.
// They have no scope toggle, so default to tool-scope
@@ -2400,7 +2400,7 @@ mod tests {
/// Spawn a manager whose prompter is wired to a live gateway receiver backed
/// by `client`, so prompting performs a real `request_permission` round-trip.
/// `client_type` selects the option set the prompter builds (e.g. the
/// always-approve option is only offered for `GrokTUI | GrokPager | Desktop`).
/// always-approve option is only offered for `KigiTUI | KigiPager | Desktop`).
fn manager_with_recording_client(
cwd: &AbsPathBuf,
config: Option<crate::permission::types::PermissionConfig>,
@@ -4930,10 +4930,10 @@ mod tests {
let cwd = AbsPathBuf::new(tmp.path().to_path_buf()).unwrap();
let client = RecordingClient::default();
let prompts = client.prompts.clone();
// GrokPager wires the always-approve option through to its YOLO
// KigiPager wires the always-approve option through to its YOLO
// toggle; it is the option set the auto path prompts under.
let (mgr, _e) =
manager_with_recording_client(&cwd, None, client, ClientType::GrokPager);
manager_with_recording_client(&cwd, None, client, ClientType::KigiPager);
mgr.set_auto_mode(true);
// Force classify to Block. Interactive auto mode must now prompt
// on the FIRST block instead of denying.
@@ -5000,7 +5000,7 @@ mod tests {
let client = RecordingClient::default();
let prompts = client.prompts.clone();
let (mgr, _e) =
manager_with_recording_client(&cwd, None, client, ClientType::GrokPager);
manager_with_recording_client(&cwd, None, client, ClientType::KigiPager);
mgr.set_auto_mode(true);
mgr.set_classifier(Some(LlmPermissionClassifier::with_fixed_model_text(
r#"{"thinking":"t","shouldBlock":true,"reason":"x"}"#,
@@ -5050,7 +5050,7 @@ mod tests {
let client = RecordingClient::default();
let prompts = client.prompts.clone();
let (mgr, _e) =
manager_with_recording_client(&cwd, None, client, ClientType::GrokPager);
manager_with_recording_client(&cwd, None, client, ClientType::KigiPager);
mgr.set_auto_mode(true);
mgr.set_classifier(Some(LlmPermissionClassifier::with_fixed_model_text(
r#"{"thinking":"t","shouldBlock":true,"reason":"x"}"#,
@@ -5098,7 +5098,7 @@ mod tests {
let client = RecordingClient::default();
let prompts = client.prompts.clone();
let (mgr, _e) =
manager_with_recording_client(&cwd, None, client, ClientType::GrokPager);
manager_with_recording_client(&cwd, None, client, ClientType::KigiPager);
mgr.set_auto_mode(true);
mgr.set_classifier(Some(LlmPermissionClassifier::with_fixed_model_text(
r#"{"thinking":"t","shouldBlock":true,"reason":"x"}"#,
@@ -5144,7 +5144,7 @@ mod tests {
let client = RecordingClient::default();
let prompts = client.prompts.clone();
let (mgr, _e) =
manager_with_recording_client(&cwd, None, client, ClientType::GrokPager);
manager_with_recording_client(&cwd, None, client, ClientType::KigiPager);
mgr.set_auto_mode(true);
mgr.set_classifier(Some(LlmPermissionClassifier::with_fixed_model_text(
r#"{"thinking":"t","shouldBlock":true,"reason":"x"}"#,
@@ -5193,7 +5193,7 @@ mod tests {
let client = RecordingClient::default();
let prompts = client.prompts.clone();
let (mgr, _e) =
manager_with_recording_client(&cwd, None, client, ClientType::GrokPager);
manager_with_recording_client(&cwd, None, client, ClientType::KigiPager);
mgr.set_auto_mode(true);
mgr.set_classifier(Some(LlmPermissionClassifier::with_fixed_model_text(
r#"{"thinking":"t","shouldBlock":true,"reason":"x"}"#,
@@ -5239,7 +5239,7 @@ mod tests {
let client = RecordingClient::default();
let prompts = client.prompts.clone();
let (mgr, _e) =
manager_with_recording_client(&cwd, None, client, ClientType::GrokPager);
manager_with_recording_client(&cwd, None, client, ClientType::KigiPager);
mgr.set_auto_mode(true);
mgr.set_classifier(Some(LlmPermissionClassifier::with_fixed_model_text(
r#"{"thinking":"t","shouldBlock":true,"reason":"x"}"#,
@@ -5324,7 +5324,7 @@ mod tests {
let client = RecordingClient::default();
let prompts = client.prompts.clone();
let (mgr, _e) =
manager_with_recording_client(&cwd, None, client, ClientType::GrokPager);
manager_with_recording_client(&cwd, None, client, ClientType::KigiPager);
mgr.set_auto_mode(true);
mgr.set_classifier(Some(LlmPermissionClassifier::with_fixed_model_text(
r#"{"thinking":"t","shouldBlock":false,"reason":"x"}"#,
@@ -5373,7 +5373,7 @@ mod tests {
let client = RecordingClient::default();
let prompts = client.prompts.clone();
let (mgr, _e) =
manager_with_recording_client(&cwd, None, client, ClientType::GrokPager);
manager_with_recording_client(&cwd, None, client, ClientType::KigiPager);
mgr.set_auto_mode(true);
mgr.set_classifier(Some(LlmPermissionClassifier::with_fixed_model_text(
r#"{"thinking":"t","shouldBlock":true,"reason":"x"}"#,
@@ -3,7 +3,7 @@ use crate::permission::shell_access::combine_decisions;
use crate::permission::types::{
AccessKind, Decision, PatternMode, PermissionConfig, PermissionRule, RuleAction, ToolFilter,
};
use kigi_tools::implementations::grok_build::web_fetch::domain::normalize_domain;
use kigi_tools::implementations::kigi::web_fetch::domain::normalize_domain;
#[derive(Clone, Copy)]
enum MatchContext {
@@ -9,9 +9,9 @@ use crate::permission::{
use agent_client_protocol::{self as acp, Client as _};
use kigi_acp_lib::AcpAgentGatewaySender as GatewaySender;
use kigi_file_utils::events::{Event, EventWriter, PermissionDecision};
use kigi_tools::implementations::grok_build::web_fetch::domain_from_url;
use kigi_tools::implementations::kigi::web_fetch::domain_from_url;
const REJECT_ONCE_LABEL: &str = "No, and tell Grok what to do differently";
const REJECT_ONCE_LABEL: &str = "No, and tell Kigi what to do differently";
/// Stable option id for the edit prompt's "Yes, allow all edits during this
/// session" choice. Distinct from the generic `"always-allow"` id (used by
@@ -40,7 +40,7 @@ pub const ALLOW_EDITS_SESSION_OPTION_ID: &str = "allow-edits-session";
/// 2. Drains any queued permission requests with `AllowOnce` responses
/// 3. Persists `[ui] permission_mode = "always-approve"` to
/// `~/.kigi/config.toml` via the `Effect::PersistPermissionMode` effect
/// 4. Sends the existing `x.ai/yolo_mode_changed` ACP notification so
/// 4. Sends the existing `kigi/yolo_mode_changed` ACP notification so
/// the agent's permission manager flips its `yolo_mode` flag
///
/// This split keeps the wire protocol bog-standard ACP (no new methods or
@@ -96,7 +96,7 @@ fn enable_always_approve_option() -> acp::PermissionOption {
}
/// Returns whether the given option is the special "enable always-approve mode"
/// (global yolo) option that is prepended for GrokTUI / GrokPager / Desktop.
/// (global yolo) option that is prepended for KigiTUI / KigiPager / Desktop.
///
/// This is the canonical way to identify the option instead of matching on
/// its human-facing label or assuming position 0. Callers that need to
@@ -108,12 +108,12 @@ pub fn is_enable_always_approve_option(opt: &acp::PermissionOption) -> bool {
/// Returns `true` if the given client type should see the prepended
/// "enable always-approve mode" option. Limited to the three clients
/// (`GrokTUI`, `GrokPager`, `Desktop`) that wire the option id through
/// (`KigiTUI`, `KigiPager`, `Desktop`) that wire the option id through
/// to their YOLO toggle. Other clients keep their existing option set.
fn client_supports_enable_always_approve(client_type: ClientType) -> bool {
matches!(
client_type,
ClientType::GrokTUI | ClientType::GrokPager | ClientType::Desktop
ClientType::KigiTUI | ClientType::KigiPager | ClientType::Desktop
)
}
@@ -188,8 +188,8 @@ pub fn mcp_tool_action<'a>(tool_name: &'a str, server_prefix: Option<&str>) -> &
/// Pretty-format a single MCP server- or tool-name segment for display:
/// split on `'_'`, title-case each word, join with spaces. Leaves
/// non-underscore characters (camelCase, hyphens) intact, so
/// `"list_issues"` → `"List Issues"`, `"grok_com_notion"` →
/// `"Grok Com Notion"`, and `"getMyTaskList"` → `"GetMyTaskList"`.
/// `"list_issues"` → `"List Issues"`, `"kigi_com_notion"` →
/// `"Kigi Com Notion"`, and `"getMyTaskList"` → `"GetMyTaskList"`.
pub fn mcp_titleize_segment(name: &str) -> String {
name.split('_')
.map(|word| {
@@ -248,10 +248,10 @@ pub struct McpToolPermission {
/// e.g. `"Always allow:"`. Mirrors `BashCommandPermission::prompt_prefix`.
pub prompt_prefix: String,
/// Full tool name as the agent called it
/// (e.g. `"grok_com_notion__notion-fetch"`).
/// (e.g. `"kigi_com_notion__notion-fetch"`).
pub tool_name: String,
/// Server segment (everything before the single `__` separator,
/// e.g. `"grok_com_notion"`). `None` if the tool name has no `__`,
/// e.g. `"kigi_com_notion"`). `None` if the tool name has no `__`,
/// in which case the view hides the scope toggle and only offers
/// tool-scope.
pub server_prefix: Option<String>,
@@ -397,7 +397,7 @@ impl AcpPrompter {
),
);
// Bash options for GrokTUI - interactive selection with expandable/contractable terms
// Bash options for KigiTUI - interactive selection with expandable/contractable terms
let mut bash_options: IndexMap<acp::PermissionOptionId, acp::PermissionOption> =
IndexMap::new();
bash_options.insert(
@@ -543,7 +543,7 @@ impl AcpPrompter {
if self.remember_tool_approvals
&& matches!(
self.client_type,
ClientType::GrokTUI | ClientType::GrokPager | ClientType::Desktop
ClientType::KigiTUI | ClientType::KigiPager | ClientType::Desktop
) =>
{
serde_json::to_value(primary_command_from_script(bash_command))
@@ -565,11 +565,11 @@ impl AcpPrompter {
match access {
AccessKind::Edit(_) => self.edit_options.clone(),
AccessKind::Bash(bash_command) => {
// For GrokTUI clients, use the fancy interactive options with term selection
// For KigiTUI clients, use the fancy interactive options with term selection
// For generic clients (web, etc.), use simpler options that work without
// special UI handling
match self.client_type {
ClientType::GrokTUI | ClientType::GrokPager | ClientType::Desktop => {
ClientType::KigiTUI | ClientType::KigiPager | ClientType::Desktop => {
let mut bash_commands: IndexMap<
acp::PermissionOptionId,
acp::PermissionOption,
@@ -601,7 +601,7 @@ impl AcpPrompter {
bash_commands
}
ClientType::Generic
| ClientType::GrokWeb
| ClientType::KigiWeb
| ClientType::Nebula
| ClientType::Extension => {
// For generic clients, use simpler options that display well
@@ -654,7 +654,7 @@ impl AcpPrompter {
// `fallback_options` (`always-allow`) and the manager's
// plain `AllowAlways` arm persists tool-scope.
match self.client_type {
ClientType::GrokTUI | ClientType::GrokPager | ClientType::Desktop => {
ClientType::KigiTUI | ClientType::KigiPager | ClientType::Desktop => {
let mut options: IndexMap<acp::PermissionOptionId, acp::PermissionOption> =
IndexMap::new();
let server_prefix = tool_name.split_once("__").map(|(s, _)| s.to_owned());
@@ -694,7 +694,7 @@ impl AcpPrompter {
options
}
ClientType::Generic
| ClientType::GrokWeb
| ClientType::KigiWeb
| ClientType::Nebula
| ClientType::Extension => self.fallback_options.clone(),
}
@@ -991,7 +991,7 @@ mod tests {
#[test]
fn gate_off_strips_bash_always_allow_keeps_yes_no() {
let p = prompter_with_gate(ClientType::GrokPager, false);
let p = prompter_with_gate(ClientType::KigiPager, false);
let access = AccessKind::Bash("kubectl get pods".to_owned());
let opts = p.build_options(&access);
assert!(
@@ -1012,7 +1012,7 @@ mod tests {
#[test]
fn gate_on_includes_bash_always_allow() {
let p = prompter_with_gate(ClientType::GrokPager, true);
let p = prompter_with_gate(ClientType::KigiPager, true);
let access = AccessKind::Bash("kubectl get pods".to_owned());
let opts = p.build_options(&access);
assert!(
@@ -1027,7 +1027,7 @@ mod tests {
#[test]
fn gate_off_strips_mcp_always_allow() {
let p = prompter_with_gate(ClientType::GrokPager, false);
let p = prompter_with_gate(ClientType::KigiPager, false);
let access = AccessKind::MCPTool {
name: "linear__list".to_owned(),
input: serde_json::Value::Null,
@@ -1040,7 +1040,7 @@ mod tests {
#[test]
fn gate_off_strips_generic_bash_always_and_reject_always() {
let p = prompter_with_gate(ClientType::GrokWeb, false);
let p = prompter_with_gate(ClientType::KigiWeb, false);
let access = AccessKind::Bash("kubectl get pods".to_owned());
let opts = p.build_options(&access);
assert!(!has_option(&opts, "always-allow"));
@@ -1051,7 +1051,7 @@ mod tests {
#[test]
fn gate_off_strips_web_fetch_always_allow_domain() {
let p = prompter_with_gate(ClientType::GrokPager, false);
let p = prompter_with_gate(ClientType::KigiPager, false);
let access = AccessKind::WebFetch("https://example.com/x".to_owned());
let opts = p.build_options(&access);
assert!(!has_option(&opts, "allow-always-domain"));
@@ -1063,7 +1063,7 @@ mod tests {
fn bash_meta_present_only_when_gate_on_for_fancy_clients() {
let access = AccessKind::Bash("kubectl get pods".to_owned());
// Gate on + fancy client → meta carries the parsed command parts.
let on = prompter_with_gate(ClientType::GrokPager, true);
let on = prompter_with_gate(ClientType::KigiPager, true);
let meta = on.bash_selection_meta(&access).expect("meta present");
assert!(
serde_json::from_value::<
@@ -1073,10 +1073,10 @@ mod tests {
"meta must deserialize back into BashCommandHighlights"
);
// Gate off → no meta (no allow-always-command row to scope).
let off = prompter_with_gate(ClientType::GrokPager, false);
let off = prompter_with_gate(ClientType::KigiPager, false);
assert!(off.bash_selection_meta(&access).is_none());
// Generic client never gets the fancy-UI meta, even with the gate on.
let generic = prompter_with_gate(ClientType::GrokWeb, true);
let generic = prompter_with_gate(ClientType::KigiWeb, true);
assert!(generic.bash_selection_meta(&access).is_none());
// Non-bash access never carries bash meta.
assert!(
@@ -1087,7 +1087,7 @@ mod tests {
#[test]
fn bash_reject_always_command_maps_selected_words() {
let p = prompter(ClientType::GrokPager);
let p = prompter(ClientType::KigiPager);
let access = AccessKind::Bash("cargo test --workspace".to_owned());
let opts = p.build_options(&access);
// Pager path: the ←/→ word-scope selection arrives as
@@ -1121,7 +1121,7 @@ mod tests {
#[test]
fn gate_off_keeps_edit_session_allow() {
// The edit session allow is governed separately, not by this gate.
let p = prompter_with_gate(ClientType::GrokPager, false);
let p = prompter_with_gate(ClientType::KigiPager, false);
let access = AccessKind::Edit("src/main.rs".to_owned());
let opts = p.build_options(&access);
assert!(
@@ -1142,7 +1142,7 @@ mod tests {
#[test]
fn mcp_prompt_includes_allow_always_with_meta() {
let p = prompter(ClientType::GrokTUI);
let p = prompter(ClientType::KigiTUI);
let access = AccessKind::MCPTool {
name: "linear__list".to_owned(),
input: serde_json::Value::Null,
@@ -1161,7 +1161,7 @@ mod tests {
#[test]
fn mcp_prompt_no_separator_hides_server_scope() {
let p = prompter(ClientType::GrokPager);
let p = prompter(ClientType::KigiPager);
let access = AccessKind::MCPTool {
name: "standalone".to_owned(),
input: serde_json::Value::Null,
@@ -1178,7 +1178,7 @@ mod tests {
#[test]
fn mcp_response_tool_scope() {
let p = prompter(ClientType::GrokPager);
let p = prompter(ClientType::KigiPager);
let access = AccessKind::MCPTool {
name: "linear__list".to_owned(),
input: serde_json::Value::Null,
@@ -1197,7 +1197,7 @@ mod tests {
#[test]
fn mcp_response_server_scope() {
let p = prompter(ClientType::GrokPager);
let p = prompter(ClientType::KigiPager);
let access = AccessKind::MCPTool {
name: "linear__list".to_owned(),
input: serde_json::Value::Null,
@@ -1216,7 +1216,7 @@ mod tests {
#[test]
fn mcp_response_empty_server_falls_back_to_tool() {
let p = prompter(ClientType::GrokPager);
let p = prompter(ClientType::KigiPager);
let access = AccessKind::MCPTool {
name: "linear__list".to_owned(),
input: serde_json::Value::Null,
@@ -1238,7 +1238,7 @@ mod tests {
// TUI / Desktop case: option id is `allow-always-mcp` but the renderer
// does not build the toggle meta. The prompter must default to
// tool-scope using the access-kind name.
let p = prompter(ClientType::GrokTUI);
let p = prompter(ClientType::KigiTUI);
let access = AccessKind::MCPTool {
name: "notion__fetch".to_owned(),
input: serde_json::Value::Null,
@@ -1253,7 +1253,7 @@ mod tests {
#[test]
fn mcp_fallback_client_returns_plain_allow_always() {
// non-TUI clients (Generic / GrokWeb / Extension / …) see `fallback_options`. The
// non-TUI clients (Generic / KigiWeb / Extension / …) see `fallback_options`. The
// legacy `"always-allow"` id maps to plain `PromptOutcome::AllowAlways`;
// the manager arm persists tool-scope from there.
let p = prompter(ClientType::Generic);
@@ -1303,7 +1303,7 @@ mod tests {
fn mcp_titleize_segment_handles_snake_camel_kebab() {
// snake_case → words split + each title-cased
assert_eq!(mcp_titleize_segment("list_issues"), "List Issues");
assert_eq!(mcp_titleize_segment("grok_com_notion"), "Grok Com Notion");
assert_eq!(mcp_titleize_segment("kigi_com_notion"), "Kigi Com Notion");
// single word: just capitalize first letter
assert_eq!(mcp_titleize_segment("linear"), "Linear");
// camelCase preserved (no `_` to split on, only first letter touched)
@@ -1330,7 +1330,7 @@ mod tests {
/// pin position 0 with an exhaustive enumeration.
#[test]
fn enable_always_approve_is_first_option_for_pager() {
let p = prompter(ClientType::GrokPager);
let p = prompter(ClientType::KigiPager);
let cases: Vec<(&str, AccessKind)> = vec![
("edit", AccessKind::Edit("write".to_owned())),
("bash", AccessKind::Bash("ls -la".to_owned())),
@@ -1362,13 +1362,13 @@ mod tests {
}
}
/// Same pin as above for `GrokTUI` and `Desktop` — both client types
/// Same pin as above for `KigiTUI` and `Desktop` — both client types
/// route the option id through to the YOLO toggle, so both must
/// see it. A copy-paste regression that limits the prepend to one
/// client only would be caught here.
#[test]
fn enable_always_approve_is_first_for_tui_and_desktop() {
for ct in [ClientType::GrokTUI, ClientType::Desktop] {
for ct in [ClientType::KigiTUI, ClientType::Desktop] {
let p = prompter(ct);
let opts = p.build_options(&AccessKind::Edit("write".to_owned()));
assert_eq!(
@@ -1387,7 +1387,7 @@ mod tests {
fn enable_always_approve_omitted_for_non_tui_clients() {
for ct in [
ClientType::Generic,
ClientType::GrokWeb,
ClientType::KigiWeb,
ClientType::Nebula,
ClientType::Extension,
] {
@@ -1406,7 +1406,7 @@ mod tests {
/// state for this id. Pin the override for every access kind.
#[test]
fn enable_always_approve_maps_to_allow_once_for_every_access_kind() {
let p = prompter(ClientType::GrokPager);
let p = prompter(ClientType::KigiPager);
let cases: Vec<(&str, AccessKind)> = vec![
("edit", AccessKind::Edit("write".to_owned())),
("bash", AccessKind::Bash("ls".to_owned())),
@@ -1440,7 +1440,7 @@ mod tests {
/// target kind is in play. Pin the kind regardless.
#[test]
fn enable_always_approve_uses_allow_once_kind() {
let p = prompter(ClientType::GrokPager);
let p = prompter(ClientType::KigiPager);
let opts = p.build_options(&AccessKind::Edit("write".to_owned()));
let opt = opts
.get(&enable_always_approve_id())
@@ -1461,7 +1461,7 @@ mod tests {
/// allow-once, reject-once, reject-always-command].
#[test]
fn bash_option_order_toggle_first_reject_always_last() {
let p = prompter(ClientType::GrokPager);
let p = prompter(ClientType::KigiPager);
// "ls" has a parseable primary command, so `allow-always-command`
// will be inserted into the option list.
let access = AccessKind::Bash("ls -la".to_owned());
@@ -71,7 +71,7 @@ fn synthetic_rules_for_default_mode(
}
/// Parse a raw defaultMode string: unknown → [`DefaultPermissionMode::Default`]
/// (fail-safe) with a warn + skip record for `grok inspect`.
/// (fail-safe) with a warn + skip record for `kigi inspect`.
fn parse_default_mode_claiming_scope(
raw: &str,
path: &Path,
@@ -214,7 +214,7 @@ fn load_requirements_permissions() -> Vec<Sourced<PermissionRule>> {
///
/// Returned paths are ordered from repo root (lowest priority) to `cwd`
/// (highest priority), matching `kigi-shell::config::find_project_configs`.
fn find_project_grok_configs(cwd: &Path) -> Vec<PathBuf> {
fn find_project_kigi_configs(cwd: &Path) -> Vec<PathBuf> {
let git_root = git2::Repository::discover(cwd)
.ok()
.and_then(|repo| repo.workdir().map(|p| p.to_path_buf()));
@@ -242,7 +242,7 @@ fn find_project_grok_configs(cwd: &Path) -> Vec<PathBuf> {
configs
}
/// Load `[permission]` rules from native Grok TOML config files:
/// Load `[permission]` rules from native Kigi TOML config files:
///
/// * `~/.kigi/config.toml` (lowest priority)
/// * Each `.kigi/config.toml` from the git repo root down to `cwd`
@@ -272,7 +272,7 @@ fn load_config_toml_permissions(cwd: &Path) -> Vec<Sourced<PermissionRule>> {
}
// Project-scoped configs walking from git root down to cwd.
for path in find_project_grok_configs(cwd) {
for path in find_project_kigi_configs(cwd) {
match kigi_config::load_config_file(&path) {
Ok(value) => rules.extend(extract_toml_permissions(&value, || {
RequirementSource::Config { path: path.clone() }
@@ -303,7 +303,7 @@ fn managed_config_permissions(
// Fallback Resolver
// ═════════════════════════════════════════════════════════════════════════════
/// Resolve permission config, merging native Grok and Claude sources.
/// Resolve permission config, merging native Kigi and Claude sources.
/// Evaluation is order-independent (deny > ask > allow); merge order affects
/// provenance display only.
///
@@ -392,7 +392,7 @@ fn is_admin_source(source: &RequirementSource) -> bool {
}
/// Under the pin, drop untrusted catch-all Allow rules (they substitute for the
/// blocked `--yolo`); keep admin-tier ones. Records each drop for `grok inspect`.
/// blocked `--yolo`); keep admin-tier ones. Records each drop for `kigi inspect`.
fn drop_untrusted_catchall_allows(
rules: Vec<Sourced<PermissionRule>>,
policy_block: Option<&'static str>,
@@ -459,7 +459,7 @@ impl ResolveInputs<'static> {
/// **Always-approve (yolo) is independent of defaultMode:** session always-approve
/// still auto-approves before [`PromptPolicy::Deny`] (`dontAsk`) is consulted,
/// so always-approve outranks `defaultMode` unless
/// bypass is pinned off via grok `requirements.toml`
/// bypass is pinned off via kigi `requirements.toml`
/// (`[ui] disable_bypass_permissions_mode = true`). Pair managed `dontAsk` with
/// that pin when org policy must not be bypassable by `--always-approve`.
pub async fn resolve_permissions_with_provenance(cwd: &Path) -> Option<ResolvedPermissions> {
@@ -540,7 +540,7 @@ async fn resolve_permissions_with_provenance_inner(
// `--allow '*'` is filtered at its own merge site (acp_session).
let all_rules = drop_untrusted_catchall_allows(all_rules, policy_block, &mut skipped);
// Keep skip-only resolutions alive so the drop reaches `grok inspect`; zero
// Keep skip-only resolutions alive so the drop reaches `kigi inspect`; zero
// rules with Ask is a no-op for the evaluator, identical to the `None` arm.
if all_rules.is_empty() && prompt_policy == PromptPolicy::Ask && skipped.is_empty() {
return None;
@@ -627,7 +627,7 @@ fn resolve_claude_settings_inner(
warn!(path = %path.display(), "{}", w);
}
// Rules *or* skip-only parse failures still own provenance for
// `grok inspect` (all-invalid allow/deny/ask must not leave
// `kigi inspect` (all-invalid allow/deny/ask must not leave
// primary_source_path unset and panic below).
if (!cfg.rules.is_empty() || !warnings.is_empty()) && primary_source_path.is_none() {
primary_source_path = Some(path.clone());
@@ -665,7 +665,7 @@ fn resolve_claude_settings_inner(
// A blocked bypass, a claimed defaultMode (incl. typo→default), or skip
// records still resolve (possibly zero rules) so provenance reaches
// `grok inspect` via the outer resolver.
// `kigi inspect` via the outer resolver.
if all_rules.is_empty()
&& prompt_policy == PromptPolicy::Ask
&& !bypass_blocked
@@ -954,10 +954,10 @@ pub const YOLO_PIN_REASON_LEGACY_YOLO: &str =
/// `Some(reason)` iff a requirements layer sets `[ui]
/// disable_bypass_permissions_mode = true` (or legacy `[ui] yolo = false`).
/// Vendor `managed-settings.json` `disableBypassPermissionsMode` is deliberately
/// not consulted: grok must not inherit a host-wide always-approve lockdown from
/// that file. grok still honors that file's permission rules / MCP / marketplace
/// not consulted: kigi must not inherit a host-wide always-approve lockdown from
/// that file. kigi still honors that file's permission rules / MCP / marketplace
/// allowlists, and the user's own `--yolo` / `[ui] permission_mode` / runtime
/// toggle drive always-approve; to disable it in grok use a root-owned
/// toggle drive always-approve; to disable it in kigi use a root-owned
/// `requirements.toml`. Fails open on user-writable layers.
pub fn yolo_disabled_by_policy() -> Option<&'static str> {
let layers = kigi_config::requirements_layers();
@@ -1204,12 +1204,12 @@ impl McpServerAllowlist {
}
}
/// Namespace prefix for managed (grok.com-injected) MCP server names. Defined
/// Namespace prefix for managed (kimi.com-injected) MCP server names. Defined
/// here (shell depends on workspace) and re-exported by shell's `to_managed_name`
/// so the prefix and policy matching never drift.
pub const MANAGED_MCP_PREFIX: &str = "grok_com_";
pub const MANAGED_MCP_PREFIX: &str = "kigi_com_";
/// Max `char` length of a managed runtime name (`grok_com_` + normalized display
/// Max `char` length of a managed runtime name (`kigi_com_` + normalized display
/// name), sized to the 64-char tool-name budget. Shared by `to_managed_name` and
/// `mcp_name_matches` so a long policy `serverName` still matches its truncated
/// runtime name.
@@ -1236,7 +1236,7 @@ fn mcp_server_name(server: &agent_client_protocol::McpServer) -> &str {
/// Match a policy `serverName` against a runtime server name.
///
/// Both sides reduce to one key (strip `grok_com_`, [`normalize_managed_name`],
/// Both sides reduce to one key (strip `kigi_com_`, [`normalize_managed_name`],
/// truncate to the cap) compared by exact equality — never substring, so deny
/// `foo` can't leak onto `foobar`; an empty key never matches.
fn mcp_name_matches(pattern: &str, name: &str) -> bool {
@@ -1942,7 +1942,7 @@ mod tests {
let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let home = tempfile::tempdir().unwrap();
let _home_guard = EnvVarGuard::set("KIGI_SHARE_DIR", home.path());
let _marker_guard = EnvVarGuard::unset("_GROK_CLAUDE_MARKER_OVERRIDE");
let _marker_guard = EnvVarGuard::unset("_KIGI_CLAUDE_MARKER_OVERRIDE");
let tmp = tempfile::tempdir().unwrap();
let claude_dir = tmp.path().join(".claude");
std::fs::create_dir_all(&claude_dir).unwrap();
@@ -1976,7 +1976,7 @@ mod tests {
let home = tempfile::tempdir().unwrap();
let _home_guard = EnvVarGuard::set("KIGI_SHARE_DIR", home.path());
let _real_home_guard = EnvVarGuard::set("HOME", home.path());
let _marker_guard = EnvVarGuard::unset("_GROK_CLAUDE_MARKER_OVERRIDE");
let _marker_guard = EnvVarGuard::unset("_KIGI_CLAUDE_MARKER_OVERRIDE");
let tmp = tempfile::tempdir().unwrap();
let env = load_claude_env_with_project(tmp.path(), true);
assert!(env.is_empty());
@@ -1992,7 +1992,7 @@ mod tests {
let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let home = tempfile::tempdir().unwrap();
let _home_guard = EnvVarGuard::set("KIGI_SHARE_DIR", home.path());
let _marker_guard = EnvVarGuard::unset("_GROK_CLAUDE_MARKER_OVERRIDE");
let _marker_guard = EnvVarGuard::unset("_KIGI_CLAUDE_MARKER_OVERRIDE");
let tmp = tempfile::tempdir().unwrap();
let claude_dir = tmp.path().join(".claude");
std::fs::create_dir_all(&claude_dir).unwrap();
@@ -2431,11 +2431,11 @@ mod tests {
fn mcp_name_matches_strips_managed_prefix_both_sides_exactly() {
// Exact match after stripping the prefix — never substring.
assert!(mcp_name_matches("foo", "foo"));
assert!(mcp_name_matches("foo", "grok_com_foo"));
assert!(mcp_name_matches("grok_com_foo", "foo"));
assert!(mcp_name_matches("grok_com_foo", "grok_com_foo"));
assert!(mcp_name_matches("foo", "kigi_com_foo"));
assert!(mcp_name_matches("kigi_com_foo", "foo"));
assert!(mcp_name_matches("kigi_com_foo", "kigi_com_foo"));
assert!(!mcp_name_matches("foo", "foobar"));
assert!(!mcp_name_matches("foo", "grok_com_foobar"));
assert!(!mcp_name_matches("foo", "kigi_com_foobar"));
assert!(!mcp_name_matches("foo", "barfoo"));
assert!(!mcp_name_matches("foo", "bar"));
assert!(!mcp_name_matches("", "foo"));
@@ -2453,14 +2453,14 @@ mod tests {
fn mcp_name_matches_is_case_and_space_insensitive() {
// A display-cased policy serverName matches to_managed_name's normalized
// runtime name, for managed and local servers alike.
assert!(mcp_name_matches("Slack", "grok_com_slack"));
assert!(mcp_name_matches("My Server", "grok_com_my_server"));
assert!(mcp_name_matches("grok_com_my_server", "My Server"));
assert!(mcp_name_matches("Slack", "kigi_com_slack"));
assert!(mcp_name_matches("My Server", "kigi_com_my_server"));
assert!(mcp_name_matches("kigi_com_my_server", "My Server"));
assert!(mcp_name_matches("My Server", "my_server"));
assert!(mcp_name_matches("SLACK", "slack"));
assert!(!mcp_name_matches("My Server", "my_server_2"));
assert!(!mcp_name_matches("", ""));
assert!(!mcp_name_matches("grok_com_", "grok_com_anything"));
assert!(!mcp_name_matches("kigi_com_", "kigi_com_anything"));
}
#[test]
@@ -2503,17 +2503,17 @@ mod tests {
assert!(al.is_server_denied(&bare));
assert!(!al.is_server_allowed(&bare));
let managed = http_named("grok_com_foo", "https://foo.example.com/mcp");
let managed = http_named("kigi_com_foo", "https://foo.example.com/mcp");
assert!(al.is_server_denied(&managed));
assert!(!al.is_server_allowed(&managed));
// Name match is transport-agnostic.
let stdio = stdio_named("grok_com_foo", "npx");
let stdio = stdio_named("kigi_com_foo", "npx");
assert!(al.is_server_denied(&stdio));
assert!(!al.is_server_allowed(&stdio));
// Unrelated names are NOT denied — exact match after strip, never substring.
for unrelated in ["foobar", "grok_com_foobar", "barfoo", "bar"] {
for unrelated in ["foobar", "kigi_com_foobar", "barfoo", "bar"] {
let s = http_named(unrelated, "https://x.example.com/mcp");
assert!(
!al.is_server_denied(&s),
@@ -2536,8 +2536,8 @@ mod tests {
// A name allowlist is transport-agnostic: the named server is allowed on
// any transport regardless of URL/command, others are blocked.
assert!(al.is_server_allowed(&http_named("foo", "https://anything.example.com/x")));
assert!(al.is_server_allowed(&http_named("grok_com_foo", "https://evil.example.com/x")));
assert!(al.is_server_allowed(&stdio_named("grok_com_foo", "/usr/bin/whatever")));
assert!(al.is_server_allowed(&http_named("kigi_com_foo", "https://evil.example.com/x")));
assert!(al.is_server_allowed(&stdio_named("kigi_com_foo", "/usr/bin/whatever")));
let bar_http = http_named("bar", "https://anything.example.com/x");
assert!(!al.is_server_allowed(&bar_http));
@@ -2555,7 +2555,7 @@ mod tests {
for s in [
http_named("foo", "https://foo.example.com/x"),
http_named("grok_com_foo", "https://foo.example.com/x"),
http_named("kigi_com_foo", "https://foo.example.com/x"),
] {
assert!(al.is_server_denied(&s));
assert!(
@@ -2569,13 +2569,13 @@ mod tests {
fn server_name_prefix_edge_cases_vice_versa() {
// Reverse case: prefixed policy vs bare runtime still matches after strip.
let al = allowlist_from(serde_json::json!({
"deniedMcpServers": [ { "serverName": "grok_com_foo" } ]
"deniedMcpServers": [ { "serverName": "kigi_com_foo" } ]
}));
assert!(al.is_server_denied(&http_named("foo", "https://x.example.com/mcp")));
assert!(al.is_server_denied(&http_named("grok_com_foo", "https://x.example.com/mcp")));
assert!(al.is_server_denied(&http_named("kigi_com_foo", "https://x.example.com/mcp")));
assert!(!al.is_server_denied(&http_named("foobar", "https://x.example.com/mcp")));
assert!(!al.is_server_denied(&http_named("grok_com_foobar", "https://x.example.com/mcp")));
assert!(!al.is_server_denied(&http_named("kigi_com_foobar", "https://x.example.com/mcp")));
}
#[test]
@@ -3748,7 +3748,7 @@ mod tests {
skipped
.iter()
.any(|s| s.rule.contains("dontask") || s.rule.contains("defaultMode=")),
"typo should be recorded for grok inspect"
"typo should be recorded for kigi inspect"
);
}
@@ -16,11 +16,11 @@ pub struct PermissionState {
/// Domains the user has approved for `web_fetch`
/// during this session.
pub allowed_web_fetch_domains: HashSet<String>,
/// Exact MCP tool names (e.g. `"grok_com_notion__notion-fetch"`)
/// Exact MCP tool names (e.g. `"kigi_com_notion__notion-fetch"`)
/// the user has granted "always allow" for. Lookup is exact.
pub allowed_mcp_tools: HashSet<String>,
/// MCP server prefixes (everything before the first `__`,
/// e.g. `"grok_com_notion"`) for which the user has granted
/// e.g. `"kigi_com_notion"`) for which the user has granted
/// "always allow" to every tool. Lookup is "tool name starts with
/// `<prefix>__`".
pub allowed_mcp_servers: HashSet<String>,
@@ -299,7 +299,7 @@ mod tests {
let mut state = PermissionState::default();
state
.allowed_mcp_tools
.insert("grok_com_notion__notion-fetch".to_string());
.insert("kigi_com_notion__notion-fetch".to_string());
state
.allowed_mcp_tools
.insert("linear__list_issues".to_string());
@@ -311,7 +311,7 @@ mod tests {
assert!(
restored
.allowed_mcp_tools
.contains("grok_com_notion__notion-fetch")
.contains("kigi_com_notion__notion-fetch")
);
assert!(restored.allowed_mcp_tools.contains("linear__list_issues"));
assert!(restored.allowed_mcp_servers.is_empty());
@@ -322,14 +322,14 @@ mod tests {
let mut state = PermissionState::default();
state
.allowed_mcp_servers
.insert("grok_com_slack".to_string());
.insert("kigi_com_slack".to_string());
state.allowed_mcp_servers.insert("linear".to_string());
let toml_str = toml::to_string_pretty(&state).unwrap();
let restored: PermissionState = toml::from_str(&toml_str).unwrap();
assert_eq!(restored.allowed_mcp_servers.len(), 2);
assert!(restored.allowed_mcp_servers.contains("grok_com_slack"));
assert!(restored.allowed_mcp_servers.contains("kigi_com_slack"));
assert!(restored.allowed_mcp_servers.contains("linear"));
assert!(restored.allowed_mcp_tools.is_empty());
}
@@ -78,36 +78,36 @@ pub enum ClientType {
#[default]
#[serde(
rename = "generic",
alias = "grok-shell",
alias = "grok_shell",
alias = "kigi-shell",
alias = "kigi_shell",
alias = "kigi"
)]
Generic,
/// Grok TUI client - show fancy options with interactive bash term selection
#[serde(rename = "grok-tui", alias = "grok_tui")]
GrokTUI,
/// Grok Web client - identified by clientIdentifier "grok-web"
#[serde(rename = "grok_web")]
GrokWeb,
/// Kigi TUI client - show fancy options with interactive bash term selection
#[serde(rename = "kigi-tui", alias = "kigi_tui")]
KigiTUI,
/// Kigi Web client - identified by clientIdentifier "kigi-web"
#[serde(rename = "kigi_web")]
KigiWeb,
/// Named client (`"nebula"`) — uses the generic permission UI
#[serde(rename = "nebula")]
Nebula,
/// IDE extension client (VS Code and similar) - identified by clientIdentifier "grok-code-extension"
/// IDE extension client (VS Code and similar) - identified by clientIdentifier "kigi-code-extension"
#[serde(rename = "extension")]
Extension,
/// Grok Pager client - TUI-like terminal pager with interactive permission UI.
/// Treated identically to GrokTUI for permission options (gets bash highlights +
/// Kigi Pager client - TUI-like terminal pager with interactive permission UI.
/// Treated identically to KigiTUI for permission options (gets bash highlights +
/// interactive selection). Reports as "pager" for telemetry attribution.
///
/// Accepts both the hyphenated `"grok-pager"` (what the pager actually
/// Accepts both the hyphenated `"kigi-pager"` (what the pager actually
/// sends over the wire, matching `PAGER_CLIENT_TYPE`) and the underscored
/// `"grok_pager"` form for symmetry with the rest of this enum.
#[serde(rename = "grok-pager", alias = "grok_pager")]
GrokPager,
/// Grok Desktop (Electron) client - identified by clientIdentifier "grok-desktop".
/// `"kigi_pager"` form for symmetry with the rest of this enum.
#[serde(rename = "kigi-pager", alias = "kigi_pager")]
KigiPager,
/// Kigi Desktop (Electron) client - identified by clientIdentifier "kigi-desktop".
/// Uses TUI-style bash permission options (primary command extraction + prefix matching)
/// but without interactive `<`/`>` word selection.
#[serde(rename = "grok_desktop")]
#[serde(rename = "kigi_desktop")]
Desktop,
}
impl ClientType {
@@ -118,30 +118,30 @@ impl ClientType {
pub fn user_agent_label(&self) -> &'static str {
match self {
Self::Generic => "kigi",
Self::GrokTUI => "grok-tui",
Self::GrokWeb => "grok-web",
Self::KigiTUI => "kigi-tui",
Self::KigiWeb => "kigi-web",
Self::Nebula => "nebula",
Self::Extension => "grok-code-extension",
Self::GrokPager => "kigi",
Self::Desktop => "grok-desktop",
Self::Extension => "kigi-code-extension",
Self::KigiPager => "kigi",
Self::Desktop => "kigi-desktop",
}
}
/// Resolve from ACP `clientIdentifier` string (e.g. `"grok-web"`, `"grok-desktop"`).
/// Resolve from ACP `clientIdentifier` string (e.g. `"kigi-web"`, `"kigi-desktop"`).
pub fn from_client_identifier(id: Option<&str>) -> Self {
match id {
Some("grok-web") => Self::GrokWeb,
Some("kigi-web") => Self::KigiWeb,
Some("nebula") => Self::Nebula,
Some("grok-code-extension") => Self::Extension,
Some("grok-desktop") => Self::Desktop,
Some("grok-pager") => Self::GrokPager,
Some("kigi-code-extension") => Self::Extension,
Some("kigi-desktop") => Self::Desktop,
Some("kigi-pager") => Self::KigiPager,
_ => Self::Generic,
}
}
/// Label for feedback reporting and experiment filtering.
pub fn feedback_label(&self) -> &'static str {
match self {
Self::GrokTUI | Self::GrokPager => "tui",
Self::GrokWeb => "web",
Self::KigiTUI | Self::KigiPager => "tui",
Self::KigiWeb => "web",
Self::Nebula => "nebula",
Self::Extension => "extension",
Self::Generic => "agent",
@@ -498,7 +498,7 @@ mod tests {
}
#[test]
fn hashline_edit_maps_to_edit_access() {
use kigi_tools::implementations::grok_build_hashline::edit::types::HashlineEditInput;
use kigi_tools::implementations::kigi_hashline::edit::types::HashlineEditInput;
use kigi_tools::types::ToolInput;
let input = ToolInput::HashlineEdit(HashlineEditInput {
file_path: "src/main.rs".into(),
@@ -512,7 +512,7 @@ mod tests {
}
#[test]
fn bash_maps_to_bash_access() {
use kigi_tools::implementations::grok_build::bash::BashToolInput;
use kigi_tools::implementations::kigi::bash::BashToolInput;
use kigi_tools::types::ToolInput;
let input = ToolInput::Bash(BashToolInput {
command: "cargo test".into(),
@@ -543,7 +543,7 @@ mod tests {
}
#[test]
fn monitor_maps_to_bash_access() {
use kigi_tools::implementations::grok_build::monitor::types::MonitorInput;
use kigi_tools::implementations::kigi::monitor::types::MonitorInput;
use kigi_tools::types::ToolInput;
let input = ToolInput::Monitor(MonitorInput {
command: "tail -f /var/log/syslog".into(),
@@ -560,7 +560,7 @@ mod tests {
}
#[test]
fn search_replace_maps_to_edit_access() {
use kigi_tools::implementations::grok_build::search_replace::SearchReplaceInput;
use kigi_tools::implementations::kigi::search_replace::SearchReplaceInput;
use kigi_tools::types::ToolInput;
let input = ToolInput::SearchReplace(SearchReplaceInput {
file_path: "lib.rs".into(),
@@ -576,7 +576,7 @@ mod tests {
}
#[test]
fn web_fetch_maps_to_web_fetch_access() {
use kigi_tools::implementations::grok_build::web_fetch::WebFetchInput;
use kigi_tools::implementations::kigi::web_fetch::WebFetchInput;
use kigi_tools::types::ToolInput;
let input = ToolInput::WebFetch(WebFetchInput {
url: "https://custom.example.com/api".into(),
@@ -590,7 +590,7 @@ mod tests {
}
#[test]
fn web_search_maps_to_web_search_access() {
use kigi_tools::implementations::grok_build::web_search::WebSearchInput;
use kigi_tools::implementations::kigi::web_search::WebSearchInput;
use kigi_tools::types::ToolInput;
let input = ToolInput::WebSearch(WebSearchInput {
query: "rust lang".into(),
@@ -631,13 +631,13 @@ mod tests {
);
}
#[test]
fn client_type_deserializes_grok_shell_as_generic() {
fn client_type_deserializes_kigi_shell_as_generic() {
assert_eq!(
serde_json::from_value::<ClientType>("grok-shell".into()).unwrap(),
serde_json::from_value::<ClientType>("kigi-shell".into()).unwrap(),
ClientType::Generic,
);
assert_eq!(
serde_json::from_value::<ClientType>("grok_shell".into()).unwrap(),
serde_json::from_value::<ClientType>("kigi_shell".into()).unwrap(),
ClientType::Generic,
);
assert_eq!(