§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
@@ -759,7 +759,7 @@ mod tests {
let output = ToolOutput::Todo(TodoWriteOutput::TodosUpdated(TodoWriteSuccess {
summary_for_prompt: "tasks".to_string(),
todos: vec![],
state: kigi_tools::implementations::grok_build::todo::TodoState::default(),
state: kigi_tools::implementations::kigi::todo::TodoState::default(),
}));
let update = acp_tool_update(&output, "call-1", None, None).unwrap();
assert_eq!(update.fields.status, Some(acp::ToolCallStatus::Completed));
@@ -768,7 +768,7 @@ mod tests {
#[test]
fn test_turn_end_plan_cleanup_preserves_semantics_and_priority() {
use crate::tools::todo::plan_entry_from_todo_item;
use kigi_tools::implementations::grok_build::todo::{TodoItem, TodoPriority, TodoStatus};
use kigi_tools::implementations::kigi::todo::{TodoItem, TodoPriority, TodoStatus};
// Simulate a mixed todo list at turn end.
let items = [
@@ -836,13 +836,13 @@ mod tests {
fn test_acp_plan_update_todo() {
let output = ToolOutput::Todo(TodoWriteOutput::TodosUpdated(TodoWriteSuccess {
summary_for_prompt: "tasks".to_string(),
todos: vec![kigi_tools::implementations::grok_build::todo::TodoItem {
todos: vec![kigi_tools::implementations::kigi::todo::TodoItem {
content: "Task 1".to_string(),
priority: kigi_tools::implementations::grok_build::todo::TodoPriority::Medium,
status: kigi_tools::implementations::grok_build::todo::TodoStatus::Completed,
priority: kigi_tools::implementations::kigi::todo::TodoPriority::Medium,
status: kigi_tools::implementations::kigi::todo::TodoStatus::Completed,
meta: None,
}],
state: kigi_tools::implementations::grok_build::todo::TodoState::default(),
state: kigi_tools::implementations::kigi::todo::TodoState::default(),
}));
let plan = acp_plan_update(&output).unwrap();
assert_eq!(plan.entries.len(), 1);
@@ -1,14 +1,14 @@
//! In-process SDK MCP servers over the ACP reverse channel (`x.ai/mcp/sdk_call`).
//! In-process SDK MCP servers over the ACP reverse channel (`kigi/mcp/sdk_call`).
//!
//! The official `grok-agent-sdk` lets a host define in-process tools (`@tool` /
//! The official `kigi-agent-sdk` lets a host define in-process tools (`@tool` /
//! `create_sdk_mcp_server`). When `transport="acp"`, the SDK registers them in
//! `session/new` `_meta["x.ai/mcp/servers"] = [{ "name", "serverId" }]` and the agent
//! `session/new` `_meta["kigi/mcp/servers"] = [{ "name", "serverId" }]` and the agent
//! invokes their tools by sending each MCP JSON-RPC message back to the client as a
//! reverse `x.ai/mcp/sdk_call` request — handled here by [`GatewayAcpInvoker`].
//! reverse `kigi/mcp/sdk_call` request — handled here by [`GatewayAcpInvoker`].
//!
//! NOTE: the *reverse* route (agent -> client, `x.ai/mcp/sdk_call`) invokes a tool that
//! NOTE: the *reverse* route (agent -> client, `kigi/mcp/sdk_call`) invokes a tool that
//! lives in the SDK's process. It is the zero-IPC mirror of the *forward* route (client
//! -> agent, `x.ai/mcp/call` in `extensions::mcp`), which invokes a tool on a server the
//! -> agent, `kigi/mcp/call` in `extensions::mcp`), which invokes a tool on a server the
//! AGENT is connected to. They use distinct method strings and sit on opposite request
//! handlers, so they never collide.
@@ -20,7 +20,7 @@ use kigi_mcp::acp_transport::AcpReverseInvoker;
use kigi_mcp::servers::AcpServerEntry;
use kigi_mcp::wire;
/// Parse `_meta["x.ai/mcp/servers"]` into [`AcpServerEntry`] registrations. Each entry
/// Parse `_meta["kigi/mcp/servers"]` into [`AcpServerEntry`] registrations. Each entry
/// is deserialized directly into the canonical type (so the `serverId` wire field is
/// serde-checked, not hand-read); entries missing `name`/`serverId` are skipped with a
/// warning. A name seen twice keeps the first (server names are the tool namespace, so a
@@ -38,12 +38,12 @@ pub fn parse_acp_mcp_servers(meta: Option<&acp::Meta>) -> Vec<AcpServerEntry> {
let server: AcpServerEntry = match serde_json::from_value(entry.clone()) {
Ok(server) => server,
Err(err) => {
tracing::warn!(entry = %entry, %err, "ignoring malformed x.ai/mcp/servers entry");
tracing::warn!(entry = %entry, %err, "ignoring malformed kigi/mcp/servers entry");
continue;
}
};
if !seen.insert(server.name.clone()) {
tracing::warn!(name = %server.name, "ignoring duplicate x.ai/mcp/servers entry");
tracing::warn!(name = %server.name, "ignoring duplicate kigi/mcp/servers entry");
continue;
}
servers.push(server);
@@ -53,7 +53,7 @@ pub fn parse_acp_mcp_servers(meta: Option<&acp::Meta>) -> Vec<AcpServerEntry> {
/// Reverse-RPC invoker for in-process SDK MCP servers.
///
/// Each [`invoke`](AcpReverseInvoker::invoke) sends one `x.ai/mcp/sdk_call` reverse request
/// Each [`invoke`](AcpReverseInvoker::invoke) sends one `kigi/mcp/sdk_call` reverse request
/// straight through the gateway. `AcpAgentGatewaySender::send` returns a `Send` future
/// (unlike the `?Send` `acp::Client::ext_method` trait method), so the rmcp transport's
/// `Send` invoker bound is satisfied with no relay task. Calls are independent and may
@@ -68,7 +68,7 @@ impl GatewayAcpInvoker {
}
}
/// Reverse `x.ai/mcp/sdk_call` params. Declares the on-wire field names once (mirrors
/// Reverse `kigi/mcp/sdk_call` params. Declares the on-wire field names once (mirrors
/// the forward side's typed `McpCallRequest`) so the `serverId` literal isn't hand-spelled.
#[derive(serde::Serialize)]
struct SdkCallParams<'a> {
@@ -111,7 +111,7 @@ mod tests {
#[test]
fn parses_valid_entries_and_skips_malformed() {
let meta = serde_json::json!({
"x.ai/mcp/servers": [
"kigi/mcp/servers": [
{ "name": "harness-tools", "serverId": "srv_0" },
{ "name": "missing-id" },
{ "serverId": "no_name" },
@@ -126,7 +126,7 @@ mod tests {
#[test]
fn duplicate_names_keep_the_first() {
let meta = serde_json::json!({
"x.ai/mcp/servers": [
"kigi/mcp/servers": [
{ "name": "tools", "serverId": "srv_0" },
{ "name": "tools", "serverId": "srv_1" },
]
@@ -61,7 +61,7 @@ use kigi_sampler::SamplerConfig as SamplingConfig;
use kigi_sampling_types::truncate_bytes;
use kigi_tools::computer::local::LocalTerminalBackend;
use kigi_tools::implementations::BashToolInput;
use kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig;
use kigi_tools::implementations::kigi::web_fetch::WebFetchConfig;
use kigi_tools::types::ToolInput;
use kigi_tools::types::compat::CompatConfig;
use kigi_tools::types::output::{
@@ -178,7 +178,7 @@ mod spawn;
use super::acp_types::*;
pub use spawn::SessionThread;
pub(crate) use spawn::*;
/// Client-registered hook gates (the `x.ai/hooks/run` reverse request).
/// Client-registered hook gates (the `kigi/hooks/run` reverse request).
mod hooks;
pub(crate) struct InputItem {
pub(crate) prompt_id: String,
@@ -461,7 +461,7 @@ pub(crate) struct SessionActor {
/// Server-side doom-loop check policy, resolved once at spawn by
/// `Config::resolve_doom_loop_recovery`; `None` = disabled.
/// `reconstruct_full_config` threads it into the sampler config, and the
/// sampler itself sends the matching `x-grok-doom-loop-check` header.
/// sampler itself sends the matching `x-kigi-doom-loop-check` header.
pub(crate) doom_loop_recovery: Option<kigi_sampling_types::DoomLoopRecoveryPolicy>,
/// Telemetry-only per-turn doom-loop recovery tally (attempts, whether a
/// budget-spent accept happened, tightest trigger label). Accumulated by
@@ -524,10 +524,10 @@ pub(crate) struct SessionActor {
/// Wrapped in `RefCell` for mid-session mutation (skill refresh, prompt regen).
/// Safe: session actor is single-threaded (LocalSet), no concurrent access.
pub(crate) agent: std::cell::RefCell<kigi_agent::Agent>,
/// Dedup slot for `x.ai/git_head_changed`, shared with the fs-watch
/// Dedup slot for `kigi/git_head_changed`, shared with the fs-watch
/// `GitHead` consumer (see `git_head_dedup_key`).
pub(crate) last_reported_branch: Arc<parking_lot::Mutex<Option<String>>>,
/// Client opted into `x.ai/gitHeadChanged`. When false (headless/SDK),
/// Client opted into `kigi/gitHeadChanged`. When false (headless/SDK),
/// `maybe_notify_git_branch` no-ops — no git subprocess.
git_head_enabled: bool,
/// Shared models manager for etag-triggered refresh from response headers.
@@ -615,7 +615,7 @@ pub(crate) struct SessionActor {
pub(crate) goal_update_rx: std::cell::RefCell<
Option<
tokio::sync::mpsc::UnboundedReceiver<
kigi_tools::implementations::grok_build::update_goal::UpdateGoalEnvelope,
kigi_tools::implementations::kigi::update_goal::UpdateGoalEnvelope,
>,
>,
>,
@@ -624,7 +624,7 @@ pub(crate) struct SessionActor {
/// empty ToolBridge. The `rx` half is owned by the drainer task (see
/// `goal_update_rx`).
pub(crate) goal_update_tx: tokio::sync::mpsc::UnboundedSender<
kigi_tools::implementations::grok_build::update_goal::UpdateGoalEnvelope,
kigi_tools::implementations::kigi::update_goal::UpdateGoalEnvelope,
>,
/// Resolved master kill-switch for the verification stage (the
/// adversarial skeptic panel). `false` short-circuits
@@ -690,7 +690,7 @@ pub(crate) struct SessionActor {
/// time; only the input is parked here for the TurnEnd drain to
/// run through the verification stage.
pub(crate) pending_classifier_completions: parking_lot::Mutex<
VecDeque<kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput>,
VecDeque<kigi_tools::implementations::kigi::update_goal::UpdateGoalInput>,
>,
/// Per-session re-entry guard for the verification stage. Set with
/// `compare_exchange(false, true)` at fire-entry and cleared on
@@ -742,7 +742,7 @@ pub(crate) struct SessionActor {
/// Wrapped in `RefCell` for mid-session reload from `&self` methods.
/// Safe: session actor is single-threaded (LocalSet), no concurrent access.
pub(crate) hook_registry: std::cell::RefCell<Option<Arc<kigi_hooks::discovery::HookRegistry>>>,
/// Client hooks from `session/new` `_meta["x.ai/hooks"]`; gated in
/// Client hooks from `session/new` `_meta["kigi/hooks"]`; gated in
/// [`crate::session::acp_session::hooks`]. `RefCell` so `load_session` reconnect can
/// replace the set on the live actor (see `SessionCommand::SetClientHooks`).
pub(crate) client_hooks: std::cell::RefCell<crate::extensions::hooks::ClientHooks>,
@@ -981,7 +981,7 @@ impl SessionActor {
memory_configured: self.memory.backend_params.is_some(),
scheduler: tool_names
.iter()
.any(|n| n == kigi_tools::implementations::grok_build::SCHEDULER_CREATE_TOOL_NAME),
.any(|n| n == kigi_tools::implementations::kigi::SCHEDULER_CREATE_TOOL_NAME),
hooks: self.hook_registry.borrow().is_some(),
plugins: self.plugin_registry.borrow().is_some(),
goal,
@@ -1039,7 +1039,7 @@ const PROMPT_CONTEXT_FILENAME: &str = "prompt_context.json";
/// Persist the structured prompt context to `{session_dir}/prompt_context.json`.
///
/// This is best-effort: failures are logged but do not block session creation.
/// The saved JSON enables deterministic re-rendering, `grok prompt --json`
/// The saved JSON enables deterministic re-rendering, `kigi prompt --json`
/// inspection, and post-hoc debugging of what went into a session's system prompt.
fn save_prompt_context(session_info: &SessionInfo, prompt_context: &kigi_agent::PromptContext) {
let dir = crate::session::persistence::session_dir(session_info);
@@ -1232,7 +1232,7 @@ mod turn_completion_emit_tests;
mod usage_categories_tests;
#[cfg(test)]
mod tool_meta_stamp_tests {
//! Pin the `x.ai/tool` stamps on the harness emission paths: the early
//! Pin the `kigi/tool` stamps on the harness emission paths: the early
//! ToolCall registered by `prepare_tool_call` and the permission-request
//! ToolCallUpdate (a dropped `stamp_tool_meta` call would regress silently).
use super::replay_buffer_send_update_tests::make_replay_send_update_fixture;
@@ -1252,7 +1252,7 @@ mod tool_meta_stamp_tests {
},
}
}
/// The `x.ai/tool` object from an event's `_meta`, if present.
/// The `kigi/tool` object from an event's `_meta`, if present.
fn tool_meta(meta: Option<&acp::Meta>) -> Option<&serde_json::Value> {
meta.and_then(|m| m.get(TOOL_META_KEY))
}
@@ -1263,10 +1263,8 @@ mod tool_meta_stamp_tests {
.run_until(async {
let mut fixture = make_replay_send_update_fixture().await;
fixture.actor.agent = std::cell::RefCell::new(
test_agent_with_tools(vec![ToolConfig::from_id(
"GrokBuild:read_file".to_string(),
)])
.await,
test_agent_with_tools(vec![ToolConfig::from_id("Kigi:read_file".to_string())])
.await,
);
let prepared = fixture
.actor
@@ -1289,13 +1287,13 @@ mod tool_meta_stamp_tests {
}
}
let early = early.expect("early ToolCall emitted");
let t = tool_meta(early.as_ref()).expect("early ToolCall carries x.ai/tool");
let t = tool_meta(early.as_ref()).expect("early ToolCall carries kigi/tool");
assert_eq!(t["name"], "read_file");
assert_eq!(t["kind"], "read");
assert_eq!(t["namespace"], "grok_build");
assert_eq!(t["namespace"], "kigi");
assert!(t.get("input").is_none(), "identity-only before parse");
let refined = refined.expect("refinement ToolCallUpdate emitted");
let t = tool_meta(refined.as_ref()).expect("refinement carries x.ai/tool");
let t = tool_meta(refined.as_ref()).expect("refinement carries kigi/tool");
assert_eq!(t["input"]["path"], "/tmp/stamp.txt");
})
.await;
@@ -1307,10 +1305,8 @@ mod tool_meta_stamp_tests {
.run_until(async {
let mut fixture = make_replay_send_update_fixture().await;
fixture.actor.agent = std::cell::RefCell::new(
test_agent_with_tools(vec![ToolConfig::from_id(
"GrokBuild:read_file".to_string(),
)])
.await,
test_agent_with_tools(vec![ToolConfig::from_id("Kigi:read_file".to_string())])
.await,
);
let (perm_tx, mut perm_rx) = mpsc::unbounded_channel();
fixture.actor.permissions = PermissionHandle::Actor {
@@ -1350,7 +1346,7 @@ mod tool_meta_stamp_tests {
.take()
.expect("permission request must have been issued");
let t = tool_meta(update.meta.as_ref())
.expect("permission-request ToolCallUpdate carries x.ai/tool");
.expect("permission-request ToolCallUpdate carries kigi/tool");
assert_eq!(t["name"], "read_file");
assert_eq!(t["kind"], "read");
assert_eq!(t["input"]["path"], "/tmp/stamp.txt");
@@ -1,11 +1,11 @@
//! Client-registered hooks for [`SessionActor`].
//!
//! Hooks registered at `session/new` (`_meta["x.ai/hooks"]`) come in two flavors,
//! Hooks registered at `session/new` (`_meta["kigi/hooks"]`) come in two flavors,
//! both matched by the agent ([`kigi_hooks::matcher::HookMatcher`], shared with
//! file hooks):
//! - **`PreToolUse` gate**: an awaited reverse *request* `x.ai/hooks/run`; a `deny`
//! - **`PreToolUse` gate**: an awaited reverse *request* `kigi/hooks/run`; a `deny`
//! blocks the tool.
//! - **All other events**: fire-and-forget *notifications* `x.ai/hooks/event`,
//! - **All other events**: fire-and-forget *notifications* `kigi/hooks/event`,
//! observe-only (the callback's return is ignored). Sent per matching callback.
use std::sync::Arc;
@@ -23,10 +23,10 @@ use crate::extensions::hooks::{
};
use crate::sampling::types::ToolCallResponse;
const HOOK_EVENT_METHOD: &str = "x.ai/hooks/event";
const HOOK_RUN_METHOD: &str = "x.ai/hooks/run";
const HOOK_EVENT_METHOD: &str = "kigi/hooks/event";
const HOOK_RUN_METHOD: &str = "kigi/hooks/run";
/// Default per-callback bound for a client's `x.ai/hooks/run` reply; on timeout the gate
/// Default per-callback bound for a client's `kigi/hooks/run` reply; on timeout the gate
/// fails open (the tool proceeds).
///
/// Some external hosts default to 600s per hook; we default to 30s because our gate sits
@@ -47,7 +47,7 @@ enum ClientHookGateOutcome {
UnknownDecision,
}
/// Outcome of the `x.ai/hooks/run` reverse request, before interpreting it as a
/// Outcome of the `kigi/hooks/run` reverse request, before interpreting it as a
/// decision. Separate so [`classify`] stays pure and unit-testable.
enum ReverseOutcome {
Responded(Arc<RawValue>),
@@ -68,7 +68,7 @@ fn classify(outcome: ReverseOutcome) -> (ClientHookResponse, ClientHookGateOutco
ClientHookDecision::Deny => ClientHookGateOutcome::Denied,
ClientHookDecision::Other => {
tracing::warn!(
"x.ai/hooks/run returned an unknown decision value; failing open"
"kigi/hooks/run returned an unknown decision value; failing open"
);
ClientHookGateOutcome::UnknownDecision
}
@@ -77,7 +77,7 @@ fn classify(outcome: ReverseOutcome) -> (ClientHookResponse, ClientHookGateOutco
(resp, label)
}
Err(err) => {
tracing::warn!(%err, "malformed x.ai/hooks/run response; failing open");
tracing::warn!(%err, "malformed kigi/hooks/run response; failing open");
(
ClientHookResponse::default(),
ClientHookGateOutcome::Malformed,
@@ -86,14 +86,14 @@ fn classify(outcome: ReverseOutcome) -> (ClientHookResponse, ClientHookGateOutco
}
}
ReverseOutcome::Transport(err) => {
tracing::warn!(%err, "x.ai/hooks/run transport error (no client wired?); failing open");
tracing::warn!(%err, "kigi/hooks/run transport error (no client wired?); failing open");
(
ClientHookResponse::default(),
ClientHookGateOutcome::TransportError,
)
}
ReverseOutcome::Timeout => {
tracing::warn!("x.ai/hooks/run timed out; failing open");
tracing::warn!("kigi/hooks/run timed out; failing open");
(
ClientHookResponse::default(),
ClientHookGateOutcome::TimedOut,
@@ -206,7 +206,7 @@ impl SessionActor {
}
/// Run the client-registered `PreToolUse` hooks for `call`, firing
/// `x.ai/hooks/run` once per matching callback with the shared `envelope` (the
/// `kigi/hooks/run` once per matching callback with the shared `envelope` (the
/// same payload file hooks and observe events receive).
///
/// Returns `Some(ToolLoop::HookDenied)` on the first deny, else `None`.
@@ -285,7 +285,7 @@ impl SessionActor {
Ok(None)
}
/// Issue one `x.ai/hooks/run` reverse request, bounded by a per-callback `timeout`.
/// Issue one `kigi/hooks/run` reverse request, bounded by a per-callback `timeout`.
async fn send_hook_run(
&self,
dispatch: &ClientHookDispatch<'_>,
@@ -305,7 +305,7 @@ impl SessionActor {
}
/// Fire observe-only client hooks for `envelope`'s event: send an
/// `x.ai/hooks/event` notification to each matching registered callback.
/// `kigi/hooks/event` notification to each matching registered callback.
/// Fire-and-forget (no decision is consumed); independent of file hooks, so it
/// runs even when no on-disk hook registry exists. No-op when nothing is registered.
pub(super) fn notify_client_hooks(&self, envelope: &HookEventEnvelope) {
@@ -22,7 +22,7 @@ impl RoleCapability {
/// `can_execute` for terminal/bash).
fn is_satisfied(
self,
summary: &kigi_tools::implementations::grok_build::task::types::SubagentTypeSummary,
summary: &kigi_tools::implementations::kigi::task::types::SubagentTypeSummary,
) -> bool {
match self {
Self::Skeptic => summary.can_read && summary.can_search,
@@ -42,7 +42,7 @@ pub(crate) struct PanelResolveCache {
/// result for the role's `general-purpose` toolset on that harness).
describe: std::collections::HashMap<
String,
kigi_tools::implementations::grok_build::task::types::SubagentDescribeOutcome,
kigi_tools::implementations::kigi::task::types::SubagentDescribeOutcome,
>,
}
@@ -57,7 +57,7 @@ fn role_tool_names_from(
cache: &PanelResolveCache,
inherit: &crate::session::goal_role_tools::RoleToolNames,
) -> crate::session::goal_role_tools::RoleToolNames {
use kigi_tools::implementations::grok_build::task::types::SubagentDescribeOutcome;
use kigi_tools::implementations::kigi::task::types::SubagentDescribeOutcome;
// `override_.agent_type` is the committed harness; the cache is keyed on it.
match override_.agent_type.as_deref() {
Some(harness) => match cache.describe.get(harness) {
@@ -115,9 +115,9 @@ impl SessionActor {
&self,
current_tokens: i64,
purpose: DrainPurpose,
extra: Vec<kigi_tools::implementations::grok_build::update_goal::UpdateGoalEnvelope>,
extra: Vec<kigi_tools::implementations::kigi::update_goal::UpdateGoalEnvelope>,
) {
use kigi_tools::implementations::grok_build::update_goal::{RejectReason, UpdateGoalAck};
use kigi_tools::implementations::kigi::update_goal::{RejectReason, UpdateGoalAck};
// The `update_goal` tool and its `GoalUpdateHandle` are always
// registered (see `spawn_session_actor`), so a model can call
// `update_goal` in a session that never entered goal mode — e.g. any
@@ -650,10 +650,10 @@ impl SessionActor {
attempt: u32,
outcome: crate::session::goal_classifier::GoalClassifierOutcome,
notify: &crate::session::goal_orchestrator::GoalNotifySender,
) -> kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck {
) -> kigi_tools::implementations::kigi::update_goal::UpdateGoalAck {
use crate::session::goal_classifier::GoalClassifierOutcome;
use crate::session::goal_tracker::GoalClassifierVerdict;
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
let current_tokens = self.chat_state_handle.get_total_tokens().await as i64;
let (tokens_used, finished_marginal) = self.goal_tokens(current_tokens);
@@ -1299,7 +1299,7 @@ impl SessionActor {
choice: &crate::agent::config::GoalRoleModelChoice,
capability: RoleCapability,
event_tx: &tokio::sync::mpsc::UnboundedSender<
kigi_tools::implementations::grok_build::task::types::SubagentEvent,
kigi_tools::implementations::kigi::task::types::SubagentEvent,
>,
) -> (
crate::session::goal_planner::RoleSpawnOverride,
@@ -1372,17 +1372,15 @@ impl SessionActor {
pair: &crate::util::config::GoalRoleModel,
capability: RoleCapability,
event_tx: &tokio::sync::mpsc::UnboundedSender<
kigi_tools::implementations::grok_build::task::types::SubagentEvent,
kigi_tools::implementations::kigi::task::types::SubagentEvent,
>,
available_models: &indexmap::IndexMap<String, crate::agent::config::ModelEntry>,
cache: &mut PanelResolveCache,
) -> crate::session::goal_planner::RoleSpawnOverride {
use crate::session::events::{Event, GoalRoleModelFailOpenReason as Reason};
use crate::session::goal_planner::RoleSpawnOverride;
use kigi_tools::implementations::grok_build::task::backend::{
ChannelBackend, SubagentBackend,
};
use kigi_tools::implementations::grok_build::task::types::SubagentDescribeOutcome;
use kigi_tools::implementations::kigi::task::backend::{ChannelBackend, SubagentBackend};
use kigi_tools::implementations::kigi::task::types::SubagentDescribeOutcome;
let fail_open = |reason: Reason| {
self.emit_event(Event::GoalRoleModelFailOpen {
@@ -1408,7 +1406,7 @@ impl SessionActor {
}
// 2b. Reject a STRICT harness whose flavor isn't representable (e.g.
// `codex`): it resolves, but `resolve_subagent_toolset` would
// silently run grok-build flavor. Non-strict names (grok-build
// silently run kigi flavor. Non-strict names (kigi
// family) run the default flavor and pass; unresolvable names fall
// through to the describe `Unknown` arm below.
if kigi_agent::config::is_strict_harness_agent_type(&pair.agent_type)
@@ -1511,9 +1509,9 @@ impl SessionActor {
/// toolset; [`RoleToolNames::from_parent`] applies the literal fallback for
/// any kind the bridge lacks, and resolves `{WRITE_TOOL}` from the parent
/// `Edit` tool when the bridge has no `Write` (so the inherit / retry render
/// agrees with `from_summary` — `search_replace` on the default grok-build
/// agrees with `from_summary` — `search_replace` on the default kigi
/// host, not the literal `write`). The `{TOOLSET_TOOLS}` block is empty on
/// this path (no per-role toolset enumeration); on the default grok-build
/// this path (no per-role toolset enumeration); on the default kigi
/// host the bridge resolves the parent's real tool ids (`read_file`, …).
pub(crate) async fn resolve_inherit_role_tool_names(
&self,
@@ -2340,7 +2338,7 @@ impl SessionActor {
#[cfg(test)]
mod role_capability_tests {
use super::RoleCapability;
use kigi_tools::implementations::grok_build::task::types::SubagentTypeSummary;
use kigi_tools::implementations::kigi::task::types::SubagentTypeSummary;
fn summary(can_read: bool, can_search: bool, can_execute: bool) -> SubagentTypeSummary {
SubagentTypeSummary {
@@ -2371,7 +2369,7 @@ mod role_tool_names_tests {
use super::{PanelResolveCache, role_tool_names_from};
use crate::session::goal_planner::RoleSpawnOverride;
use crate::session::goal_role_tools::RoleToolNames;
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentDescribeOutcome, SubagentTypeSummary,
};
use kigi_tools::types::tool::ToolKind;
@@ -15,10 +15,10 @@ impl DrainSource {
pub(super) fn into_parts(
self,
) -> (
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput,
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput,
Option<
tokio::sync::oneshot::Sender<
kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck,
kigi_tools::implementations::kigi::update_goal::UpdateGoalAck,
>,
>,
) {
@@ -33,11 +33,9 @@ impl DrainSource {
/// source). No-op for `Pending` source — its ack was already resolved.
pub(super) fn try_send_ack(
ack_tx: Option<
tokio::sync::oneshot::Sender<
kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck,
>,
tokio::sync::oneshot::Sender<kigi_tools::implementations::kigi::update_goal::UpdateGoalAck>,
>,
ack: kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck,
ack: kigi_tools::implementations::kigi::update_goal::UpdateGoalAck,
) {
if let Some(tx) = ack_tx {
send_ack(tx, ack);
@@ -154,9 +152,9 @@ impl<F: FnOnce(&mut crate::session::goal_tracker::GoalTracker)> Drop for Tracker
/// the receiver was dropped (benign — tool future aborted).
pub(super) fn send_ack(
ack_tx: tokio::sync::oneshot::Sender<
kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck,
kigi_tools::implementations::kigi::update_goal::UpdateGoalAck,
>,
ack: kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck,
ack: kigi_tools::implementations::kigi::update_goal::UpdateGoalAck,
) {
if ack_tx.send(ack).is_err() {
tracing::debug!("update_goal ack receiver dropped before harness could respond");
@@ -702,14 +700,14 @@ mod fold_tokens_by_model_tests {
#[test]
fn mixed_models_sum_marginals_sorted_desc() {
let records = vec![
rec(Some("g1"), 0, 100, Some("grok-3")),
rec(Some("g1"), 100, 500, Some("grok-4")), // marginal 400
rec(Some("g1"), 0, 50, Some("grok-3")), // grok-3 total 150
rec(Some("g1"), 0, 100, Some("kigi-3")),
rec(Some("g1"), 100, 500, Some("kigi-4")), // marginal 400
rec(Some("g1"), 0, 50, Some("kigi-3")), // kigi-3 total 150
];
let out = fold_tokens_by_model(&records, "g1", "cur");
assert_eq!(
out,
vec![("grok-4".to_owned(), 400), ("grok-3".to_owned(), 150)]
vec![("kigi-4".to_owned(), 400), ("kigi-3".to_owned(), 150)]
);
}
@@ -736,38 +734,38 @@ mod fold_tokens_by_model_tests {
#[test]
fn single_distinct_model_collapses_to_one_entry() {
let records = vec![
rec(Some("g1"), 0, 100, Some("grok-4")),
rec(Some("g1"), 0, 200, None), // folds under current = grok-4
rec(Some("g1"), 0, 100, Some("kigi-4")),
rec(Some("g1"), 0, 200, None), // folds under current = kigi-4
];
let out = fold_tokens_by_model(&records, "g1", "grok-4");
assert_eq!(out, vec![("grok-4".to_owned(), 300)]);
let out = fold_tokens_by_model(&records, "g1", "kigi-4");
assert_eq!(out, vec![("kigi-4".to_owned(), 300)]);
}
#[test]
fn other_goal_records_excluded() {
let records = vec![
rec(Some("g1"), 0, 100, Some("grok-4")),
rec(Some("g2"), 0, 999, Some("grok-4")),
rec(None, 0, 999, Some("grok-4")),
rec(Some("g1"), 0, 100, Some("kigi-4")),
rec(Some("g2"), 0, 999, Some("kigi-4")),
rec(None, 0, 999, Some("kigi-4")),
];
let out = fold_tokens_by_model(&records, "g1", "cur");
assert_eq!(out, vec![("grok-4".to_owned(), 100)]);
assert_eq!(out, vec![("kigi-4".to_owned(), 100)]);
}
#[test]
fn last_below_anchor_does_not_underflow() {
let records = vec![rec(Some("g1"), 500, 100, Some("grok-4"))];
let records = vec![rec(Some("g1"), 500, 100, Some("kigi-4"))];
// marginal saturates to 0 -> skipped as a zero-token entry.
assert!(fold_tokens_by_model(&records, "g1", "cur").is_empty());
}
#[test]
fn captured_model_survives_mid_goal_current_model_switch() {
// A record captured `grok-4` at spawn keeps it even though the
// current model at aggregation time is `grok-3`.
let records = vec![rec(Some("g1"), 0, 100, Some("grok-4"))];
let out = fold_tokens_by_model(&records, "g1", "grok-3");
assert_eq!(out, vec![("grok-4".to_owned(), 100)]);
// A record captured `kigi-4` at spawn keeps it even though the
// current model at aggregation time is `kigi-3`.
let records = vec![rec(Some("g1"), 0, 100, Some("kigi-4"))];
let out = fold_tokens_by_model(&records, "g1", "kigi-3");
assert_eq!(out, vec![("kigi-4".to_owned(), 100)]);
}
#[test]
@@ -788,11 +786,11 @@ mod fold_tokens_by_model_tests {
// An empty id folds into the SAME bucket as records that
// explicitly captured the current model id.
let records = vec![
rec(Some("g1"), 0, 100, Some("grok-4")),
rec(Some("g1"), 0, 100, Some("kigi-4")),
rec(Some("g1"), 0, 200, Some("")),
];
let out = fold_tokens_by_model(&records, "g1", "grok-4");
assert_eq!(out, vec![("grok-4".to_owned(), 300)]);
let out = fold_tokens_by_model(&records, "g1", "kigi-4");
assert_eq!(out, vec![("kigi-4".to_owned(), 300)]);
}
}
@@ -822,7 +820,7 @@ pub(crate) fn planner_failure_pause_message() -> String {
}
pub(crate) fn goal_slash_and_harness_available(goal_enabled: bool, tool_names: &[String]) -> bool {
use kigi_tools::implementations::grok_build::UPDATE_GOAL_TOOL_NAME;
use kigi_tools::implementations::kigi::UPDATE_GOAL_TOOL_NAME;
goal_enabled && tool_names.iter().any(|n| n == UPDATE_GOAL_TOOL_NAME)
}
@@ -1468,9 +1466,7 @@ impl SessionActor {
self.agent
.borrow()
.tool_bridge()
.update_resource(
kigi_tools::implementations::grok_build::task::types::GoalLoopActive(active),
)
.update_resource(kigi_tools::implementations::kigi::task::types::GoalLoopActive(active))
.await;
}
}
@@ -22,7 +22,7 @@ pub(crate) type PendingInterjection =
/// converted into standalone prompt turns (arrived while idle, or after the
/// running turn's final drain). The prefix keeps the turn's user echo
/// persist-only: every pane already rendered the text from the
/// `x.ai/session/interjection` broadcast, so a live echo would duplicate it.
/// `kigi/session/interjection` broadcast, so a live echo would duplicate it.
pub(crate) const INTERJECT_FALLBACK_PROMPT_PREFIX: &str = "interject-fallback-";
impl SessionActor {
@@ -164,7 +164,7 @@ impl SessionActor {
self.notifications
.gateway
.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/session/interjection",
"kigi/session/interjection",
params.into(),
));
}
@@ -323,7 +323,7 @@ impl SessionActor {
/// the sampler call goes through `prepare_chat_completion().conversation_collect()`
/// — a side-channel direct-HTTP call that does NOT publish events
/// on the per-session shared sampler channel. The client never
/// sees "Grok is thinking", streaming token chunks, or any other
/// sees "Kigi is thinking", streaming token chunks, or any other
/// session update from a classifier fire. Stalled verdicts only
/// queue a `<system-reminder>` into chat state via
/// `push_system_reminder`; no `InputItem` is pushed into
@@ -549,18 +549,18 @@ impl SessionActor {
model: Some(model_id.clone()),
temperature: Some(0.0),
max_output_tokens: Some(LAZINESS_MAX_OUTPUT_TOKENS),
// Don't pass `reasoning_effort` — `grok-4.5` (and
// other tool-flavoured Grok variants) reject the field at
// Don't pass `reasoning_effort` — `kigi-4.5` (and
// other tool-flavoured Kigi variants) reject the field at
// the proxy with `400 Bad Request: Model does not support
// parameter reasoningEffort`. Omitting it lets each model
// apply its own default. The classifier task is one short
// JSON object — even on reasoning-capable models the
// default suffices; no need to force it off.
reasoning_effort: None,
x_grok_conv_id: Some(session_id_str.clone()),
x_grok_req_id: Some(format!("{LAZINESS_REQ_ID_PREFIX}{}", uuid::Uuid::new_v4())),
x_grok_session_id: Some(session_id_str),
x_grok_agent_id: Some(crate::util::agent_id::agent_id()),
x_kigi_conv_id: Some(session_id_str.clone()),
x_kigi_req_id: Some(format!("{LAZINESS_REQ_ID_PREFIX}{}", uuid::Uuid::new_v4())),
x_kigi_session_id: Some(session_id_str),
x_kigi_agent_id: Some(crate::util::agent_id::agent_id()),
..ConversationRequest::default()
};
@@ -110,7 +110,7 @@ impl LazinessAbortReason {
/// Prompt-structure mitigations against motivated reasoning:
/// "Do not roleplay", JSON-only, no chain-of-thought,
/// no role context, transcript framed as third-party data.
/// Prefix on `x_grok_req_id` for laziness-classifier sampler calls.
/// Prefix on `x_kigi_req_id` for laziness-classifier sampler calls.
/// Centralised here so the production producer
/// (`maybe_fire_laziness_check`) AND the offline replay harness
/// (`crate::trace_classifier::build_classifier_request`) share a
@@ -150,7 +150,7 @@ impl SessionActor {
);
}
}
/// Emit per-server `x.ai/mcp/tools_changed` notifications.
/// Emit per-server `kigi/mcp/tools_changed` notifications.
///
/// Each emission carries the owning
/// `sessionId` so the pager can route via `find_session_match`
@@ -182,7 +182,7 @@ impl SessionActor {
}
}
}
/// Handle explicit auth trigger from the client (x.ai/mcp/auth_trigger).
/// Handle explicit auth trigger from the client (kigi/mcp/auth_trigger).
///
/// Runs force_reauth (browser flow), then re-initializes the server
/// and registers its tools.
@@ -580,7 +580,7 @@ impl SessionActor {
/// On success: the new `Arc<McpClient>` is in
/// `mcp_state.owned_clients[server]` with `ClientState::Ready`,
/// the dispatcher's `notify_tx` is wired to its
/// `GrokClientHandler`, and the liveness watcher is armed —
/// `KigiClientHandler`, and the liveness watcher is armed —
/// matching the post-handshake state produced by
/// [`Self::ensure_mcp_tools_initialized`] for a fresh server.
///
@@ -603,7 +603,7 @@ impl SessionActor {
/// `Reason::Initialized` from the dispatcher's mapping, one
/// `Reason::RestartSucceeded` from the restart task).
///
/// The `GrokClientHandler` constructed inside `try_handshake`
/// The `KigiClientHandler` constructed inside `try_handshake`
/// holds the SHARED `Arc<Mutex<Option<Sender>>>` slot
/// (`SharedEventTx`), so wiring the sender AFTER the handshake
/// still routes subsequent `tools/list_changed` /
@@ -814,7 +814,7 @@ impl SessionActor {
self.notifications
.gateway
.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/mcp_initialized",
"kigi/mcp_initialized",
params.into(),
));
}
@@ -884,7 +884,7 @@ impl SessionActor {
self.notifications
.gateway
.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/mcp_initialized",
"kigi/mcp_initialized",
params.into(),
));
}
@@ -1432,7 +1432,7 @@ impl SessionActor {
"elapsedMs" : elapsed.as_millis() as u64, }
)) {
gateway.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/mcp_initialized",
"kigi/mcp_initialized",
params.into(),
));
}
@@ -314,10 +314,10 @@ impl SessionActor {
ConversationItem::user(user_message),
],
model: Some(model),
x_grok_conv_id: Some(session_id.clone()),
x_grok_req_id: Some(format!("xai-dream-{}", uuid::Uuid::new_v4())),
x_grok_session_id: Some(session_id),
x_grok_agent_id: Some(crate::util::agent_id::agent_id()),
x_kigi_conv_id: Some(session_id.clone()),
x_kigi_req_id: Some(format!("xai-dream-{}", uuid::Uuid::new_v4())),
x_kigi_session_id: Some(session_id),
x_kigi_agent_id: Some(crate::util::agent_id::agent_id()),
..Default::default()
};
let response = sampling_client
@@ -413,10 +413,10 @@ impl SessionActor {
let request = ConversationRequest {
items,
model: Some(model),
x_grok_conv_id: Some(session_id.clone()),
x_grok_req_id: Some(format!("xai-flush-{}", uuid::Uuid::new_v4())),
x_grok_session_id: Some(session_id.clone()),
x_grok_agent_id: Some(crate::util::agent_id::agent_id()),
x_kigi_conv_id: Some(session_id.clone()),
x_kigi_req_id: Some(format!("xai-flush-{}", uuid::Uuid::new_v4())),
x_kigi_session_id: Some(session_id.clone()),
x_kigi_agent_id: Some(crate::util::agent_id::agent_id()),
..Default::default()
};
@@ -608,7 +608,7 @@ impl SessionActor {
}
/// Rewrite a raw memory note into well-structured markdown via a one-shot
/// LLM call using the `grok-build` model.
/// LLM call using the `kigi` model.
///
/// Follows the same streaming pattern as [`handle_ai_suggest`]: prepares
/// a sampling client, builds a system+user prompt, streams the response,
@@ -207,7 +207,7 @@ impl SessionActor {
}
bridge
.update_resource(
kigi_tools::implementations::grok_build::update_goal::GoalUpdateHandle(
kigi_tools::implementations::kigi::update_goal::GoalUpdateHandle(
self.goal_update_tx.clone(),
),
)
@@ -94,7 +94,7 @@ impl SessionActor {
.borrow()
.tool_bridge()
.update_resource(
kigi_tools::implementations::grok_build::task::types::CurrentPromptIdResource(
kigi_tools::implementations::kigi::task::types::CurrentPromptIdResource(
prompt_id.clone(),
),
)
@@ -249,7 +249,7 @@ impl SessionActor {
let Some(buffer) = &self.tool_context.monitor_event_buffer else {
return;
};
for event in kigi_tools::implementations::grok_build::task::types::drain_owned(
for event in kigi_tools::implementations::kigi::task::types::drain_owned(
buffer,
Some(self.session_info.id.0.as_ref()),
) {
@@ -308,7 +308,7 @@ impl SessionActor {
notifications: Vec<PendingNotification>,
task_output_tool_name: &str,
) -> bool {
use kigi_tools::implementations::grok_build::task::types::MonitorEventNotification;
use kigi_tools::implementations::kigi::task::types::MonitorEventNotification;
// Collapse monitor entries: collect their text into events, remember
// where the first one sat so the batch lands in arrival position.
@@ -277,7 +277,7 @@ pub(super) fn build_truncated_prompt_message(
}
/// Replace the file-referencing offload `notice` embedded in `message` with the
/// no-file [`OFFLOAD_FAILED_NOTICE`]. Position-independent (the notice sits at the
/// end for grok ordering), so a failed offload never
/// end for kigi ordering), so a failed offload never
/// leaves the model chasing a "read this file" pointer to a file that does not
/// exist. Returns `message` unchanged if the notice is absent (defensive).
pub(super) fn strip_offload_notice(message: &str, notice: &str) -> String {
@@ -319,7 +319,7 @@ impl SessionActor {
entry_count = payload.entries.len(),
entries = ?payload.entries.iter().map(|e| e.id.as_str()).collect::<Vec<_>>(),
session = self.session_info.id.0.as_ref(),
"broadcasting x.ai/queue/changed to subscribers",
"broadcasting kigi/queue/changed to subscribers",
);
if let Ok(params) = serde_json::value::to_raw_value(&payload) {
self.notifications
@@ -426,7 +426,7 @@ impl SessionActor {
/// stale `expected_version`, or is owned by another client, or
/// - the row is not a plain prompt (it would reach the model as prompt text).
///
/// Always re-broadcasts `x.ai/queue/changed` so every client reconciles
/// Always re-broadcasts `kigi/queue/changed` so every client reconciles
/// (the row vanishes on success, is unchanged on a no-op).
/// `new_text` (when `Some`) replaces the stored queue text in the
/// interjection — the client edited the row before interjecting. It rides
@@ -609,7 +609,7 @@ impl SessionActor {
/// user has explicitly typed replacement text).
/// 2. Update `queue_meta.text`, bump `queue_meta.version`, and record
/// `last_editor` (the original `owner` attribution is preserved).
/// 3. Re-broadcast `x.ai/queue/changed` so every subscriber renders the
/// 3. Re-broadcast `kigi/queue/changed` so every subscriber renders the
/// new text and version.
///
/// **No-op cases** (each is a benign discard with no rebroadcast — nothing
@@ -110,10 +110,10 @@ impl SessionActor {
tools: tool_specs,
model: Some(model.clone()),
temperature: None,
x_grok_conv_id: Some(btw_session_id.clone()),
x_grok_req_id: Some(format!("xai-btw-{}", uuid::Uuid::new_v4())),
x_grok_session_id: Some(parent_session_id.clone()),
x_grok_agent_id: Some(crate::util::agent_id::agent_id()),
x_kigi_conv_id: Some(btw_session_id.clone()),
x_kigi_req_id: Some(format!("xai-btw-{}", uuid::Uuid::new_v4())),
x_kigi_session_id: Some(parent_session_id.clone()),
x_kigi_agent_id: Some(crate::util::agent_id::agent_id()),
..Default::default()
};
@@ -238,8 +238,8 @@ impl SessionActor {
// ~2540 words, and `clean_recap_text` caps it at a generous
// RECAP_MAX_CHARS safety net, so an explicit token cap isn't needed.
let started_at = chrono::Utc::now().to_rfc3339();
let x_grok_conv_id = format!("recap-{}", uuid::Uuid::new_v4());
let x_grok_req_id = format!("xai-recap-{}", uuid::Uuid::new_v4());
let x_kigi_conv_id = format!("recap-{}", uuid::Uuid::new_v4());
let x_kigi_req_id = format!("xai-recap-{}", uuid::Uuid::new_v4());
// Clone the exact request items for the on-disk artifact (recap never
// mutates conversation state, so this file is the only durable record).
let chat_history_for_artifact = items.clone();
@@ -248,10 +248,10 @@ impl SessionActor {
tools: vec![],
model: Some(model.clone()),
temperature: None,
x_grok_conv_id: Some(x_grok_conv_id.clone()),
x_grok_req_id: Some(x_grok_req_id.clone()),
x_grok_session_id: Some(self.session_info.id.to_string()),
x_grok_agent_id: Some(crate::util::agent_id::agent_id()),
x_kigi_conv_id: Some(x_kigi_conv_id.clone()),
x_kigi_req_id: Some(x_kigi_req_id.clone()),
x_kigi_session_id: Some(self.session_info.id.to_string()),
x_kigi_agent_id: Some(crate::util::agent_id::agent_id()),
..Default::default()
};
@@ -265,8 +265,8 @@ impl SessionActor {
auto,
strip_reasoning,
tag,
&x_grok_req_id,
&x_grok_conv_id,
&x_kigi_req_id,
&x_kigi_conv_id,
started_at,
None,
None,
@@ -291,8 +291,8 @@ impl SessionActor {
auto,
strip_reasoning,
tag,
&x_grok_req_id,
&x_grok_conv_id,
&x_kigi_req_id,
&x_kigi_conv_id,
started_at,
None,
Some(raw_response.as_str()).filter(|s| !s.is_empty()),
@@ -307,7 +307,7 @@ impl SessionActor {
}
// New prompt while generating: keep artifact, skip display, leave watermark.
// Applies to manual `/recap` too: spinner-less clients (e.g. Grok
// Applies to manual `/recap` too: spinner-less clients (e.g. Kigi
// Desktop) would otherwise append the late recap mid-turn.
if self.recap_was_cancelled(recap_epoch) {
tracing::info!(
@@ -322,8 +322,8 @@ impl SessionActor {
auto,
strip_reasoning,
tag,
&x_grok_req_id,
&x_grok_conv_id,
&x_kigi_req_id,
&x_kigi_conv_id,
started_at,
Some(summary.as_str()),
Some(raw_response.as_str()),
@@ -346,8 +346,8 @@ impl SessionActor {
auto,
strip_reasoning,
tag,
&x_grok_req_id,
&x_grok_conv_id,
&x_kigi_req_id,
&x_kigi_conv_id,
started_at,
Some(summary.as_str()),
Some(raw_response.as_str()),
@@ -365,8 +365,8 @@ impl SessionActor {
auto,
strip_reasoning,
tag,
&x_grok_req_id,
&x_grok_conv_id,
&x_kigi_req_id,
&x_kigi_conv_id,
started_at,
Some(summary.as_str()),
Some(raw_response.as_str()),
@@ -433,8 +433,8 @@ impl SessionActor {
auto: bool,
strip_reasoning: bool,
reminder_tag: &str,
x_grok_req_id: &str,
x_grok_conv_id: &str,
x_kigi_req_id: &str,
x_kigi_conv_id: &str,
started_at: String,
summary: Option<&str>,
raw_response: Option<&str>,
@@ -449,8 +449,8 @@ impl SessionActor {
created_at: started_at,
trigger: if auto { "auto" } else { "manual" }.to_owned(),
model: model.to_owned(),
x_grok_req_id: x_grok_req_id.to_owned(),
x_grok_conv_id: x_grok_conv_id.to_owned(),
x_kigi_req_id: x_kigi_req_id.to_owned(),
x_kigi_conv_id: x_kigi_conv_id.to_owned(),
strip_reasoning,
reminder_tag: reminder_tag.to_owned(),
chat_history,
@@ -578,7 +578,7 @@ impl SessionActor {
/// Temperature, max_output_tokens, and
/// reasoning_effort are left unset — mirrors [`Self::handle_recap`]: the
/// proxy may inject provider defaults, a small token cap silently empties
/// a reasoning model's response, and some models (e.g. `grok-build`)
/// a reasoning model's response, and some models (e.g. `kigi`)
/// reject an explicit `reasoningEffort` with a 400. Output is filtered
/// through [`prompt_suggest::sanitize_suggestion`]; any failure returns
/// through [`prompt_suggest::sanitize_suggestion`]; any failure returns
@@ -643,10 +643,10 @@ impl SessionActor {
tools: vec![],
model: Some(model),
temperature: None,
x_grok_conv_id: Some(format!("promptsuggest-{}", uuid::Uuid::new_v4())),
x_grok_req_id: Some(format!("xai-promptsuggest-{}", uuid::Uuid::new_v4())),
x_grok_session_id: Some(self.session_info.id.to_string()),
x_grok_agent_id: Some(crate::util::agent_id::agent_id()),
x_kigi_conv_id: Some(format!("promptsuggest-{}", uuid::Uuid::new_v4())),
x_kigi_req_id: Some(format!("xai-promptsuggest-{}", uuid::Uuid::new_v4())),
x_kigi_session_id: Some(self.session_info.id.to_string()),
x_kigi_agent_id: Some(crate::util::agent_id::agent_id()),
..Default::default()
};
@@ -175,7 +175,7 @@ pub(crate) fn date_rollover_reminder(
}
/// Body of the one-shot interrupt `<system-reminder>` injected on the next real
/// user turn after a mid-stream abort that left the model with no other signal.
/// Wrapped in grok's `<system-reminder>` shape by [`SessionActor::push_system_reminder`].
/// Wrapped in kigi's `<system-reminder>` shape by [`SessionActor::push_system_reminder`].
/// See [`SessionActor::maybe_inject_interrupt_reminder`].
pub(crate) const INTERRUPT_REMINDER: &str = "[Request interrupted by user]";
/// TodoGate when enabled and the prompt carries `<task_completion_discipline>`
@@ -317,7 +317,7 @@ impl SessionActor {
let Some(tx) = &self.tool_context.subagent_event_tx else {
return;
};
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentCompletionsRequest, SubagentEvent,
};
let suppress_ids = self
@@ -525,7 +525,7 @@ impl SessionActor {
.send(PersistenceMsg::MergeRewindPointsFrom { target_index });
}
/// Out-of-band history repair (`x.ai/session/repair`) for a resident
/// Out-of-band history repair (`kigi/session/repair`) for a resident
/// session: run `kigi_chat_state::compaction_utils::repair_history` inside
/// the chat-state actor, then flush persistence so `chat_history.jsonl`
/// is rewritten on disk before the caller sees success.
@@ -386,7 +386,7 @@ pub(super) async fn run_session(
acp::ContentBlock::Text(t) = b { Some(t.text.clone()) } else { None } })
.collect::< Vec < _ >> ().join("\n"); let task_id = source.task_id()
.to_owned(); const MAX_BUFFER_EVENTS : usize = 50; buffer
.push_capped(kigi_tools::implementations::grok_build::task::types::MonitorEventNotification
.push_capped(kigi_tools::implementations::kigi::task::types::MonitorEventNotification
{ task_id : task_id.clone(), event_text, owner_session_id : Some(session
.session_info.id.0.to_string(),), }, MAX_BUFFER_EVENTS,);
tracing::debug!(task_id = % task_id,
@@ -33,7 +33,7 @@ struct SessionTokenAuthGate {
is_session_based: bool,
model_byok: crate::agent::auth_method::ModelByok,
/// Whether the request targets a first-party host. Lets an `Unknown`
/// BYOK status still refresh against cli-chat-proxy / `*.x.ai` without
/// BYOK status still refresh against the first-party cli-chat-proxy hosts without
/// risking a session-token leak to a third-party BYOK endpoint.
endpoint_is_first_party: bool,
}
@@ -419,10 +419,10 @@ impl SessionActor {
kigi_workspace::permission::classifier_output_json_schema(),
),
reasoning_effort: classifier_reasoning_effort,
x_grok_conv_id: Some(session_id.clone()),
x_grok_req_id: Some(format!("xai-perm-auto-{}", uuid::Uuid::new_v4())),
x_grok_session_id: Some(session_id),
x_grok_agent_id: Some(crate::util::agent_id::agent_id()),
x_kigi_conv_id: Some(session_id.clone()),
x_kigi_req_id: Some(format!("xai-perm-auto-{}", uuid::Uuid::new_v4())),
x_kigi_session_id: Some(session_id),
x_kigi_agent_id: Some(crate::util::agent_id::agent_id()),
..ConversationRequest::default()
};
let fut = sampling_client.conversation_collect(request);
@@ -289,7 +289,7 @@ impl SessionActor {
);
}
/// The activation reminder template for the active template (no
/// first-entry/reentry distinction), or grok's reentry/full variant.
/// first-entry/reentry distinction), or kigi's reentry/full variant.
/// Shared by turn-start injection (`inject_plan_mode_reminders` case 1)
/// and the mid-turn toggle (`activate_plan_mode_mid_turn`).
fn plan_activation_template(&self, is_reentry: bool) -> &'static str {
@@ -146,8 +146,8 @@ pub(crate) async fn spawn_session_actor(
inference_idle_timeout_secs: u64,
max_retries: Option<u32>,
web_search_config: kigi_tools::implementations::WebSearchConfig,
web_fetch_config: kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig,
app_builder_deployer_config: kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig,
web_fetch_config: kigi_tools::implementations::kigi::web_fetch::WebFetchConfig,
app_builder_deployer_config: kigi_tools::implementations::kigi::deploy_app::AppBuilderDeployerConfig,
write_file_enabled: bool,
goal_enabled: bool,
subagents_enabled: bool,
@@ -180,7 +180,7 @@ pub(crate) async fn spawn_session_actor(
std::sync::Arc<dyn kigi_tools::computer::types::TerminalBackend>,
>,
parent_scheduler_handle: Option<
kigi_tools::implementations::grok_build::scheduler::types::SchedulerHandle,
kigi_tools::implementations::kigi::scheduler::types::SchedulerHandle,
>,
max_turns: Option<usize>,
forked_tool_override: Option<Vec<ToolSpec>>,
@@ -224,7 +224,7 @@ pub(crate) async fn spawn_session_actor(
"CLI --allow catch-all ignored: always-approve disabled by managed policy"
);
if startup_hints.non_interactive {
eprintln!("grok: --allow catch-all ignored: {reason}");
eprintln!("kigi: --allow catch-all ignored: {reason}");
}
}
if !cli_permission_rules.is_empty() {
@@ -531,7 +531,7 @@ pub(crate) async fn spawn_session_actor(
};
let reminder_policy = resolve_reminder_policy(remote_settings.as_ref(), todo_gate);
let (user_question_tx, user_question_rx) = tokio::sync::mpsc::unbounded_channel::<
kigi_tools::implementations::grok_build::ask_user_question::types::UserQuestionRequest,
kigi_tools::implementations::kigi::ask_user_question::types::UserQuestionRequest,
>();
let attribution_callback_for_spec = auth_manager.as_ref().map(|am| {
crate::auth::attribution::ShellAttribution::new_tool_callback(
@@ -672,7 +672,7 @@ pub(crate) async fn spawn_session_actor(
state.set_acp_servers(acp_mcp_servers, invoker);
tracing::info!(
session_id = % session_info.id.0, acp_mcp_servers = acp_server_count,
"Registered in-process SDK MCP servers (x.ai/mcp/sdk_call)"
"Registered in-process SDK MCP servers (kigi/mcp/sdk_call)"
);
}
Arc::new(TokioMutex::new(state))
@@ -763,7 +763,7 @@ pub(crate) async fn spawn_session_actor(
let scheduler_handle_for_handle = {
let toolset = agent.tool_bridge().toolset();
let res = toolset.resources.lock().await;
res.get::<kigi_tools::implementations::grok_build::scheduler::types::SchedulerHandle>()
res.get::<kigi_tools::implementations::kigi::scheduler::types::SchedulerHandle>()
.cloned()
};
if let Err(e) = workspace_ops.bind_local_session(
@@ -831,13 +831,13 @@ pub(crate) async fn spawn_session_actor(
"Creating feedback manager"
);
let feedback_client_type = match client_type {
ClientType::GrokTUI => crate::session::feedback_types::ClientType::Tui,
ClientType::GrokWeb => crate::session::feedback_types::ClientType::Web,
ClientType::KigiTUI => crate::session::feedback_types::ClientType::Tui,
ClientType::KigiWeb => crate::session::feedback_types::ClientType::Web,
ClientType::Nebula => crate::session::feedback_types::ClientType::Nebula,
ClientType::Extension => crate::session::feedback_types::ClientType::Extension,
ClientType::Generic => crate::session::feedback_types::ClientType::Agent,
ClientType::Desktop => crate::session::feedback_types::ClientType::Desktop,
ClientType::GrokPager => crate::session::feedback_types::ClientType::Tui,
ClientType::KigiPager => crate::session::feedback_types::ClientType::Tui,
};
let feedback_config = FeedbackManagerConfig {
feedback_enabled: feedback_flags.enabled,
@@ -930,7 +930,7 @@ pub(crate) async fn spawn_session_actor(
.map(|e| e.to_string())
.collect();
let (goal_update_tx, goal_update_rx) = tokio::sync::mpsc::unbounded_channel::<
kigi_tools::implementations::grok_build::update_goal::UpdateGoalEnvelope,
kigi_tools::implementations::kigi::update_goal::UpdateGoalEnvelope,
>();
let mut effective_config = crate::config::load_effective_config()
.ok()
@@ -1224,7 +1224,7 @@ pub(crate) async fn spawn_session_actor(
.borrow()
.tool_bridge()
.update_resource(
kigi_tools::implementations::grok_build::update_goal::GoalUpdateHandle(
kigi_tools::implementations::kigi::update_goal::GoalUpdateHandle(
session.goal_update_tx.clone(),
),
)
@@ -1288,7 +1288,7 @@ pub(crate) async fn spawn_session_actor(
}
{
use agent_client_protocol::Client as _;
use kigi_tools::implementations::grok_build::ask_user_question::{
use kigi_tools::implementations::kigi::ask_user_question::{
AskUserQuestionExtRequest, AskUserQuestionExtResponse, UserQuestionError,
UserQuestionResponse,
};
@@ -1300,7 +1300,7 @@ pub(crate) async fn spawn_session_actor(
let mut user_question_rx = user_question_rx;
tokio::task::spawn_local(async move {
while let Some(mut request) = user_question_rx.recv().await {
use kigi_tools::implementations::grok_build::ask_user_question::AskUserQuestionMode;
use kigi_tools::implementations::kigi::ask_user_question::AskUserQuestionMode;
let mode = match *current_prompt_mode.lock() {
PromptMode::Plan => AskUserQuestionMode::Plan,
_ => AskUserQuestionMode::Default,
@@ -1316,7 +1316,7 @@ pub(crate) async fn spawn_session_actor(
"ask_user_question reverse-request must carry a non-empty sessionId (design §5.4)"
);
let ext_request = agent_client_protocol::ExtRequest::new(
"x.ai/ask_user_question",
"kigi/ask_user_question",
serde_json::value::to_raw_value(&ext_req)
.expect("AskUserQuestionExtRequest serialization should not fail")
.into(),
@@ -1521,8 +1521,8 @@ pub(crate) async fn spawn_session_on_thread(
inference_idle_timeout_secs: u64,
max_retries: Option<u32>,
web_search_config: kigi_tools::implementations::WebSearchConfig,
web_fetch_config: kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig,
app_builder_deployer_config: kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig,
web_fetch_config: kigi_tools::implementations::kigi::web_fetch::WebFetchConfig,
app_builder_deployer_config: kigi_tools::implementations::kigi::deploy_app::AppBuilderDeployerConfig,
write_file_enabled: bool,
goal_enabled: bool,
subagents_enabled: bool,
@@ -1556,7 +1556,7 @@ pub(crate) async fn spawn_session_on_thread(
std::sync::Arc<dyn kigi_tools::computer::types::TerminalBackend>,
>,
parent_scheduler_handle: Option<
kigi_tools::implementations::grok_build::scheduler::types::SchedulerHandle,
kigi_tools::implementations::kigi::scheduler::types::SchedulerHandle,
>,
max_turns: Option<usize>,
forked_tool_override: Option<Vec<ToolSpec>>,
@@ -179,7 +179,7 @@ impl SessionActor {
fn cancel_subagents_for_prompt_id(&self, parent_prompt_id: &str) {
if let Some(event_tx) = self.tool_context.subagent_event_tx.clone() {
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentCancelRequest, SubagentCancelTarget, SubagentEvent,
};
let _ = event_tx.send(SubagentEvent::Cancel(SubagentCancelRequest {
@@ -346,7 +346,7 @@ impl SessionActor {
// * normal interactive cancel: remove ONLY the running turn,
// PRESERVING every queued prompt so the `Cancel` handler's
// follow-up `maybe_start_running_task` promotes the new front (the
// user's next queued prompt) and rebroadcasts `x.ai/queue/changed`.
// user's next queued prompt) and rebroadcasts `kigi/queue/changed`.
// The cancelling client does not pull any prompt back into its
// input — the server queue is the single source of truth for what
// runs next. Previously every cancel did `std::mem::take`,
@@ -418,7 +418,7 @@ impl SessionActor {
.borrow()
.tool_bridge()
.update_resource(
kigi_tools::implementations::grok_build::task::types::CurrentPromptIdResource(
kigi_tools::implementations::kigi::task::types::CurrentPromptIdResource(
String::new(),
),
)
@@ -135,7 +135,7 @@ pub(super) fn should_intercept_exit_plan_approval(
pub(super) enum PlanEditGate {
/// Execute normally (plan mode inactive, not an edit, or allowed target).
Allow,
/// Grok-toolset edit outside the plan file (plan-file-only rule).
/// Kigi-toolset edit outside the plan file (plan-file-only rule).
RejectNonPlanFile,
}
/// Gate edit-class tool calls while plan mode is active.
@@ -149,7 +149,7 @@ pub(super) enum PlanEditGate {
/// file is editable in plan mode (plan docs are written with these
/// same tools); everything else is rejected. Pre-existing behavior.
/// - **Compat-toolset `Delete`** is **not** on the markdown carve-out: it maps to
/// `AccessKind::Edit` and is plan-file-only (same as grok edits). Deleting
/// `AccessKind::Edit` and is plan-file-only (same as kigi edits). Deleting
/// an arbitrary `.md` in plan mode must not pass.
/// - **Every other edit tool** (`AccessKind::Edit`) is restricted to the plan
/// file itself, via the same predicate that auto-approves plan-file edits
@@ -192,7 +192,7 @@ pub(super) enum PlanApprovalOutcome {
}
impl PlanApprovalOutcome {
fn from_response(
resp: &kigi_tools::implementations::grok_build::exit_plan_mode::ExitPlanModeExtResponse,
resp: &kigi_tools::implementations::kigi::exit_plan_mode::ExitPlanModeExtResponse,
) -> Self {
match resp.outcome.as_str() {
"approved" => Self::Approved,
@@ -255,7 +255,7 @@ fn resume_action_for(outcome: PlanApprovalOutcome, feedback: Option<String>) ->
}
}
impl SessionActor {
/// Merge the canonical `x.ai/tool` identity envelope into a tool-call
/// Merge the canonical `kigi/tool` identity envelope into a tool-call
/// event's `_meta`, resolving the tool from the live toolset by wire name.
pub(super) fn stamp_tool_meta(
&self,
@@ -1203,7 +1203,7 @@ impl SessionActor {
};
Ok(Ok(prepared))
}
/// Issue the `x.ai/exit_plan_mode` reverse-request and await the user's
/// Issue the `kigi/exit_plan_mode` reverse-request and await the user's
/// decision. Shared by the mid-turn intercept and the resume
/// re-park. Marks `awaiting_plan_approval` while the request is
/// outstanding and clears it on every exit path via [`AwaitingApprovalGuard`].
@@ -1212,11 +1212,11 @@ impl SessionActor {
tool_call_id: &acp::ToolCallId,
plan_content: Option<String>,
) -> Result<
kigi_tools::implementations::grok_build::exit_plan_mode::ExitPlanModeExtResponse,
kigi_tools::implementations::kigi::exit_plan_mode::ExitPlanModeExtResponse,
acp::Error,
> {
use agent_client_protocol::Client as _;
use kigi_tools::implementations::grok_build::exit_plan_mode::{
use kigi_tools::implementations::kigi::exit_plan_mode::{
ExitPlanModeExtRequest, ExitPlanModeExtResponse,
};
let ext_req = ExitPlanModeExtRequest {
@@ -1229,7 +1229,7 @@ impl SessionActor {
"exit_plan_mode reverse-request must carry a non-empty sessionId (design §5.4)"
);
let ext_request = acp::ExtRequest::new(
"x.ai/exit_plan_mode",
"kigi/exit_plan_mode",
serde_json::value::to_raw_value(&ext_req)
.expect("ExitPlanModeExtRequest serialization should not fail")
.into(),
@@ -2540,7 +2540,7 @@ mod plan_mode_edit_gate_tests {
plan_mode_edit_gate(tracker, input, &AccessKind::from(input))
}
fn search_replace(path: &str) -> ToolInput {
use kigi_tools::implementations::grok_build::search_replace::SearchReplaceInput;
use kigi_tools::implementations::kigi::search_replace::SearchReplaceInput;
ToolInput::SearchReplace(SearchReplaceInput {
file_path: path.into(),
old_string: "a".into(),
@@ -2555,10 +2555,10 @@ mod plan_mode_edit_gate_tests {
content: "x".into(),
})
}
/// Grok edit tools are plan-file-only while plan mode is active — the
/// Kigi edit tools are plan-file-only while plan mode is active — the
/// enforcement that makes plan mode read-only even under always-approve.
#[test]
fn grok_edits_outside_plan_file_rejected() {
fn kigi_edits_outside_plan_file_rejected() {
let t = active_tracker();
assert_eq!(
gate(&t, &search_replace("/tmp/src/main.rs")),
@@ -2567,7 +2567,7 @@ mod plan_mode_edit_gate_tests {
assert_eq!(
gate(&t, &write("/tmp/README.md")),
PlanEditGate::RejectNonPlanFile,
"grok tools get no markdown exception — plan file only"
"kigi tools get no markdown exception — plan file only"
);
}
/// The carve-out and the permission bypass share `should_auto_approve_edit`,
@@ -2644,7 +2644,7 @@ mod plan_approval_helper_tests {
PlanApprovalOutcome, ResumeAction, ext_method_no_client, resume_action_for,
revise_plan_message,
};
use kigi_tools::implementations::grok_build::exit_plan_mode::ExitPlanModeExtResponse;
use kigi_tools::implementations::kigi::exit_plan_mode::ExitPlanModeExtResponse;
fn resp(outcome: &str) -> ExitPlanModeExtResponse {
ExitPlanModeExtResponse {
outcome: outcome.into(),
@@ -41,11 +41,11 @@ fn str_arg<'a>(args: &'a serde_json::Value, keys: &[&str]) -> Option<&'a str> {
/// serializing concurrent same-file edits inside `execute_tool_calls`.
///
/// Different toolsets advertise the path under different JSON keys:
/// - `file_path` — grok_build (`search_replace`), opencode (`EditTool`,
/// `WriteTool`, `ReadTool`), codex (`read_file`), grok_build_hashline
/// - `file_path` — kigi (`search_replace`), opencode (`EditTool`,
/// `WriteTool`, `ReadTool`), codex (`read_file`), kigi_hashline
/// (`hashline_edit`)
/// - `path` — alternate edit/read tools
/// - `target_file` — grok_build (`read_file`, via `#[serde(rename)]`)
/// - `target_file` — kigi (`read_file`, via `#[serde(rename)]`)
///
/// Returning the same string for two calls in a batch causes them to share a
/// `tokio::sync::Mutex` and therefore run sequentially in model-emitted order.
@@ -202,7 +202,7 @@ impl SessionActor {
is_background: false,
});
// Bash mode has no model-issued wire name; resolve the toolset's
// execute tool by kind so the x.ai/tool identity still stamps.
// execute tool by kind so the kigi/tool identity still stamps.
let bash_marker = serde_json::json!({"bash_mode": true}).as_object().cloned();
let exec_wire = {
let agent = self.agent.borrow();
@@ -366,7 +366,7 @@ pub(crate) const MAX_ARGS_IN_ERROR: usize = 2_000;
///
/// 1. The normal error description (so the model knows *what* failed).
/// 2. The **original arguments string** the model produced (capped at
/// [`MAX_ARGS_IN_ERROR`] bytes). Without this, grok-shell would sanitize
/// [`MAX_ARGS_IN_ERROR`] bytes). Without this, kigi-shell would sanitize
/// the arguments to `"{}"` before forwarding them to the provider (to
/// avoid 400 errors), so the model would only see an empty object and have
/// to regenerate all its work from scratch.
@@ -60,9 +60,7 @@ impl UsageDrainOutcome {
/// Same policy as freeze's terminal outcome: FG live → fail-closed;
/// sticky and background → report only.
pub(super) fn from_outstanding_reply(
reply: Option<
&kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply,
>,
reply: Option<&kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply>,
) -> Self {
match reply {
None => Self {
@@ -1050,7 +1048,7 @@ impl SessionActor {
let Some(tx) = &self.tool_context.subagent_event_tx else {
return false;
};
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentEvent, SubagentMarkUsageNotAppliedRequest,
};
let (respond_to, ack) = tokio::sync::oneshot::channel();
@@ -1074,12 +1072,12 @@ impl SessionActor {
/// `push_user_message`, NOT `inject_synthetic_user_message`: the latter
/// persists a `UserMessageChunk` to `updates.jsonl`, which resume
/// replays — the raw XML would render as a user prompt. Clients see
/// monitor events only via the structured `x.ai/monitor_event` channel.
/// monitor events only via the structured `kigi/monitor_event` channel.
pub(crate) async fn inject_pending_monitor_events(&self) {
let Some(buffer) = &self.tool_context.monitor_event_buffer else {
return;
};
let mine = kigi_tools::implementations::grok_build::task::types::drain_owned(
let mine = kigi_tools::implementations::kigi::task::types::drain_owned(
buffer,
Some(self.session_info.id.0.as_ref()),
);
@@ -1632,12 +1630,12 @@ impl SessionActor {
)),
);
let mut request = request;
request.x_grok_session_id = Some(self.session_info.id.to_string());
request.x_grok_turn_idx =
request.x_kigi_session_id = Some(self.session_info.id.to_string());
request.x_kigi_turn_idx =
Some(self.chat_state_handle.get_prompt_index().await.to_string());
request.x_grok_agent_id = Some(crate::util::agent_id::agent_id());
if request.x_grok_deployment_id.is_none() {
request.x_grok_deployment_id = crate::managed_config::resolve_deployment_id(
request.x_kigi_agent_id = Some(crate::util::agent_id::agent_id());
if request.x_kigi_deployment_id.is_none() {
request.x_kigi_deployment_id = crate::managed_config::resolve_deployment_id(
crate::managed_config::resolve_deployment_key().as_deref(),
);
}
@@ -2115,7 +2113,7 @@ mod user_echo_broadcast_tests {
);
}
/// Interject-fallback turns are persist-only: every pane already rendered
/// the text from the `x.ai/session/interjection` broadcast, so a live
/// the text from the `kigi/session/interjection` broadcast, so a live
/// echo would duplicate the block.
#[test]
fn interject_fallback_turn_is_persist_only() {
@@ -78,7 +78,7 @@ impl SessionActor {
self.emit_transient_notification(notification);
}
/// Emit `x.ai/git_head_changed` after an edit/shell command that may have
/// Emit `kigi/git_head_changed` after an edit/shell command that may have
/// moved HEAD (e.g. `git checkout`), so clients update their status bar
/// immediately rather than waiting for the debounced fs-watch refresh.
pub(super) async fn maybe_notify_git_branch(&self) {
@@ -112,7 +112,7 @@ impl SessionActor {
main_repo,
};
if let Ok(raw) = serde_json::value::to_raw_value(&params) {
let notification = acp::ExtNotification::new("x.ai/git_head_changed", raw.into());
let notification = acp::ExtNotification::new("kigi/git_head_changed", raw.into());
self.notifications
.gateway
.forward_fire_and_forget(notification);
@@ -123,12 +123,11 @@ impl SessionActor {
pub(super) async fn outstanding_reply_for_prompt(
&self,
prompt_id: &str,
) -> Option<kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply>
{
) -> Option<kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply> {
let Some(tx) = &self.tool_context.subagent_event_tx else {
return Some(Default::default());
};
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentEvent, SubagentOutstandingRequest,
};
let (respond_to, rx) = tokio::sync::oneshot::channel();
@@ -147,9 +146,7 @@ impl SessionActor {
/// Report-level incomplete (error-path attach, tests). Same OR as
/// [`super::turn::UsageDrainOutcome::report_incomplete`].
pub(super) fn usage_incomplete_from_reply(
reply: Option<
&kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply,
>,
reply: Option<&kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply>,
) -> bool {
super::turn::UsageDrainOutcome::from_outstanding_reply(reply).report_incomplete()
}
@@ -158,7 +155,7 @@ impl SessionActor {
let Some(tx) = &self.tool_context.subagent_event_tx else {
return;
};
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentClearUsageNotAppliedRequest, SubagentEvent,
};
let _ = tx.send(SubagentEvent::ClearUsageNotApplied(
@@ -186,7 +183,7 @@ impl SessionActor {
.borrow()
.tool_bridge()
.update_resource(
kigi_tools::implementations::grok_build::task::types::CurrentPromptIdResource(
kigi_tools::implementations::kigi::task::types::CurrentPromptIdResource(
String::new(),
),
)
@@ -257,7 +254,7 @@ impl SessionActor {
// Durable twin of the fire-and-forget `prompt_complete` (emitted from
// `MvpAgent::prompt`): publish the turn's terminal on the persisted +
// replayed `_x.ai/session/update` rail so a viewer that re-attaches
// replayed `_kigi/session/update` rail so a viewer that re-attaches
// mid-turn finalizes from replay instead of stranding on "Waiting…".
// The caller flushed the replay buffer first, so this lands strictly
// after the turn's last `session/update` delta. Emit ONLY for a
@@ -199,8 +199,8 @@ pub(crate) enum DrainPurpose {
/// Origin of a drain entry. `Pending` entries had their acks resolved
/// at defer time; `Channel` entries still carry a live oneshot.
pub(crate) enum DrainSource {
Pending(kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput),
Channel(kigi_tools::implementations::grok_build::update_goal::UpdateGoalEnvelope),
Pending(kigi_tools::implementations::kigi::update_goal::UpdateGoalInput),
Channel(kigi_tools::implementations::kigi::update_goal::UpdateGoalEnvelope),
}
/// Reason a NotAchieved verdict was synthesized without invoking the
@@ -230,7 +230,7 @@ impl SessionActor {
self.notifications
.gateway
.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/session_notification",
"kigi/session_notification",
params.into(),
));
}
@@ -734,7 +734,7 @@ impl SessionActor {
.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());
self.notifications
.gateway
.forward_fire_and_forget(ext_notification);
@@ -355,7 +355,7 @@ async fn proactive_refresh_makes_per_turn_refresh_a_cache_hit() {
fn model_not_found_error() -> kigi_sampler::SamplingErrorInfo {
kigi_sampler::SamplingErrorInfo {
kind: kigi_sampler::SamplingErrorKind::Api,
message: "API error (status 404 Not Found): The model grok-build does not exist or your team does not have access".into(),
message: "API error (status 404 Not Found): The model kigi does not exist or your team does not have access".into(),
status_code: Some(404),
is_retryable: false,
retry_after_secs: None,
@@ -379,7 +379,7 @@ fn model_not_found_error() -> kigi_sampler::SamplingErrorInfo {
fn unauthorized_401_error() -> kigi_sampler::SamplingErrorInfo {
kigi_sampler::SamplingErrorInfo {
kind: kigi_sampler::SamplingErrorKind::Api,
message: "Unauthorized (401) from https://cli-chat-proxy.kigi.com/v1/responses: {\"error\":\"Invalid or expired credentials (auth_kind=bearer, x_xai_token_auth=xai-grok-cli, upstream=Unauthenticated, reason=no auth context)\"}".into(),
message: "Unauthorized (401) from https://cli-chat-proxy.kigi.com/v1/responses: {\"error\":\"Invalid or expired credentials (auth_kind=bearer, x_xai_token_auth=xai-kigi-cli, upstream=Unauthenticated, reason=no auth context)\"}".into(),
status_code: Some(401),
is_retryable: false,
retry_after_secs: None,
@@ -553,7 +553,7 @@ async fn sampler_401_login_method_with_stale_api_key_auth_type_still_recovers()
let (_dir, am) = auth_manager_with_refresher(refresher);
let (actor, _rx) = make_actor_with_method_and_credentials(
Some(am),
"grok.com",
"kimi-code",
kigi_chat_state::AuthType::ApiKey,
"stale-session-jwt".to_string(),
)
@@ -905,7 +905,7 @@ async fn reparented_record_is_noop_without_goal_harness() {
/// subagent(s) completed" reminder.
#[tokio::test(flavor = "current_thread")]
async fn between_turn_drain_suppresses_auto_wake_delivered_subagents() {
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentCompletionSummary, SubagentEvent,
};
use kigi_tools::reminders::task_completion::AutoWakeDeliveredIds;
@@ -1,4 +1,4 @@
use kigi_tools::implementations::grok_build::task::types::SubagentCompletionSummary;
use kigi_tools::implementations::kigi::task::types::SubagentCompletionSummary;
use kigi_tools::reminders::task_completion::format_between_turn_completions;
fn summary(
@@ -778,7 +778,7 @@ async fn cancel_running_task_teardown_clears_running_and_pending_work() {
agent
.tool_bridge()
.update_resource(
kigi_tools::implementations::grok_build::task::types::CurrentPromptIdResource(
kigi_tools::implementations::kigi::task::types::CurrentPromptIdResource(
"running".to_string(),
),
)
@@ -981,7 +981,7 @@ async fn cancel_running_task_teardown_clears_running_and_pending_work() {
actor.cancel_running_task(true, true, false, None).await;
let scoped_prompt_id = bridge
.read_resource::<
kigi_tools::implementations::grok_build::task::types::CurrentPromptIdResource,
kigi_tools::implementations::kigi::task::types::CurrentPromptIdResource,
>()
.await;
assert!(
@@ -1013,7 +1013,7 @@ async fn cancel_running_task_teardown_clears_running_and_pending_work() {
/// the running turn and removes ONLY the running prompt (the front of
/// `pending_inputs`). Every queued prompt is PRESERVED so the `Cancel`
/// handler's follow-up `maybe_start_running_task` promotes the new front (the
/// user's next queued prompt) and rebroadcasts `x.ai/queue/changed`. The
/// user's next queued prompt) and rebroadcasts `kigi/queue/changed`. The
/// cancelling client never pulls a queued prompt back into its input — the
/// server queue is the single source of truth for what runs next.
///
@@ -1756,7 +1756,7 @@ async fn cancel_propagates_to_sampler_handle_so_no_further_emission() {
agent
.tool_bridge()
.update_resource(
kigi_tools::implementations::grok_build::task::types::CurrentPromptIdResource(
kigi_tools::implementations::kigi::task::types::CurrentPromptIdResource(
"running".to_string(),
),
)
@@ -42,9 +42,9 @@ async fn client_hooks_fire_without_file_registry() {
.try_recv()
.expect("client hook must fire with no file registry");
let kigi_acp_lib::AcpClientMessage::ExtNotification(args) = msg else {
panic!("expected an x.ai/hooks/event ext notification");
panic!("expected an kigi/hooks/event ext notification");
};
assert_eq!(args.request.method.as_ref(), "x.ai/hooks/event");
assert_eq!(args.request.method.as_ref(), "kigi/hooks/event");
let params: serde_json::Value =
serde_json::from_str(args.request.params.get()).unwrap();
assert_eq!(params["hookCallbackId"], "cb_0");
@@ -54,7 +54,7 @@ async fn client_hooks_fire_without_file_registry() {
}
/// The PreToolUse gate blocks a tool when a client hook returns `deny`: the reverse
/// `x.ai/hooks/run` request is answered with a deny and `run_pre_tool_use_client_hook`
/// `kigi/hooks/run` request is answered with a deny and `run_pre_tool_use_client_hook`
/// returns `ToolLoop::HookDenied`. Complements the pure `classify` test by covering the
/// gate wiring (the one new path that can block tool execution).
#[tokio::test(flavor = "current_thread")]
@@ -79,7 +79,7 @@ async fn pre_tool_use_client_deny_blocks_the_tool() {
);
*actor.client_hooks.borrow_mut() = client_hooks;
// Answer the x.ai/hooks/run reverse request with a deny; ack the UI
// Answer the kigi/hooks/run reverse request with a deny; ack the UI
// notifications `deny_tool` emits so it can't block the gate.
tokio::task::spawn_local(async move {
while let Some(msg) = gateway_rx.recv().await {
@@ -431,7 +431,7 @@ async fn pre_tool_use_slow_callback_does_not_starve_a_deny() {
/// dispatch error fires only PostToolUseFailure; a successful dispatch fires only
/// PostToolUse. Guards the explicitly-hardened no-double-fire path (the PostToolUse
/// success block routes through `dispatch_hook`, the same as the failure arm). Each
/// post-tool event is observed as a fire-and-forget `x.ai/hooks/event` notification.
/// post-tool event is observed as a fire-and-forget `kigi/hooks/event` notification.
#[tokio::test(flavor = "current_thread")]
async fn post_tool_use_and_failure_never_double_fire() {
let local = tokio::task::LocalSet::new();
@@ -443,7 +443,7 @@ async fn post_tool_use_and_failure_never_double_fire() {
tokio::sync::mpsc::unbounded_channel::<PersistenceMsg>();
let actor = create_test_actor(0, 256_000, 85, gateway_tx, persistence_tx).await;
// The agent's tool bridge must know `todo_write` for it to parse + dispatch.
*actor.agent.borrow_mut() = test_grok_build_agent_with_todo().await;
*actor.agent.borrow_mut() = test_kigi_agent_with_todo().await;
let mut client_hooks = crate::extensions::hooks::ClientHooks::new();
for event in [
@@ -461,13 +461,13 @@ async fn post_tool_use_and_failure_never_double_fire() {
}
*actor.client_hooks.borrow_mut() = client_hooks;
// Collect the `hookEventName` of every `x.ai/hooks/event` notification queued.
// Collect the `hookEventName` of every `kigi/hooks/event` notification queued.
let drain =
|rx: &mut tokio::sync::mpsc::UnboundedReceiver<kigi_acp_lib::AcpClientMessage>| {
let mut events = Vec::new();
while let Ok(msg) = rx.try_recv() {
if let kigi_acp_lib::AcpClientMessage::ExtNotification(args) = msg
&& args.request.method.as_ref() == "x.ai/hooks/event"
&& args.request.method.as_ref() == "kigi/hooks/event"
{
let params: serde_json::Value =
serde_json::from_str(args.request.params.get()).unwrap();
@@ -544,7 +544,7 @@ async fn pre_tool_use_deny_feeds_reason_back_and_continues_turn() {
let actor = create_test_actor(0, 256_000, 85, gateway_tx, persistence_tx).await;
// The agent's tool bridge must know `todo_write` so it parses + reaches
// the PreToolUse gate (rather than short-circuiting as an unknown tool).
*actor.agent.borrow_mut() = test_grok_build_agent_with_todo().await;
*actor.agent.borrow_mut() = test_kigi_agent_with_todo().await;
let mut client_hooks = crate::extensions::hooks::ClientHooks::new();
client_hooks.insert(
@@ -557,7 +557,7 @@ async fn pre_tool_use_deny_feeds_reason_back_and_continues_turn() {
);
*actor.client_hooks.borrow_mut() = client_hooks;
// Answer the reverse x.ai/hooks/run request with a deny carrying a reason;
// Answer the reverse kigi/hooks/run request with a deny carrying a reason;
// ack the UI notifications `deny_tool` emits so it can't block the gate.
tokio::task::spawn_local(async move {
while let Some(msg) = gateway_rx.recv().await {
@@ -18,7 +18,7 @@ async fn tool_bridge_routes_writes_through_injected_fs() {
let config = ToolServerConfig {
tools: vec![
ToolConfig {
id: "GrokBuild:read_file".into(),
id: "Kigi:read_file".into(),
params: None,
name_override: None,
params_name_overrides: None,
@@ -27,7 +27,7 @@ async fn tool_bridge_routes_writes_through_injected_fs() {
kind: None,
},
ToolConfig {
id: "GrokBuild:search_replace".into(),
id: "Kigi:search_replace".into(),
params: Some(
serde_json::from_value(serde_json::json!({
"skip_read_before_edit": true
@@ -47,13 +47,13 @@ async fn tool_bridge_routes_writes_through_injected_fs() {
backend: terminal,
fs,
cwd: cwd.clone(),
session_folder: std::env::temp_dir().join("grok-test-fs"),
session_folder: std::env::temp_dir().join("kigi-test-fs"),
session_env: std::sync::Arc::new(std::collections::HashMap::new()),
notification_handle: ToolNotificationHandle::noop(),
owner_session_id: None,
parent_scheduler_handle: None,
skills: vec![],
state_path: std::env::temp_dir().join("grok-test-fs/tool_state.json"),
state_path: std::env::temp_dir().join("kigi-test-fs/tool_state.json"),
memory_backend: None,
web_search_config: Default::default(),
web_fetch_config: Default::default(),
@@ -459,8 +459,8 @@ async fn handle_turn_end_verified_complete_during_drain_skips_bail_nudge() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
tx.send(
kigi_tools::implementations::grok_build::update_goal::envelope_for_test(
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
kigi_tools::implementations::kigi::update_goal::envelope_for_test(
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: Some(true),
message: None,
blocked_reason: None,
@@ -1436,8 +1436,8 @@ async fn drain_goal_updates_blocked_reason_transitions_after_three_attempts() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
tx.send(
kigi_tools::implementations::grok_build::update_goal::envelope_for_test(
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
kigi_tools::implementations::kigi::update_goal::envelope_for_test(
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: None,
message: Some("longer body".into()),
blocked_reason: Some("short label".into()),
@@ -1479,8 +1479,8 @@ async fn drain_goal_updates_blocked_reason_rejected_below_threshold() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
tx.send(
kigi_tools::implementations::grok_build::update_goal::envelope_for_test(
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
kigi_tools::implementations::kigi::update_goal::envelope_for_test(
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: None,
message: None,
blocked_reason: Some("only label".into()),
@@ -1580,8 +1580,8 @@ async fn drain_goal_updates_blocked_reason_against_non_active_does_not_stash_pau
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
tx.send(
kigi_tools::implementations::grok_build::update_goal::envelope_for_test(
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
kigi_tools::implementations::kigi::update_goal::envelope_for_test(
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: None,
message: Some("body".into()),
blocked_reason: Some("would-block".into()),
@@ -1625,7 +1625,7 @@ async fn drain_goal_updates_completes_after_blocked_does_not_leak_pause_message(
.store(2, Ordering::Relaxed);
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
tx.send(kigi_tools::implementations::grok_build::update_goal::envelope_for_test(kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
tx.send(kigi_tools::implementations::kigi::update_goal::envelope_for_test(kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: None,
message: None,
blocked_reason: Some("blk".into()),
@@ -1641,7 +1641,7 @@ async fn drain_goal_updates_completes_after_blocked_does_not_leak_pause_message(
// accepts complete() from any paused variant (including
// Blocked), and the pause_message
// is cleared during the transition.
tx.send(kigi_tools::implementations::grok_build::update_goal::envelope_for_test(kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
tx.send(kigi_tools::implementations::kigi::update_goal::envelope_for_test(kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: Some(true),
message: None,
@@ -1675,8 +1675,8 @@ async fn drain_goal_updates_skips_subsequent_completed_after_block() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
tx.send(
kigi_tools::implementations::grok_build::update_goal::envelope_for_test(
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
kigi_tools::implementations::kigi::update_goal::envelope_for_test(
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: None,
message: None,
blocked_reason: Some("X".into()),
@@ -1685,8 +1685,8 @@ async fn drain_goal_updates_skips_subsequent_completed_after_block() {
)
.unwrap();
tx.send(
kigi_tools::implementations::grok_build::update_goal::envelope_for_test(
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
kigi_tools::implementations::kigi::update_goal::envelope_for_test(
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: Some(true),
message: None,
blocked_reason: None,
@@ -2259,8 +2259,8 @@ async fn drain_goal_updates_message_only_does_not_change_status() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
tx.send(
kigi_tools::implementations::grok_build::update_goal::envelope_for_test(
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
kigi_tools::implementations::kigi::update_goal::envelope_for_test(
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: None,
message: Some("Running tests...".into()),
blocked_reason: None,
@@ -2294,7 +2294,7 @@ async fn drain_goal_updates_message_only_does_not_change_status() {
/// producing an ack"). The drain must instead reply with a clean ack.
#[tokio::test(flavor = "current_thread")]
async fn drain_goal_updates_harness_disabled_does_not_drop_ack() {
use kigi_tools::implementations::grok_build::update_goal::{
use kigi_tools::implementations::kigi::update_goal::{
RejectReason, UpdateGoalAck, UpdateGoalInput,
};
let local = tokio::task::LocalSet::new();
@@ -2374,8 +2374,8 @@ async fn drain_goal_updates_blocked_reason_takes_precedence_over_completed() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
tx.send(
kigi_tools::implementations::grok_build::update_goal::envelope_for_test(
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
kigi_tools::implementations::kigi::update_goal::envelope_for_test(
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: Some(true),
message: None,
blocked_reason: Some("stuck".into()),
@@ -2404,9 +2404,8 @@ async fn drain_goal_updates_blocked_reason_takes_precedence_over_completed() {
// classifier sampler invoked); the full Achieved/NotAchieved/cap
// E2E suite using `MockSpawner` lives separately.
fn make_completed_cmd() -> kigi_tools::implementations::grok_build::update_goal::UpdateGoalEnvelope
{
let input = kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
fn make_completed_cmd() -> kigi_tools::implementations::kigi::update_goal::UpdateGoalEnvelope {
let input = kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: Some(true),
message: None,
blocked_reason: None,
@@ -2966,8 +2965,8 @@ async fn drain_goal_updates_completed_resets_blocked_streak() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
tx.send(
kigi_tools::implementations::grok_build::update_goal::envelope_for_test(
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
kigi_tools::implementations::kigi::update_goal::envelope_for_test(
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: Some(true),
message: None,
blocked_reason: None,
@@ -3379,7 +3378,7 @@ async fn subagent_spawn_captures_effective_model_id() {
.handle_xai_session_notification(spawn_notif_with_model(
"a",
None,
Some("grok-4.5"),
Some("kigi-4.5"),
))
.await;
let model = actor
@@ -3387,7 +3386,7 @@ async fn subagent_spawn_captures_effective_model_id() {
.lock()
.get("a")
.and_then(|r| r.model.clone());
assert_eq!(model.as_deref(), Some("grok-4.5"));
assert_eq!(model.as_deref(), Some("kigi-4.5"));
})
.await;
}
@@ -3417,17 +3416,17 @@ async fn goal_tokens_by_model_breaks_down_active_goal_records() {
local
.run_until(async {
let actor = make_test_actor_with_active_goal().await;
insert_record_with_model(&actor, "a", Some("test-goal"), 0, 100, Some("grok-4"));
insert_record_with_model(&actor, "b", Some("test-goal"), 0, 400, Some("grok-3"));
insert_record_with_model(&actor, "a", Some("test-goal"), 0, 100, Some("kigi-4"));
insert_record_with_model(&actor, "b", Some("test-goal"), 0, 400, Some("kigi-3"));
// No captured model → folds under the supplied current model.
insert_record_with_model(&actor, "c", Some("test-goal"), 0, 50, None);
// A record from another goal must be excluded.
insert_record_with_model(&actor, "d", Some("other-goal"), 0, 999, Some("grok-3"));
insert_record_with_model(&actor, "d", Some("other-goal"), 0, 999, Some("kigi-3"));
// A FINISHED record under the active goal must be excluded from the
// LIVE active-window breakdown (the per-model analogue of the
// finished/in-flight split in goal_tokens). If it leaked, grok-4
// finished/in-flight split in goal_tokens). If it leaked, kigi-4
// would be 800 and sort first.
insert_record_with_model(&actor, "e", Some("test-goal"), 0, 700, Some("grok-4"));
insert_record_with_model(&actor, "e", Some("test-goal"), 0, 700, Some("kigi-4"));
actor
.subagent_token_records
.lock()
@@ -3438,8 +3437,8 @@ async fn goal_tokens_by_model_breaks_down_active_goal_records() {
assert_eq!(
out,
vec![
("grok-3".to_owned(), 400),
("grok-4".to_owned(), 100),
("kigi-3".to_owned(), 400),
("kigi-4".to_owned(), 100),
("cur-model".to_owned(), 50),
]
);
@@ -3453,7 +3452,7 @@ async fn goal_tokens_by_model_empty_without_active_goal() {
local
.run_until(async {
let actor = make_test_actor_with_active_goal().await;
insert_record_with_model(&actor, "a", Some("test-goal"), 0, 100, Some("grok-4"));
insert_record_with_model(&actor, "a", Some("test-goal"), 0, 100, Some("kigi-4"));
// Drop the orchestration: with no active goal the breakdown is
// empty regardless of any lingering records.
actor.goal_tracker.lock().clear();
@@ -3757,8 +3756,8 @@ async fn blocked_streak_reaches_pause_across_successful_turns() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
let blocked = || {
kigi_tools::implementations::grok_build::update_goal::envelope_for_test(
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
kigi_tools::implementations::kigi::update_goal::envelope_for_test(
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: None,
message: None,
blocked_reason: Some("cannot reach service".into()),
@@ -3849,7 +3848,7 @@ async fn subagent_progress_advances_goal_tokens_live_without_double_count() {
let kigi_acp_lib::AcpClientMessage::ExtNotification(args) = msg else {
continue;
};
if args.request.method.as_ref() != "x.ai/session_notification" {
if args.request.method.as_ref() != "kigi/session_notification" {
continue;
}
let Ok(v) =
@@ -4028,8 +4027,8 @@ async fn setup_goal_resets_streaks_from_previous_goal() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
tx.send(
kigi_tools::implementations::grok_build::update_goal::envelope_for_test(
kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput {
kigi_tools::implementations::kigi::update_goal::envelope_for_test(
kigi_tools::implementations::kigi::update_goal::UpdateGoalInput {
completed: None,
message: None,
blocked_reason: Some("blk".into()),
@@ -17,10 +17,10 @@
use super::support::*;
use super::*;
use crate::session::PromptOrigin;
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentCancelOutcome, SubagentEvent, SubagentResult,
};
use kigi_tools::implementations::grok_build::update_goal::{RejectReason, UpdateGoalInput};
use kigi_tools::implementations::kigi::update_goal::{RejectReason, UpdateGoalInput};
use serial_test::serial;
use std::collections::VecDeque;
use std::sync::Arc as StdArc;
@@ -190,9 +190,7 @@ struct MockCoordinator {
/// configured role pair commits; tests override it to exercise the
/// describe-driven fail-open branches.
describe_outcome: StdArc<
parking_lot::Mutex<
kigi_tools::implementations::grok_build::task::types::SubagentDescribeOutcome,
>,
parking_lot::Mutex<kigi_tools::implementations::kigi::task::types::SubagentDescribeOutcome>,
>,
/// Per-describe `(subagent_type, harness_agent_type)` in call order.
describe_calls: DescribeCallLog,
@@ -200,8 +198,8 @@ struct MockCoordinator {
/// A fully-capable describe summary (read + search + execute + edit + write)
/// so any role's capability gate passes.
fn capable_describe_outcome()
-> kigi_tools::implementations::grok_build::task::types::SubagentDescribeOutcome {
use kigi_tools::implementations::grok_build::task::types::{
-> kigi_tools::implementations::kigi::task::types::SubagentDescribeOutcome {
use kigi_tools::implementations::kigi::task::types::{
SubagentDescribeOutcome, SubagentTypeSummary,
};
use kigi_tools::types::tool::ToolKind;
@@ -403,7 +401,7 @@ fn seed_channel(actor: &SessionActor, cmds: Vec<UpdateGoalInput>) {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
for cmd in cmds {
tx.send(kigi_tools::implementations::grok_build::update_goal::envelope_for_test(cmd))
tx.send(kigi_tools::implementations::kigi::update_goal::envelope_for_test(cmd))
.unwrap();
}
drop(tx);
@@ -415,9 +413,7 @@ fn seed_channel_with_acks(
actor: &SessionActor,
cmds: Vec<UpdateGoalInput>,
) -> Vec<
tokio::sync::oneshot::Receiver<
kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck,
>,
tokio::sync::oneshot::Receiver<kigi_tools::implementations::kigi::update_goal::UpdateGoalAck>,
> {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
@@ -712,7 +708,7 @@ async fn goal_classifier_stall_early_exit_pauses_with_no_progress() {
VecDeque::from([Response::not_achieved(), Response::not_achieved()]),
);
let (actor, tmp) = make_actor(Some(coord.tx.clone()), true).await;
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
let mut last_ack = None;
for _ in 0..2 {
let mut rxs = seed_channel_with_acks(&actor, vec![make_completed()]);
@@ -752,7 +748,7 @@ async fn goal_classifier_blocked_outcome_pauses_for_user_and_consolidates_queue(
let local = tokio::task::LocalSet::new();
local
.run_until(async {
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
let coord = MockCoordinator::spawn(VecDeque::from([Response::blocked("unverifiable")]));
let (actor, tmp) = make_actor(Some(coord.tx.clone()), true).await;
let rxs = seed_channel_with_acks(&actor, vec![make_completed(), make_completed()]);
@@ -817,7 +813,7 @@ async fn goal_classifier_cap_takes_precedence_over_stall() {
let local = tokio::task::LocalSet::new();
local
.run_until(async {
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
let coord = MockCoordinator::spawn(VecDeque::from([
Response::not_achieved(),
Response::not_achieved(),
@@ -878,7 +874,7 @@ async fn goal_classifier_stall_pause_consolidates_mid_drain_queue() {
let local = tokio::task::LocalSet::new();
local
.run_until(async {
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
let coord = MockCoordinator::spawn(VecDeque::from([
Response::not_achieved(),
Response::not_achieved(),
@@ -929,7 +925,7 @@ async fn goal_classifier_post_blocked_resume_does_not_immediately_restall() {
let local = tokio::task::LocalSet::new();
local
.run_until(async {
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
let coord = MockCoordinator::spawn(VecDeque::from([
Response::not_achieved_with("src/a.rs:1 missing coverage"),
Response::blocked("contradiction"),
@@ -2070,7 +2066,7 @@ async fn update_goal_tool_blocks_until_classifier_verdict_when_enabled() {
let ack = ack_rx.await.expect("ack delivered");
assert!(
matches!(ack,
kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck::ClassifierAchieved
kigi_tools::implementations::kigi::update_goal::UpdateGoalAck::ClassifierAchieved
{ .. },),
"classifier-enabled drain must deliver Achieved ack; got {ack:?}",
);
@@ -2089,7 +2085,7 @@ async fn update_goal_tool_returns_immediately_when_classifier_disabled() {
let ack = ack_rx.await.expect("ack delivered");
assert!(
matches!(ack,
kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck::CompletedWithoutClassifier,)
kigi_tools::implementations::kigi::update_goal::UpdateGoalAck::CompletedWithoutClassifier,)
);
})
.await;
@@ -2105,7 +2101,7 @@ async fn update_goal_tool_returns_error_when_classifier_in_flight_for_previous_c
let ack_rx = rxs.pop().expect("one ack");
actor.drain_goal_updates(0, DrainPurpose::TurnEnd).await;
let ack = ack_rx.await.expect("ack delivered");
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
match ack {
UpdateGoalAck::ClassifierConcurrentInFlight {
attempt, max_runs, ..
@@ -2127,7 +2123,7 @@ async fn update_goal_tool_returns_error_when_classifier_in_flight_for_previous_c
}
#[tokio::test(flavor = "current_thread")]
async fn update_goal_tool_does_not_deadlock_on_mid_turn_completion() {
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
let local = tokio::task::LocalSet::new();
local
.run_until(async {
@@ -2150,7 +2146,7 @@ async fn update_goal_tool_does_not_deadlock_on_mid_turn_completion() {
}
#[tokio::test(flavor = "current_thread")]
async fn update_goal_tool_deferred_input_fires_classifier_at_turn_end() {
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
let local = tokio::task::LocalSet::new();
local
.run_until(async {
@@ -2185,18 +2181,14 @@ async fn update_goal_tool_returns_immediately_for_blocked_reason() {
local
.run_until(async {
let (actor, _tmp) = make_actor(None, true).await;
let mut rxs = seed_channel_with_acks(
&actor,
vec![make_blocked("transient")],
);
let mut rxs = seed_channel_with_acks(&actor, vec![make_blocked("transient")]);
let ack_rx = rxs.pop().expect("one ack");
actor.drain_goal_updates(0, DrainPurpose::TurnEnd).await;
let ack = ack_rx.await.expect("ack delivered");
assert!(
matches!(ack,
kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck::Accepted
{ .. },)
);
assert!(matches!(
ack,
kigi_tools::implementations::kigi::update_goal::UpdateGoalAck::Accepted { .. },
));
})
.await;
}
@@ -2291,7 +2283,7 @@ async fn goal_classifier_sequential_drain_four_completions_three_attempts_then_c
let local = tokio::task::LocalSet::new();
local
.run_until(async {
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
let coordinator = MockCoordinator::spawn(three_distinct_not_achieved());
let (actor, _tmp) = make_actor_with_cap(
Some(coordinator.tx.clone()),
@@ -2358,7 +2350,7 @@ async fn goal_classifier_sequential_drain_four_completions_three_attempts_then_c
/// must ack as `ClassifierConcurrentInFlight` (NOT success).
#[tokio::test(flavor = "current_thread")]
async fn goal_classifier_concurrent_in_flight_short_circuits_second_completion() {
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
let local = tokio::task::LocalSet::new();
local
.run_until(async {
@@ -2414,7 +2406,7 @@ async fn rejected_ack_post_cap_carries_correct_reason() {
let ack_rx = rxs.pop().unwrap();
actor.drain_goal_updates(0, DrainPurpose::TurnEnd).await;
let ack = ack_rx.await.expect("ack delivered");
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
match ack {
UpdateGoalAck::Rejected { reason, detail } => {
assert_eq!(reason, RejectReason::PostCap);
@@ -2437,7 +2429,7 @@ async fn pending_queue_overflow_acks_all_as_deferred_and_caps_at_pending_queue_c
}
let rxs = seed_channel_with_acks(&actor, inputs);
actor.drain_goal_updates(0, DrainPurpose::MidTurn).await;
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalAck;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalAck;
for rx in rxs {
let ack = rx.await.expect("ack delivered");
assert!(
@@ -2462,9 +2454,7 @@ async fn pending_queue_overflow_acks_all_as_deferred_and_caps_at_pending_queue_c
}
#[test]
fn render_ack_classifier_achieved_is_success() {
use kigi_tools::implementations::grok_build::update_goal::{
UpdateGoalAck, render_ack_into_output,
};
use kigi_tools::implementations::kigi::update_goal::{UpdateGoalAck, render_ack_into_output};
let out = render_ack_into_output(UpdateGoalAck::ClassifierAchieved {
details_path: "/tmp/details.md".to_string(),
})
@@ -2475,9 +2465,7 @@ fn render_ack_classifier_achieved_is_success() {
}
#[test]
fn render_ack_classifier_fail_open_achieved_clarifies_no_verdict() {
use kigi_tools::implementations::grok_build::update_goal::{
UpdateGoalAck, render_ack_into_output,
};
use kigi_tools::implementations::kigi::update_goal::{UpdateGoalAck, render_ack_into_output};
let out =
render_ack_into_output(UpdateGoalAck::ClassifierFailOpenAchieved { reason: "timeout" })
.expect("FailOpen must be Ok (treated as achieved)");
@@ -2488,9 +2476,7 @@ fn render_ack_classifier_fail_open_achieved_clarifies_no_verdict() {
}
#[test]
fn render_ack_not_achieved_is_tool_error_with_correct_code() {
use kigi_tools::implementations::grok_build::update_goal::{
UpdateGoalAck, render_ack_into_output,
};
use kigi_tools::implementations::kigi::update_goal::{UpdateGoalAck, render_ack_into_output};
let err = render_ack_into_output(UpdateGoalAck::ClassifierNotAchieved {
details_path: "/tmp/details.md".to_string(),
attempt: 2,
@@ -2501,9 +2487,7 @@ fn render_ack_not_achieved_is_tool_error_with_correct_code() {
}
#[test]
fn render_ack_cap_reached_is_tool_error_with_cap_code() {
use kigi_tools::implementations::grok_build::update_goal::{
UpdateGoalAck, render_ack_into_output,
};
use kigi_tools::implementations::kigi::update_goal::{UpdateGoalAck, render_ack_into_output};
let err = render_ack_into_output(UpdateGoalAck::ClassifierCapReached {
details_path: "/tmp/details.md".to_string(),
attempt: 3,
@@ -2524,9 +2508,7 @@ fn tool_error_code(err: &kigi_tool_runtime::ToolError) -> &str {
}
#[test]
fn render_ack_rejected_uses_reason_error_code() {
use kigi_tools::implementations::grok_build::update_goal::{
UpdateGoalAck, render_ack_into_output,
};
use kigi_tools::implementations::kigi::update_goal::{UpdateGoalAck, render_ack_into_output};
for (reason, want_code) in all_reject_reasons() {
let err = render_ack_into_output(UpdateGoalAck::Rejected {
reason: *reason,
@@ -2613,7 +2595,7 @@ fn reject_reasons_complete_matrix() {
}
use crate::session::acp_session::GoalRoleModelConfig;
use crate::session::acp_session::goal::{PanelResolveCache, RoleCapability};
use kigi_tools::implementations::grok_build::task::types::SubagentDescribeOutcome;
use kigi_tools::implementations::kigi::task::types::SubagentDescribeOutcome;
fn role_pair(model: &str, agent_type: &str) -> crate::util::config::GoalRoleModel {
crate::util::config::GoalRoleModel {
model: model.to_string(),
@@ -2824,11 +2806,10 @@ async fn resolve_role_override_toolset_incapable_fails_open() {
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let mut summary =
kigi_tools::implementations::grok_build::task::types::SubagentTypeSummary {
can_read: true,
..Default::default()
};
let mut summary = kigi_tools::implementations::kigi::task::types::SubagentTypeSummary {
can_read: true,
..Default::default()
};
summary
.tool_names
.insert(kigi_tools::types::tool::ToolKind::Read, "read_file".into());
@@ -2871,7 +2852,7 @@ async fn resolve_role_override_all_pass_commits_and_emits_resolved() {
}
/// A strict-but-unrepresentable harness (`codex`) fails open with the distinct
/// `harness_flavor_unsupported` reason. Honored:
/// `grok-build-plan` (non-strict), and `opencode` (non-strict + unrepresentable
/// `kigi-plan` (non-strict), and `opencode` (non-strict + unrepresentable
/// — proving the gate keys on `is_strict`, not representability alone).
#[tokio::test(flavor = "current_thread")]
async fn resolve_role_override_harness_flavor_representability_gate() {
@@ -2889,7 +2870,7 @@ async fn resolve_role_override_harness_flavor_representability_gate() {
let evs = lines_with_type(&log, "goal_role_model_fail_open");
assert_eq!(evs.len(), 1, "{log}");
assert_eq!(evs[0]["reason"], "harness_flavor_unsupported");
let honored = ["grok-build-plan", "opencode"];
let honored = ["kigi-plan", "opencode"];
for harness in honored {
let (ov, log) = run_resolve(
&role_pair("good-model", harness),
@@ -2983,7 +2964,7 @@ async fn single_role_override_explicit_builds_tool_names_from_summary() {
let local = tokio::task::LocalSet::new();
local
.run_until(async {
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentDescribeOutcome, SubagentTypeSummary,
};
use kigi_tools::types::tool::ToolKind;
@@ -5,7 +5,7 @@
use super::support::*;
use super::*;
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
use serial_test::serial;
use std::sync::Arc as StdArc;
use std::sync::atomic::{AtomicUsize, Ordering as SeqOrd};
@@ -129,7 +129,7 @@ async fn setup_goal_includes_simplified_prompt() {
}
#[tokio::test(flavor = "current_thread")]
async fn goal_enabled_without_update_goal_disables_harness_continuation_and_todo_gate() {
use kigi_tools::implementations::grok_build::UPDATE_GOAL_TOOL_NAME;
use kigi_tools::implementations::kigi::UPDATE_GOAL_TOOL_NAME;
let local = tokio::task::LocalSet::new();
local
.run_until(async {
@@ -828,7 +828,7 @@ fn render_goal_rules_places_discipline_after_block_recap() {
&block_recap,
"",
None,
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
let recap_idx = body
@@ -851,7 +851,7 @@ fn render_goal_rules_discipline_before_tracking_when_block_recap_empty() {
"",
"",
None,
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert_goal_discipline_in_reminder(&body, "render_goal_rules_empty_recap");
@@ -865,7 +865,7 @@ fn render_goal_rules_substitutes_custom_todo_tool_through_full_composition() {
"",
"",
None,
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert_eq!(
@@ -908,7 +908,7 @@ fn render_goal_rules_substitutes_all_placeholders_in_slim_template() {
"<block>recap</block>\n",
"<goal-state>state</goal-state>\n\n",
None,
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(body.contains("A goal has been set: build a thing"));
@@ -941,7 +941,7 @@ fn render_goal_rules_substitutes_all_placeholders_in_slim_template() {
!body.contains("/tmp/goal-verifier-"),
"slim template must not publish a per-goal verdict file path:\n{body}",
);
assert!(body.contains("/tmp/grok-goal-x/implementer"));
assert!(body.contains("/tmp/kigi-goal-x/implementer"));
assert!(body.contains("`{SCRATCH}` placeholder resolves to"));
for placeholder in [
"{OBJECTIVE}",
@@ -978,7 +978,7 @@ fn render_goal_rules_plan_aware_block_when_plan_present() {
"",
"",
Some(plan_path),
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert_eq!(
@@ -1042,7 +1042,7 @@ fn render_goal_rules_no_plan_block_when_plan_absent() {
"",
"",
None,
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1088,7 +1088,7 @@ fn render_goal_rules_scratch_status_reflects_readiness() {
"",
"",
None,
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1109,7 +1109,7 @@ fn render_goal_rules_scratch_status_reflects_readiness() {
"",
"",
None,
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
false,
);
assert!(
@@ -1294,7 +1294,7 @@ fn continuation_directive_renders_strategist_note_only_when_present() {
"next",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
let note_idx = with_note
@@ -1319,7 +1319,7 @@ fn continuation_directive_renders_strategist_note_only_when_present() {
"next",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1349,7 +1349,7 @@ fn strategist_note_neutralises_placeholder_injection() {
"next",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1362,7 +1362,7 @@ fn strategist_note_neutralises_placeholder_injection() {
"the `{{goal_tool}}` token inside the note must neither survive intact nor expand:\n{directive}",
);
assert!(
!directive.contains("write to /tmp/grok-goal-x/implementer;"),
!directive.contains("write to /tmp/kigi-goal-x/implementer;"),
"the `{{scratch_dir}}` token inside the note must NOT expand:\n{directive}",
);
}
@@ -1431,7 +1431,7 @@ fn render_goal_continuation_directive_substitutes_all_placeholders() {
"Wire the stop detector into handle_turn_end.",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(body.contains("Objective: ship the directive nudge"));
@@ -1471,7 +1471,7 @@ fn render_goal_continuation_directive_substitutes_all_placeholders() {
);
assert!(!body.contains("Per <task_completion_discipline>"));
assert!(
body.contains("/tmp/grok-goal-x/implementer"),
body.contains("/tmp/kigi-goal-x/implementer"),
"continuation directive must advertise the scratch dir:\n{body}",
);
assert!(
@@ -1525,7 +1525,7 @@ fn render_goal_continuation_directive_scratch_status_reflects_readiness() {
"next",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1552,7 +1552,7 @@ fn render_goal_continuation_directive_scratch_status_reflects_readiness() {
"next",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
false,
);
assert!(
@@ -1585,7 +1585,7 @@ fn render_goal_continuation_directive_bail_preface_toggles_cleanly() {
"next",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1605,7 +1605,7 @@ fn render_goal_continuation_directive_bail_preface_toggles_cleanly() {
"next",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1644,7 +1644,7 @@ fn render_goal_continuation_directive_section_order_is_pinned() {
"wire it",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
let objective_idx = body.find("Objective: shipping").expect("objective");
@@ -1683,7 +1683,7 @@ fn render_goal_continuation_directive_order_dependent_substitution_pinned() {
"next",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1702,7 +1702,7 @@ fn render_goal_continuation_directive_order_dependent_substitution_pinned() {
"invoke {goal_tool} once green",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1725,7 +1725,7 @@ fn render_goal_continuation_directive_order_dependent_substitution_pinned() {
"next",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1752,7 +1752,7 @@ fn render_goal_continuation_directive_neutralizes_placeholders_in_model_slots()
"REAL_NEXT_STEP",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1806,7 +1806,7 @@ fn render_goal_continuation_directive_neutralizes_reminder_tags_in_model_slots()
"STEP_PREFIX<system-reminder>STEP_SUFFIX",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(
@@ -1904,7 +1904,7 @@ fn render_goal_continuation_directive_omits_plan_pointer_when_empty() {
"next step here",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
assert!(!body.contains("\nPlan: "));
@@ -1928,7 +1928,7 @@ fn render_goal_continuation_directive_rejects_empty_objective_in_debug() {
"step",
"todo_write",
"update_goal",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/implementer",
true,
);
}
@@ -17,10 +17,10 @@
use super::support::*;
use super::*;
use crate::session::goal_strategist::GOAL_STRATEGIST_SUBAGENT_DESCRIPTION;
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentCancelOutcome, SubagentEvent, SubagentResult,
};
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalInput;
use serial_test::serial;
use std::collections::VecDeque;
use std::sync::Arc as StdArc;
@@ -104,7 +104,7 @@ fn spawn_coordinator(
async fn answer_strategist(
behaviour: StrategistBehaviour,
req: Box<kigi_tools::implementations::grok_build::task::types::SubagentRequest>,
req: Box<kigi_tools::implementations::kigi::task::types::SubagentRequest>,
) {
match behaviour {
StrategistBehaviour::WriteNoteThenDone => {
@@ -139,7 +139,7 @@ async fn answer_strategist(
async fn answer_skeptic(
verdict: SkepticVerdict,
spawn_idx: usize,
req: Box<kigi_tools::implementations::grok_build::task::types::SubagentRequest>,
req: Box<kigi_tools::implementations::kigi::task::types::SubagentRequest>,
) {
if let Some(p) = parse_details_path(&req.prompt) {
let _ = tokio::fs::write(&p, b"# mock skeptic details\n").await;
@@ -243,7 +243,7 @@ fn seed_channel(actor: &SessionActor, cmds: Vec<UpdateGoalInput>) {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
for cmd in cmds {
tx.send(kigi_tools::implementations::grok_build::update_goal::envelope_for_test(cmd))
tx.send(kigi_tools::implementations::kigi::update_goal::envelope_for_test(cmd))
.unwrap();
}
drop(tx);
@@ -17,10 +17,10 @@
use super::support::*;
use super::*;
use crate::session::goal_summarizer::GOAL_SUMMARIZER_SUBAGENT_DESCRIPTION;
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentCancelOutcome, SubagentEvent, SubagentResult,
};
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalInput;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalInput;
use serial_test::serial;
use std::collections::VecDeque;
use std::sync::Arc as StdArc;
@@ -98,7 +98,7 @@ fn spawn_coordinator(
async fn answer_summarizer(
behaviour: SummarizerBehaviour,
req: Box<kigi_tools::implementations::grok_build::task::types::SubagentRequest>,
req: Box<kigi_tools::implementations::kigi::task::types::SubagentRequest>,
) {
match behaviour {
SummarizerBehaviour::ReturnSummary => {
@@ -127,7 +127,7 @@ async fn answer_summarizer(
async fn answer_skeptic(
verdict: SkepticVerdict,
spawn_idx: usize,
req: Box<kigi_tools::implementations::grok_build::task::types::SubagentRequest>,
req: Box<kigi_tools::implementations::kigi::task::types::SubagentRequest>,
) {
if let Some(p) =
crate::session::goal_classifier::parse_skeptic_details_path_from_prompt(&req.prompt)
@@ -268,7 +268,7 @@ fn seed_channel(actor: &SessionActor, cmds: Vec<UpdateGoalInput>) {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
*actor.goal_update_rx.borrow_mut() = Some(rx);
for cmd in cmds {
tx.send(kigi_tools::implementations::grok_build::update_goal::envelope_for_test(cmd))
tx.send(kigi_tools::implementations::kigi::update_goal::envelope_for_test(cmd))
.unwrap();
}
drop(tx);
@@ -199,7 +199,7 @@ mod interjection_broadcast_tests {
/// Multi-client fix: a mid-turn interjection must be broadcast to every
/// attached client (not just the originator) so all panes viewing the same
/// session render it. This locks the wire contract the pager's
/// `handle_interjection` depends on: method `x.ai/session/interjection`
/// `handle_interjection` depends on: method `kigi/session/interjection`
/// carrying `sessionId` + `text`.
#[tokio::test]
async fn broadcast_interjection_emits_sessionid_and_text() {
@@ -217,14 +217,14 @@ mod interjection_broadcast_tests {
let mut payload = None;
while let Ok(msg) = gateway_rx.try_recv() {
if let kigi_acp_lib::AcpClientMessage::ExtNotification(args) = msg
&& args.request.method.as_ref() == "x.ai/session/interjection"
&& args.request.method.as_ref() == "kigi/session/interjection"
{
payload =
serde_json::from_str::<serde_json::Value>(args.request.params.get())
.ok();
}
}
let payload = payload.expect("an x.ai/session/interjection broadcast");
let payload = payload.expect("an kigi/session/interjection broadcast");
assert_eq!(
payload.get("sessionId").and_then(|v| v.as_str()),
Some("test-actor"),
@@ -622,7 +622,7 @@ fn sample_line() -> LazinessDebugLogLine {
LazinessDebugLogLine {
timestamp: "2026-05-21T22:14:01.123Z".to_string(),
session_id: "019e4c65-434b-7d62-9d4b-8137d1d413e4".to_string(),
model_id: "grok-4.5".to_string(),
model_id: "kigi-4.5".to_string(),
items_sent: 28,
todo_snapshot: vec![DebugTodoSnapshot {
id: "turn-finish-test-1".to_string(),
@@ -180,7 +180,7 @@ async fn incremental_dispatch_surfaces_fast_tool_before_slow_sibling() {
/// `execute_tool_calls` Phase 2. The original implementation hardcoded
/// `parsed_args.get("file_path")`, which silently bypassed serialization
/// for any toolset whose edit input declared the path under a different
/// JSON key. The compat toolset input types use `path`, and grok_build's
/// JSON key. The compat toolset input types use `path`, and kigi's
/// `read_file` uses `target_file`, so all of
/// those calls fell through to fully concurrent dispatch and could lose
/// edits via TOCTOU on the same workspace file.
@@ -188,8 +188,8 @@ async fn incremental_dispatch_surfaces_fast_tool_before_slow_sibling() {
/// These tests pin the JSON-key contract so the bucket key keeps tracking
/// every toolset's actual schema.
#[test]
fn lock_path_for_args_matches_grok_build_file_path() {
// grok_build search_replace / opencode EditTool / WriteTool / etc.
fn lock_path_for_args_matches_kigi_file_path() {
// kigi search_replace / opencode EditTool / WriteTool / etc.
let args = serde_json::json!({
"file_path": "/repo/src/main.rs",
"old_string": "foo",
@@ -210,8 +210,8 @@ fn lock_path_for_args_matches_path_arg() {
}
#[test]
fn lock_path_for_args_matches_grok_build_target_file() {
// grok_build read_file uses #[serde(rename = "target_file")].
fn lock_path_for_args_matches_kigi_target_file() {
// kigi read_file uses #[serde(rename = "target_file")].
let args = serde_json::json!({
"target_file": "/repo/src/main.rs",
});
@@ -274,14 +274,14 @@ fn lock_path_for_args_buckets_parallel_compat_strreplace_to_same_lock() {
}
#[test]
fn lock_path_for_args_buckets_grok_build_and_compat_to_same_lock_for_same_file() {
// A mixed batch (e.g. grok_build search_replace + StrReplace
fn lock_path_for_args_buckets_kigi_and_compat_to_same_lock_for_same_file() {
// A mixed batch (e.g. kigi search_replace + StrReplace
// in the same turn — possible if the harness ever exposes both, or
// during toolset migration) must still serialize on the shared file
// path. file_path takes precedence over path when both are present,
// but neither tool emits both keys today, so this asserts the
// cross-toolset key normalization works in practice.
let grok = serde_json::json!({
let kigi = serde_json::json!({
"file_path": "/repo/src/main.rs",
"old_string": "a",
"new_string": "b",
@@ -291,7 +291,7 @@ fn lock_path_for_args_buckets_grok_build_and_compat_to_same_lock_for_same_file()
"old_string": "c",
"new_string": "d",
});
assert_eq!(lock_path_for_args(&grok), lock_path_for_args(&compat));
assert_eq!(lock_path_for_args(&kigi), lock_path_for_args(&compat));
}
/// Regression: skill-discovery reminders must land after all tool results, not mid-batch.
@@ -1,7 +1,7 @@
//! Resume re-park of the `exit_plan_mode` approval + the mid-turn
//! disconnect handling.
//!
//! On resume the shell re-issues the `x.ai/exit_plan_mode` reverse-request when
//! On resume the shell re-issues the `kigi/exit_plan_mode` reverse-request when
//! `awaiting_plan_approval` was persisted, recreating a real live waiter so the
//! pager's existing approve/revise/abandon path works unchanged. These tests
//! pin the reverse-request shape, the awaiting-bit lifecycle, and the mid-turn
@@ -93,7 +93,7 @@ async fn request_plan_approval_issues_reverse_request_and_clears_flag() {
);
let (method, session_id) = responder.await.unwrap();
assert_eq!(method.as_deref(), Some("x.ai/exit_plan_mode"));
assert_eq!(method.as_deref(), Some("kigi/exit_plan_mode"));
assert_eq!(
session_id.as_deref(),
Some("test-actor"),
@@ -6,12 +6,12 @@
//! BEFORE the permission layer can auto-approve.
use super::support::*;
use super::*;
/// Build an actor whose toolset parses grok `search_replace` plus the plan
/// Build an actor whose toolset parses kigi `search_replace` plus the plan
/// tools (so `${{ tools.by_kind.exit_plan }}` resolves in the rejection
/// message), with a gateway drain answering session notifications.
async fn build_gate_actor() -> SessionActor {
use kigi_tools::implementations::grok_build::enter_plan_mode::EnterPlanModeTool;
use kigi_tools::implementations::grok_build::exit_plan_mode::ExitPlanModeTool;
use kigi_tools::implementations::kigi::enter_plan_mode::EnterPlanModeTool;
use kigi_tools::implementations::kigi::exit_plan_mode::ExitPlanModeTool;
use kigi_tools::registry::types::ToolConfig;
let (gateway_tx, mut gateway_rx) =
tokio::sync::mpsc::unbounded_channel::<kigi_acp_lib::AcpClientMessage>();
@@ -19,8 +19,8 @@ async fn build_gate_actor() -> SessionActor {
tokio::sync::mpsc::unbounded_channel::<PersistenceMsg>();
let actor = create_test_actor(0, 256_000, 85, gateway_tx, persistence_tx).await;
*actor.agent.borrow_mut() = test_agent_with_tools(vec![
ToolConfig::from_id("GrokBuild:read_file"),
ToolConfig::from_id("GrokBuild:search_replace"),
ToolConfig::from_id("Kigi:read_file"),
ToolConfig::from_id("Kigi:search_replace"),
ToolConfig::for_tool::<EnterPlanModeTool>(),
ToolConfig::for_tool::<ExitPlanModeTool>(),
])
@@ -77,10 +77,10 @@ async fn tool_result_text(actor: &SessionActor, call_id: &str) -> String {
.unwrap_or_else(|| panic!("no tool_result for {call_id} in {conv:?}"))
}
/// The headline: plan mode Active + allow-all permissions (the always-approve
/// worst case) still rejects a grok edit outside the plan file, without ever
/// worst case) still rejects a kigi edit outside the plan file, without ever
/// reaching the permission layer, and steers the model to `exit_plan_mode`.
#[tokio::test(flavor = "current_thread")]
async fn plan_mode_rejects_grok_edit_outside_plan_file_despite_allow_all_permissions() {
async fn plan_mode_rejects_kigi_edit_outside_plan_file_despite_allow_all_permissions() {
let local = tokio::task::LocalSet::new();
local
.run_until(async {
@@ -60,7 +60,7 @@ fn test_system_prompt_write_and_read() {
#[test]
fn test_system_prompt_is_plain_text_not_json() {
let prompt = "You are a Grok Build subagent.";
let prompt = "You are a Kigi subagent.";
// system_prompt.txt is raw text, NOT JSON-encoded.
assert!(!prompt.starts_with('"'), "must not be JSON-quoted");
assert!(!prompt.starts_with('{'), "must not be JSON object");
@@ -107,7 +107,7 @@ fn test_system_prompt_matches_chat_history_system_message() {
let session_dir = tmp.path().join("session-consistency");
std::fs::create_dir_all(&session_dir).unwrap();
let system_prompt = "You are a Grok Build subagent.\n\n<tool_calling>\n...";
let system_prompt = "You are a Kigi subagent.\n\n<tool_calling>\n...";
// Write system_prompt.txt (same string used for chat_history).
std::fs::write(session_dir.join(SYSTEM_PROMPT_FILENAME), system_prompt).unwrap();
@@ -164,7 +164,7 @@ fn test_load_system_prompt_returns_content_when_present() {
let session_dir = tmp.path().join("session-load-test");
std::fs::create_dir_all(&session_dir).unwrap();
let prompt = "You are a Grok Build subagent.";
let prompt = "You are a Kigi subagent.";
std::fs::write(session_dir.join(SYSTEM_PROMPT_FILENAME), prompt).unwrap();
let loaded = load_system_prompt_from_dir(&session_dir);
@@ -243,7 +243,7 @@ const HEAD_TOKEN: &str = "HEADSTART_TOKEN_aaa";
const TAIL_TOKEN: &str = "TAILEND_TOKEN_zzz";
fn fake_prompt_path() -> std::path::PathBuf {
std::path::PathBuf::from("/tmp/grok-test-home/sessions/cwd/sid/prompts/prompt_0.txt")
std::path::PathBuf::from("/tmp/kigi-test-home/sessions/cwd/sid/prompts/prompt_0.txt")
}
/// `truncate_bytes_suffix` keeps a char-boundary-safe suffix (multibyte-safe).
@@ -343,7 +343,7 @@ fn build_truncated_preserves_small_query_truncates_context() {
assert!(message.contains(&query), "small query preserved intact");
assert!(
message.starts_with(&query),
"grok ordering: query block first"
"kigi ordering: query block first"
);
assert!(message.contains("CTXHEAD_TOKEN"), "context head preserved");
assert!(!message.contains(&context), "oversized context truncated");
@@ -381,7 +381,7 @@ fn build_truncated_both_oversized_keeps_bounded_heads() {
assert!(!message.contains(&context), "full context not inlined");
assert!(
message.starts_with("QHEAD_TOKEN"),
"grok ordering: query first"
"kigi ordering: query first"
);
assert!(
message.len() <= TRUNCATED_PROMPT_PREFIX_SIZE,
@@ -415,9 +415,9 @@ async fn manual_recap_generation_failure_persists_request_artifact() {
"artifact must include the recap request items"
);
assert!(
artifact.x_grok_req_id.starts_with("xai-recap-"),
artifact.x_kigi_req_id.starts_with("xai-recap-"),
"req id: {}",
artifact.x_grok_req_id
artifact.x_kigi_req_id
);
saw_recap_request = true;
}
@@ -529,7 +529,7 @@ async fn manual_recap_over_budget_trims_persisted_request_and_is_display_only()
/// Over-budget recap serializes to a well-formed Anthropic Messages payload:
/// system preserved, reasoning stripped, no dangling `tool_use`/`tool_result`, no
/// `tool_result` before the appended instruction. (Messages is the strictest
/// shape, so it also covers the laxer grok ChatCompletions/Responses shapes.)
/// shape, so it also covers the laxer kigi ChatCompletions/Responses shapes.)
#[test]
fn over_budget_recap_serializes_to_well_formed_messages_request() {
use crate::session::helpers::session_recap;
@@ -568,7 +568,7 @@ fn over_budget_recap_serializes_to_well_formed_messages_request() {
ConversationItem::tool_result("c2", "z".repeat(40_000)), // trailing run
];
// grok backend => strip_reasoning=false; the over-budget branch strips anyway.
// kigi backend => strip_reasoning=false; the over-budget branch strips anyway.
let items = session_recap::budget_recap_items(conv, "system-reminder", false, 8_000);
let req = ConversationRequest::from_items(items);
let msg = kigi_sampling_types::build_messages_request(&req);
@@ -115,7 +115,7 @@ fn remote_settings_preserves_false_and_zero_todo_gate_fields() {
assert_eq!(settings.todo_gate_max_fires_per_prompt, Some(0));
}
fn def_with_template(tpl: TemplateOverride) -> AgentDefinition {
let mut def = AgentDefinition::default_grok_build();
let mut def = AgentDefinition::default_kigi();
def.system_prompt = tpl;
def
}
@@ -127,7 +127,7 @@ fn policy_with_gate(enabled: bool) -> ReminderPolicy {
use crate::session::goal_tracker::GoalStatus;
#[test]
fn goal_slash_and_harness_available_predicate_matrix() {
use kigi_tools::implementations::grok_build::UPDATE_GOAL_TOOL_NAME;
use kigi_tools::implementations::kigi::UPDATE_GOAL_TOOL_NAME;
let other = vec!["todo_write".to_string()];
let with_update = vec![UPDATE_GOAL_TOOL_NAME.to_string()];
for (goal_enabled, tool_names, expect) in [
@@ -1193,7 +1193,7 @@ async fn reasoning_only_doomloop_turn_captures_every_generation_as_segments() {
completion_tokens: Some(0),
reasoning_tokens: Some(4096),
prompt_tokens: Some(128),
model: "grok-test".to_string(),
model: "kigi-test".to_string(),
first_choice_seen: true,
}),
doom_loop_triggers: None,
@@ -2,10 +2,10 @@
//! (permission / `ask_user_question` / plan-approval) must carry a
//! non-empty `sessionId`, otherwise Tier-2 routing silently drops it
//! (`server.rs`). The invariant holds today; these tests pin it.
use kigi_tools::implementations::grok_build::ask_user_question::{
use kigi_tools::implementations::kigi::ask_user_question::{
AskUserQuestionExtRequest, AskUserQuestionMode,
};
use kigi_tools::implementations::grok_build::exit_plan_mode::ExitPlanModeExtRequest;
use kigi_tools::implementations::kigi::exit_plan_mode::ExitPlanModeExtRequest;
#[test]
fn ask_user_question_request_carries_session_id() {
@@ -51,7 +51,7 @@ fn skips_synthetic_reminder_at_index_one() {
/// Drives the real `handle_rebuild_agent_for_definition` path.
#[tokio::test(flavor = "current_thread")]
async fn rebuild_reinjects_goal_update_handle() {
use kigi_tools::implementations::grok_build::update_goal::{
use kigi_tools::implementations::kigi::update_goal::{
GoalUpdateHandle, UpdateGoalInput, envelope_for_test,
};
let local = tokio::task::LocalSet::new();
@@ -61,9 +61,7 @@ async fn rebuild_reinjects_goal_update_handle() {
let (persist_tx, _persist_rx) = tokio::sync::mpsc::unbounded_channel();
let actor = create_test_actor(0, 256_000, 85, gw_tx, persist_tx).await;
actor
.handle_rebuild_agent_for_definition(
kigi_agent::AgentDefinition::default_grok_build(),
)
.handle_rebuild_agent_for_definition(kigi_agent::AgentDefinition::default_kigi())
.await
.expect("zero-turn rebuild should succeed");
let bridge = actor.agent.borrow().tool_bridge().clone();
@@ -99,7 +97,7 @@ async fn rebuild_reinjects_goal_update_handle() {
.await;
}
/// The seeded skill used by the rebuild skill-reminder tests. A non-plugin
/// Local skill is always listable, so it renders into the grok markdown skill
/// Local skill is always listable, so it renders into the kigi markdown skill
/// catalog when the pending baseline is drained for a different agent.
fn regression_skill() -> kigi_tools::implementations::skills::types::SkillInfo {
kigi_tools::implementations::skills::types::SkillInfo {
@@ -149,9 +147,9 @@ fn stale_source_reminder() -> ConversationItem {
- stale-source-skill: from the source session.\n</system-reminder>",
)
}
/// Regression: a zero-turn agent rebuild INTO a grok/Default agent
/// Regression: a zero-turn agent rebuild INTO a kigi/Default agent
/// must re-inject the baseline skill `<system-reminder>`. `initialize()`
/// is otherwise the only place skills are surfaced for the grok agent, so
/// is otherwise the only place skills are surfaced for the kigi agent, so
/// before the fix a switch into such an agent — whose rebuilt bridge holds
/// a pending `BaselineChange` — dropped the skill listing for a no-tool first
/// turn. Drives the real `inject_baseline_skill_reminder` seam that
@@ -187,7 +185,7 @@ async fn rebuild_reinjects_baseline_skill_reminder_for_non_cursor() {
let text = reminder.text_content();
assert!(
text.contains("The following skills are available for use:"),
"reminder must carry the grok skill catalog header:\n{text}",
"reminder must carry the kigi skill catalog header:\n{text}",
);
assert!(
text.contains("regression-baseline-skill"),
@@ -88,7 +88,7 @@ async fn subagent_usage_fold_attribution_gate() {
#[test]
fn usage_drain_outcome_policy_matches_freeze_and_cancel() {
use super::turn::UsageDrainOutcome;
use kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply;
use kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply;
let none = UsageDrainOutcome::from_outstanding_reply(None);
assert!(none.fail_closed);
@@ -341,11 +341,10 @@ async fn snapshot_ors_ledger_incomplete_even_when_reply_complete() {
/// Scripted coordinator stub: answers each `Outstanding` query with the next
/// queued reply, repeating the last one; other events are ignored.
fn scripted_outstanding_responder(
replies: Vec<kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply>,
) -> tokio::sync::mpsc::UnboundedSender<
kigi_tools::implementations::grok_build::task::types::SubagentEvent,
> {
use kigi_tools::implementations::grok_build::task::types::SubagentEvent;
replies: Vec<kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply>,
) -> tokio::sync::mpsc::UnboundedSender<kigi_tools::implementations::kigi::task::types::SubagentEvent>
{
use kigi_tools::implementations::kigi::task::types::SubagentEvent;
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<SubagentEvent>();
tokio::task::spawn_local(async move {
let mut queue = replies.into_iter();
@@ -365,7 +364,7 @@ fn scripted_outstanding_responder(
/// ledgers are marked incomplete.
#[tokio::test(flavor = "current_thread")]
async fn freeze_timeout_marks_report_and_both_ledgers() {
use kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply;
use kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply;
tokio::task::LocalSet::new()
.run_until(async {
let mut actor = make_actor().await;
@@ -404,7 +403,7 @@ async fn freeze_timeout_marks_report_and_both_ledgers() {
/// because its fold still lands on the session ledger at completion.
#[tokio::test(flavor = "current_thread")]
async fn freeze_background_only_flags_report_not_ledgers() {
use kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply;
use kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply;
tokio::task::LocalSet::new()
.run_until(async {
let mut actor = make_actor().await;
@@ -446,7 +445,7 @@ async fn freeze_background_only_flags_report_not_ledgers() {
#[tokio::test(flavor = "current_thread")]
async fn finalize_background_only_flags_report_not_ledgers() {
use super::turn::UsageDrainOutcome;
use kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply;
use kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply;
tokio::task::LocalSet::new()
.run_until(async {
@@ -583,7 +582,7 @@ async fn apply_miss_matching_pin_stains_prompt_and_session() {
/// Sticky (session-only) is report-only on freeze: session ledger stays complete.
#[tokio::test(flavor = "current_thread")]
async fn freeze_sticky_only_flags_report_not_ledgers() {
use kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply;
use kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply;
tokio::task::LocalSet::new()
.run_until(async {
let mut actor = make_actor().await;
@@ -637,7 +636,7 @@ async fn freeze_sticky_only_flags_report_not_ledgers() {
/// A fold landing mid-drain completes cleanly: no incomplete flag anywhere.
#[tokio::test(flavor = "current_thread")]
async fn freeze_completes_when_fold_lands_mid_drain() {
use kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply;
use kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply;
tokio::task::LocalSet::new()
.run_until(async {
let mut actor = make_actor().await;
@@ -21,16 +21,16 @@ pub(crate) async fn test_agent_default() -> kigi_agent::Agent {
/// resolve to their builtins when a turn is driven through `handle_prompt`.
#[cfg(test)]
pub(crate) async fn test_agent_with_goal_tool() -> kigi_agent::Agent {
use kigi_tools::implementations::grok_build::update_goal::UpdateGoalTool;
use kigi_tools::implementations::kigi::update_goal::UpdateGoalTool;
use kigi_tools::registry::types::ToolConfig;
test_agent_with_tools(vec![ToolConfig::for_tool::<UpdateGoalTool>()]).await
}
/// Grok-build agent with the real `TodoWriteTool` (id `todo_write`, kind
/// Kigi-build agent with the real `TodoWriteTool` (id `todo_write`, kind
/// `Plan`) registered, so `tool_for_kind(ToolKind::Plan)` resolves through the
/// live toolset instead of the literal fallback.
#[cfg(test)]
pub(crate) async fn test_grok_build_agent_with_todo() -> kigi_agent::Agent {
use kigi_tools::implementations::grok_build::todo::TodoWriteTool;
pub(crate) async fn test_kigi_agent_with_todo() -> kigi_agent::Agent {
use kigi_tools::implementations::kigi::todo::TodoWriteTool;
use kigi_tools::registry::types::ToolConfig;
test_agent_with_tools(vec![ToolConfig::for_tool::<TodoWriteTool>()]).await
}
@@ -39,8 +39,8 @@ pub(crate) async fn test_grok_build_agent_with_todo() -> kigi_agent::Agent {
/// `exit_plan_mode` only finalizes when `enter_plan_mode` is also present.
#[cfg(test)]
pub(crate) async fn test_agent_with_plan_tools() -> kigi_agent::Agent {
use kigi_tools::implementations::grok_build::enter_plan_mode::EnterPlanModeTool;
use kigi_tools::implementations::grok_build::exit_plan_mode::ExitPlanModeTool;
use kigi_tools::implementations::kigi::enter_plan_mode::EnterPlanModeTool;
use kigi_tools::implementations::kigi::exit_plan_mode::ExitPlanModeTool;
use kigi_tools::registry::types::ToolConfig;
test_agent_with_tools(vec![
ToolConfig::for_tool::<EnterPlanModeTool>(),
@@ -57,7 +57,7 @@ pub(crate) async fn test_agent_with_tools(
tools,
behavior_preset: None,
},
kigi_agent::AgentDefinition::default_grok_build(),
kigi_agent::AgentDefinition::default_kigi(),
std::sync::Arc::new(kigi_tools::computer::local::LocalTerminalBackend::new()),
)
.await
@@ -78,7 +78,7 @@ async fn test_agent_from_config(
backend,
fs,
cwd: std::path::PathBuf::from("/tmp"),
session_folder: std::env::temp_dir().join("grok-test"),
session_folder: std::env::temp_dir().join("kigi-test"),
session_env: std::sync::Arc::new(std::collections::HashMap::new()),
notification_handle: ToolNotificationHandle::noop(),
owner_session_id: None,
@@ -388,7 +388,7 @@ async fn send_now_cancel_stamps_cancel_trigger_on_turn_end() {
let mut wire_meta = None;
while let Ok(msg) = gateway_rx.try_recv() {
if let kigi_acp_lib::AcpClientMessage::ExtNotification(args) = msg
&& args.request.method.as_ref() == "x.ai/session_notification"
&& args.request.method.as_ref() == "kigi/session_notification"
&& let Ok(v) =
serde_json::from_str::<serde_json::Value>(args.request.params.get())
&& v["update"]["sessionUpdate"] == "turn_completed"
@@ -9,7 +9,7 @@ async fn web_search_errors_when_disabled() {
let builder = crate::tools::bridge::ToolBridge::get_builder();
let config = ToolServerConfig {
tools: vec![ToolConfig {
id: "GrokBuild:web_search".into(),
id: "Kigi:web_search".into(),
params: None,
name_override: None,
params_name_overrides: None,
@@ -26,13 +26,13 @@ async fn web_search_errors_when_disabled() {
backend: terminal,
fs,
cwd: std::env::temp_dir(),
session_folder: std::env::temp_dir().join("grok-web-search-disabled"),
session_folder: std::env::temp_dir().join("kigi-web-search-disabled"),
session_env: std::sync::Arc::new(std::collections::HashMap::new()),
notification_handle: ToolNotificationHandle::noop(),
owner_session_id: None,
parent_scheduler_handle: None,
skills: vec![],
state_path: std::env::temp_dir().join("grok-web-search-disabled/state.json"),
state_path: std::env::temp_dir().join("kigi-web-search-disabled/state.json"),
memory_backend: None,
web_search_config: kigi_tools::implementations::web_search::WebSearchConfig::Disabled,
web_fetch_config: Default::default(),
@@ -460,7 +460,7 @@ pub struct ContextInfo {
/// at the time this snapshot was captured. Comes from the 6-tier resolution
/// (env > user per-model > user global > GB per-model > GB global > 85).
/// Used by the TUI `/context` view so the displayed “Auto-compact at X%”
/// always matches the actual trigger (e.g. 65 for grok-build in remote settings).
/// always matches the actual trigger (e.g. 65 for kigi in remote settings).
#[serde(default = "default_auto_compact_threshold")]
pub auto_compact_threshold_percent: u8,
/// Itemized usage rows (skills listing, MCP server listing). Empty on
@@ -495,7 +495,7 @@ fn default_auto_compact_threshold() -> u8 {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionInfoData {
/// Agent definition name for this session (e.g. `grok-build`).
/// Agent definition name for this session (e.g. `kigi`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent_name: Option<String>,
pub model: Option<String>,
@@ -554,7 +554,7 @@ pub fn model_display_name(
model.to_string()
}
/// Full wire response for `x.ai/session/info`.
/// Full wire response for `kigi/session/info`.
///
/// Wraps `SessionInfoData` with session-level fields (`session_id`, `cwd`)
/// that come from the agent layer rather than the session actor.
@@ -659,7 +659,7 @@ mod tests {
);
assert_eq!(input.session_id, "sess-1");
let submission = input.to_submission(Some("grok-3".into()), None, None, Some(5));
let submission = input.to_submission(Some("kigi-3".into()), None, None, Some(5));
assert_eq!(
submission.client_type,
crate::session::feedback_types::ClientType::Desktop
@@ -44,12 +44,12 @@ use kigi_agent::prompt::context::PromptAudience;
use kigi_agent::prompt::skills::SkillsConfig;
use kigi_agent::{Agent, AgentBuilder, CompactionPolicy, ReminderPolicy};
use kigi_tools::computer::types::{AsyncFileSystem, TerminalBackend};
use kigi_tools::implementations::grok_build::ask_user_question::types::UserQuestionRequest;
use kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig;
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::ask_user_question::types::UserQuestionRequest;
use kigi_tools::implementations::kigi::deploy_app::AppBuilderDeployerConfig;
use kigi_tools::implementations::kigi::task::types::{
MonitorEventBuffer, SubagentEvent, TaskModelValidator,
};
use kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig;
use kigi_tools::implementations::kigi::web_fetch::WebFetchConfig;
use kigi_tools::implementations::lsp::LspBackend;
use kigi_tools::implementations::web_search::WebSearchConfig;
use kigi_tools::notification::ToolNotificationHandle;
@@ -125,7 +125,7 @@ pub(crate) struct AgentRebuildSpec {
pub system_prompt_label: String,
pub owner_session_id: Option<String>,
pub parent_scheduler_handle:
Option<kigi_tools::implementations::grok_build::scheduler::types::SchedulerHandle>,
Option<kigi_tools::implementations::kigi::scheduler::types::SchedulerHandle>,
}
impl AgentRebuildSpec {
/// Build a fresh [`Agent`] from this spec and an [`AgentDefinition`].
@@ -305,10 +305,10 @@ impl AgentRebuildSpec {
}))
.await;
if let Some(event_tx) = subagent_event_tx.clone() {
use kigi_tools::implementations::grok_build::task::backend::{
use kigi_tools::implementations::kigi::task::backend::{
ChannelBackend, SubagentBackendResource,
};
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SessionIdResource, SubagentDepthCounter, SubagentEventSender,
};
let backend = SubagentBackendResource(Arc::new(ChannelBackend::new(event_tx.clone())));
@@ -342,7 +342,7 @@ impl AgentRebuildSpec {
))
.await;
{
use kigi_tools::implementations::grok_build::ask_user_question::UserQuestionSender;
use kigi_tools::implementations::kigi::ask_user_question::UserQuestionSender;
agent
.tool_bridge()
.update_resource(UserQuestionSender(user_question_tx.clone()))
@@ -424,13 +424,13 @@ mod tests {
let toolset = agent.tool_bridge().toolset();
let task_name = toolset
.tool_name_for_kind(kigi_tools::types::tool::ToolKind::Task)
.expect("GrokBuild Task tool should be present");
.expect("Kigi Task tool should be present");
toolset
.tool_definitions()
.into_iter()
.find(|definition| definition.function.name == task_name)
.and_then(|definition| definition.function.description)
.expect("GrokBuild Task description should be present")
.expect("Kigi Task description should be present")
}
#[tokio::test(flavor = "current_thread")]
async fn rebuild_projects_fresh_public_model_keys_into_task_description() {
@@ -453,7 +453,7 @@ mod tests {
models_manager
.insert_test_entry("private-unselectable-model", unselectable);
let first = spec
.build_agent(AgentDefinition::default_grok_build())
.build_agent(AgentDefinition::default_kigi())
.await
.expect("first agent build should succeed");
let first_description = task_description(&first);
@@ -478,7 +478,7 @@ mod tests {
.insert_test_entry("beta-public", model_entry("internal-beta"));
assert!(validator.error_for("beta-public").is_none());
let rebuilt = spec
.build_agent(AgentDefinition::default_grok_build())
.build_agent(AgentDefinition::default_kigi())
.await
.expect("rebuilt agent should succeed");
let rebuilt_description = task_description(&rebuilt);
@@ -186,10 +186,10 @@ pub enum SessionCommand {
/// and does NOT update `primaryModelId` in signals — the resolved model
/// is already tracked via inference responses), this command also calls
/// `set_primary_model()` so that signals report the override model
/// rather than the agent-level default (e.g. `grok-4.5`).
/// rather than the agent-level default (e.g. `kigi-4.5`).
///
/// Keeps the existing base_url, api_key, and other config — only changes
/// the `model` field sent in the `x-grok-model-override` header and merges
/// the `model` field sent in the `x-kigi-model-override` header and merges
/// any additional headers (e.g. `x-openrouter-api-key` for BYOK).
///
/// Used to set model IDs (e.g. opaque third-party routing names) that are
@@ -256,7 +256,7 @@ pub enum SessionCommand {
request: RewindRequest,
respond_to: oneshot::Sender<anyhow::Result<RewindResponse>>,
},
/// Out-of-band history repair (`x.ai/session/repair`): fix tool-pairing
/// Out-of-band history repair (`kigi/session/repair`): fix tool-pairing
/// violations (orphaned/displaced `ToolResult`s, duplicates, unanswered
/// calls) that would otherwise 400 on every request. `dry_run` only
/// reports. Refused while a turn is in flight.
@@ -329,7 +329,7 @@ pub enum SessionCommand {
respond_to: oneshot::Sender<()>,
},
/// Update MCP servers for an existing session (used during reconnect or
/// mid-session via the `x.ai/session/update_mcp_servers` extension method).
/// mid-session via the `kigi/session/update_mcp_servers` extension method).
/// This replaces the current MCP server configuration and triggers re-initialization.
///
/// The caller is notified via `respond_to` once MCP re-initialization
@@ -453,7 +453,7 @@ pub enum SessionCommand {
action: kigi_hooks_plugins_types::PluginsAction,
respond_to: oneshot::Sender<kigi_hooks_plugins_types::ActionOutcome>,
},
/// This session's plugin registry, as served by `x.ai/plugins/list`.
/// This session's plugin registry, as served by `kigi/plugins/list`.
PluginsList {
respond_to: oneshot::Sender<Option<std::sync::Arc<kigi_agent::plugins::PluginRegistry>>>,
},
@@ -515,7 +515,7 @@ pub enum SessionCommand {
},
/// Replace the text of a queued (not-yet-running) prompt in place
/// (server-side LWW). Last write wins via the actor's
/// serialized mailbox; the rebroadcast of `x.ai/queue/changed` is the
/// serialized mailbox; the rebroadcast of `kigi/queue/changed` is the
/// truth signal for every attached client. The original `owner`
/// attribution is preserved; `editor` is recorded as the most recent
/// editor (for future "alice edited this" UX). A missing id, or an id
@@ -533,7 +533,7 @@ pub enum SessionCommand {
/// like [`RemoveQueuedPrompt`]. A benign no-op (the prompt stays queued and
/// runs normally) when no turn is running, the id names the running turn, is
/// stale/already-drained, or `owner` doesn't match. The rebroadcast of
/// `x.ai/queue/changed` is the truth signal for every attached client.
/// `kigi/queue/changed` is the truth signal for every attached client.
InterjectQueuedPrompt {
id: String,
expected_version: u64,
@@ -630,7 +630,7 @@ pub enum SessionCommand {
///
/// Fired by the client after a turn completes. The session builds a
/// compact text-only transcript of the recent conversation, makes one
/// tool-free model call (default `grok-build-0.1` when available via
/// tool-free model call (default `kigi-0.1` when available via
/// `model_override`, else the session model), sanitizes the output, and
/// returns the predicted prompt via `respond_to`. Best-effort: any
/// failure returns `None`.
@@ -640,7 +640,7 @@ pub enum SessionCommand {
},
/// Rewrite a raw memory note into well-structured markdown via a one-shot
/// LLM call. The session uses `prepare_chat_completion()` with
/// `grok-build` model, low temperature, and capped output tokens.
/// `kigi` model, low temperature, and capped output tokens.
RewriteMemoryNote {
raw_text: String,
context_summary: String,
@@ -653,7 +653,7 @@ pub enum SessionCommand {
Interject {
text: String,
/// Client-minted id echoed back on the broadcast
/// `x.ai/session/interjection` so the originating pager can dedup its
/// `kigi/session/interjection` so the originating pager can dedup its
/// optimistic local block. `None` from older clients.
id: Option<String>,
/// Pasted images riding along with the interjection. Empty from
@@ -1243,7 +1243,7 @@ impl SessionActor {
self.tool_context.subagent_event_tx
{
let (tx, rx) = tokio::sync::oneshot::channel();
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentEvent, SubagentListActiveRequest,
};
let _ =
@@ -2830,11 +2830,11 @@ mod inline_auto_compact_flow_tests {
SuppressReason::CreditBlock
);
assert_eq!(
classify("API error (status 402 Payment Required): Grok Build usage balance exhausted"),
classify("API error (status 402 Payment Required): Kigi usage balance exhausted"),
SuppressReason::CreditBlock
);
assert_eq!(
classify("Grok Build usage limit reached"),
classify("Kigi usage limit reached"),
SuppressReason::CreditBlock
);
assert_eq!(
@@ -163,7 +163,7 @@ mod prefire_state_tests {
note1: "NOTE1".to_string(),
prefix_len: 3,
fingerprint: 42,
model_slug: "grok".to_string(),
model_slug: "kigi".to_string(),
pass1_latency_ms: 5,
}
}
@@ -569,7 +569,7 @@ pub const GOAL_ROLE_MODEL_FAIL_OPEN_SPAWN_FAILED: &str = "spawn_failed";
/// Fail-open — the configured `agent_type` resolves as a STRICT harness whose
/// subagent flavor `resolve_subagent_toolset` can't represent (e.g. `codex`):
/// committing it would silently run grok-build flavor. Distinct from
/// committing it would silently run kigi flavor. Distinct from
/// `toolset_unknown` (a name that doesn't resolve at all).
pub const GOAL_ROLE_MODEL_FAIL_OPEN_HARNESS_FLAVOR_UNSUPPORTED: &str = "harness_flavor_unsupported";
@@ -24,7 +24,7 @@ struct XaiJsonRpcNotification<'a> {
}
const ACP_SESSION_UPDATE_METHOD: &str = "session/update";
const XAI_SESSION_UPDATE_METHOD: &str = "_x.ai/session/update";
const XAI_SESSION_UPDATE_METHOD: &str = "_kigi/session/update";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportedMessage {
@@ -1,4 +1,4 @@
//! Feedback request heuristics for Grok Code sessions.
//! Feedback request heuristics for Kigi Code sessions.
//!
//! This module implements the feedback request decision logic based on session signals.
//! It uses tiered probability sampling to request feedback at appropriate moments
@@ -245,7 +245,7 @@ impl FeedbackHeuristics {
tier1_feedback_mode: FeedbackMode::Thumbs,
tier1_dismissible: true,
tier1_prompt:
"You've been using Grok Code productively! Would you mind sharing quick feedback?"
"You've been using Kigi Code productively! Would you mind sharing quick feedback?"
.to_string(),
// Tier 2: Complex session with friction
@@ -452,7 +452,7 @@ impl FeedbackHeuristics {
tier1_feedback_mode: FeedbackMode::Thumbs,
tier1_dismissible: true,
tier1_prompt:
"You've been using Grok Code productively! Would you mind sharing quick feedback?"
"You've been using Kigi Code productively! Would you mind sharing quick feedback?"
.to_string(),
tier2_enabled: true,
tier2_sample_rate: 0.0002,
@@ -774,7 +774,7 @@ impl FeedbackRequest {
Some(p) if !p.is_empty() => p.to_string(),
_ => match tier {
FeedbackTier::Tier1 => {
"You've been using Grok Code productively! Would you mind sharing quick feedback?".to_string()
"You've been using Kigi Code productively! Would you mind sharing quick feedback?".to_string()
}
FeedbackTier::Tier2 => {
"You've worked through a complex session. Your feedback would help us improve.".to_string()
@@ -374,7 +374,7 @@ impl FeedbackManager {
/// heuristics, sampling, cooldown, and enabled checks.
///
/// Engineers developing clients can call this via the
/// `x.ai/debug/trigger_feedback` ACP extension method to exercise
/// `kigi/debug/trigger_feedback` ACP extension method to exercise
/// the full feedback notification ↔ response flow without needing a
/// real session that meets tier criteria.
#[tracing::instrument(name = "feedback.force_feedback_request", skip_all, fields(
@@ -185,7 +185,7 @@ mod tests {
source_cwd: "/old/project".to_string(),
new_cwd: "/new/project".to_string(),
new_session_id: Some("custom-session-id".to_string()),
new_model_id: Some("grok-3".to_string()),
new_model_id: Some("kigi-3".to_string()),
target_prompt_index: None,
..Default::default()
};
@@ -200,7 +200,7 @@ mod tests {
deserialized.new_session_id,
Some("custom-session-id".to_string())
);
assert_eq!(deserialized.new_model_id, Some("grok-3".to_string()));
assert_eq!(deserialized.new_model_id, Some("kigi-3".to_string()));
}
#[test]
@@ -223,7 +223,7 @@ mod tests {
plan_state_copied: true,
new_cwd: "/new/project".to_string(),
parent_session_id: "abc123".to_string(),
new_model_id: Some("grok-3".to_string()),
new_model_id: Some("kigi-3".to_string()),
};
let json = serde_json::to_string(&response).unwrap();
@@ -235,7 +235,7 @@ mod tests {
assert!(deserialized.plan_state_copied);
assert_eq!(deserialized.new_cwd, "/new/project");
assert_eq!(deserialized.parent_session_id, "abc123");
assert_eq!(deserialized.new_model_id, Some("grok-3".to_string()));
assert_eq!(deserialized.new_model_id, Some("kigi-3".to_string()));
}
#[test]
@@ -70,7 +70,7 @@ pub(crate) fn forward_to_hunk_tracker(
}
}
/// Dedup key for `x.ai/git_head_changed`, shared by the watcher's `GitHead`
/// Dedup key for `kigi/git_head_changed`, shared by the watcher's `GitHead`
/// consumer and the post-edit `maybe_notify_git_branch` path so both compute
/// the same identity (branch | is_worktree | main_repo).
pub(crate) fn git_head_dedup_key(
@@ -264,7 +264,7 @@ pub(crate) struct CapabilityInputs {
pub client_notify: bool,
pub hunk_tracking: bool,
pub code_nav: bool,
/// `x.ai/gitHeadChanged`; opt-in (absent => off).
/// `kigi/gitHeadChanged`; opt-in (absent => off).
pub git_head_changed: Option<bool>,
}
@@ -319,7 +319,7 @@ impl ClientNotify {
match self.mode {
ClientFsMode::Events => {
// Present-tense strings are the `x.ai/fs_notify` wire protocol;
// Present-tense strings are the `kigi/fs_notify` wire protocol;
// do not sync to internal variant names.
let kind_str = match kind {
FsEventKind::Created => "Create",
@@ -339,7 +339,7 @@ impl ClientNotify {
if let Ok(raw) = to_raw_value(&params) {
self.gateway
.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/fs_notify",
"kigi/fs_notify",
raw.into(),
));
}
@@ -365,7 +365,7 @@ impl ClientNotify {
if let Ok(raw) = to_raw_value(&params) {
self.gateway
.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/fs/index/delta",
"kigi/fs/index/delta",
raw.into(),
));
}
@@ -425,7 +425,7 @@ impl ClientNotify {
if let Ok(raw) = serde_json::value::to_raw_value(&params) {
self.gateway
.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/fs/index",
"kigi/fs/index",
raw.into(),
));
}
@@ -526,7 +526,7 @@ impl GitHead {
if let Ok(raw) = serde_json::value::to_raw_value(&params) {
self.gateway
.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/git_head_changed",
"kigi/git_head_changed",
raw.into(),
));
}
@@ -444,7 +444,7 @@ pub(crate) async fn capture_git_baseline(workspace_root: &Path) -> Option<String
/// never sees the spawn live — it is direct (no `task` tool call).
pub(crate) struct ChannelSpawner {
pub(crate) event_tx: tokio::sync::mpsc::UnboundedSender<
kigi_tools::implementations::grok_build::task::types::SubagentEvent,
kigi_tools::implementations::kigi::task::types::SubagentEvent,
>,
pub(crate) parent_session_id: String,
pub(crate) parent_prompt_id: Option<String>,
@@ -503,7 +503,7 @@ impl ChannelSpawner {
harness_agent_type: Option<String>,
resume_from: Option<&str>,
) -> Result<String, SpawnError> {
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentEvent, SubagentRequest, SubagentRuntimeOverrides,
};
let (result_tx, result_rx) = tokio::sync::oneshot::channel();
@@ -2355,7 +2355,7 @@ mod tests {
#[tokio::test]
async fn channel_spawner_request_is_harness_internal() {
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let spawner = ChannelSpawner {
@@ -2402,7 +2402,7 @@ mod tests {
/// SAME model — i.e. skeptic-0 keeps `pool[0]` on the cold fallback.
#[tokio::test]
async fn channel_spawner_applies_per_index_model_to_request() {
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let spawner = ChannelSpawner {
@@ -2470,7 +2470,7 @@ mod tests {
/// `None` — the historic default-spawn behavior.
#[tokio::test]
async fn channel_spawner_inherit_index_leaves_model_none() {
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let spawner = ChannelSpawner {
event_tx: tx,
@@ -2514,7 +2514,7 @@ mod tests {
let mac_like = Path::new("/var/folders/zz/T");
assert!(
validate_details_path_in_root(
Path::new("/var/folders/zz/T/grok-goal-abc/goal-classifier-abc-1.md"),
Path::new("/var/folders/zz/T/kigi-goal-abc/goal-classifier-abc-1.md"),
mac_like,
)
.is_ok(),
@@ -2527,7 +2527,7 @@ mod tests {
);
assert!(
validate_details_path_in_root(
Path::new("/tmp/grok-goal-abc/goal-classifier-abc-1.md"),
Path::new("/tmp/kigi-goal-abc/goal-classifier-abc-1.md"),
Path::new("/tmp"),
)
.is_ok(),
@@ -3478,10 +3478,10 @@ mod tests {
]);
assert_eq!(a, b, "scratch-path churn must not break the fingerprint");
let c = gap_fingerprint(&[
"no captured output in /var/folders/x1/T/grok-goal-1/out.log for criterion 2",
"no captured output in /var/folders/x1/T/kigi-goal-1/out.log for criterion 2",
]);
let d = gap_fingerprint(&[
"no captured output in /var/folders/x1/T/grok-goal-2/out.log for criterion 2",
"no captured output in /var/folders/x1/T/kigi-goal-2/out.log for criterion 2",
]);
assert_eq!(c, d);
// Genuinely different gaps still differ.
@@ -3908,7 +3908,7 @@ mod tests {
/// renders leave no tool placeholder unresolved.
#[test]
fn verifier_template_renders_per_agent_type_and_falls_back() {
use kigi_tools::implementations::grok_build::task::types::SubagentTypeSummary;
use kigi_tools::implementations::kigi::task::types::SubagentTypeSummary;
let mut tool_names = std::collections::HashMap::new();
tool_names.insert(
kigi_tools::types::tool::ToolKind::Read,
@@ -3941,14 +3941,14 @@ mod tests {
);
assert_no_tool_placeholders(&cursor);
// grok-build explicit render: no leftover placeholder either.
let grok = RoleToolNames::from_summary(&summary_with(&[
// kigi explicit render: no leftover placeholder either.
let kigi = RoleToolNames::from_summary(&summary_with(&[
(kigi_tools::types::tool::ToolKind::Read, "read_file"),
(kigi_tools::types::tool::ToolKind::ListDir, "list_dir"),
(kigi_tools::types::tool::ToolKind::Search, "grep"),
]))
.apply(GOAL_VERIFIER_PROMPT_TEMPLATE);
assert_no_tool_placeholders(&grok);
assert_no_tool_placeholders(&kigi);
// Fallback path (e.g. `describe_subagent_type` ⇒ `Unavailable`): the
// parent-toolset defaults render and no placeholder survives.
@@ -4123,8 +4123,8 @@ mod tests {
"/tmp/goal-verifier-details-x-1-0.md",
"/tmp/goal-verdict-x-1-0.json",
kind_lens(Some(GoalKind::CodeChange)),
"/tmp/grok-goal-x/skeptic-0",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/skeptic-0",
"/tmp/kigi-goal-x/implementer",
None,
&RoleToolNames::inherit_defaults(),
true,
@@ -4136,8 +4136,8 @@ mod tests {
);
// The skeptic's own scratch dir AND the implementer-scratch
// awareness line are both present, with no dangling placeholder.
assert!(body.contains("/tmp/grok-goal-x/skeptic-0"));
assert!(body.contains("/tmp/grok-goal-x/implementer"));
assert!(body.contains("/tmp/kigi-goal-x/skeptic-0"));
assert!(body.contains("/tmp/kigi-goal-x/implementer"));
assert!(
!body.contains("{SKEPTIC_SCRATCH}") && !body.contains("{IMPLEMENTER_SCRATCH}"),
"scratch placeholders must be substituted:\n{body}"
@@ -4154,8 +4154,8 @@ mod tests {
"/tmp/goal-verifier-details-x-1-0.md",
"/tmp/goal-verdict-x-1-0.json",
kind_lens(None),
"/tmp/grok-goal-x/skeptic-1",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/skeptic-1",
"/tmp/kigi-goal-x/implementer",
None,
&RoleToolNames::inherit_defaults(),
true,
@@ -4181,8 +4181,8 @@ mod tests {
"/tmp/goal-verifier-details-x-1-0.md",
"/tmp/goal-verdict-x-1-0.json",
kind_lens(Some(GoalKind::CodeChange)),
"/tmp/grok-goal-x/skeptic-0",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/skeptic-0",
"/tmp/kigi-goal-x/implementer",
None,
&RoleToolNames::inherit_defaults(),
scratch_ready,
@@ -4232,8 +4232,8 @@ mod tests {
"/tmp/goal-verifier-details-x-2-1.md",
"/tmp/goal-verdict-x-2-1.json",
kind_lens(Some(GoalKind::CodeChange)),
"/tmp/grok-goal-x/skeptic-1",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/skeptic-1",
"/tmp/kigi-goal-x/implementer",
prior,
&RoleToolNames::inherit_defaults(),
true,
@@ -4266,8 +4266,8 @@ mod tests {
"/tmp/goal-classifier-x-2-skeptic-0.md",
"/tmp/goal-verdict-x-2-0.json",
kind_lens(Some(GoalKind::CodeChange)),
"/tmp/grok-goal-x/skeptic-0",
"/tmp/grok-goal-x/implementer",
"/tmp/kigi-goal-x/skeptic-0",
"/tmp/kigi-goal-x/implementer",
None,
&RoleToolNames::inherit_defaults(),
true,
@@ -4289,8 +4289,8 @@ mod tests {
assert!(body.contains("/tmp/goal-verdict-x-2-0.json"));
assert!(body.contains("/tmp/goal-classifier-x-2-skeptic-0.md"));
// Scratch dirs: own + implementer-awareness, both substituted.
assert!(body.contains("/tmp/grok-goal-x/skeptic-0"));
assert!(body.contains("/tmp/grok-goal-x/implementer"));
assert!(body.contains("/tmp/kigi-goal-x/skeptic-0"));
assert!(body.contains("/tmp/kigi-goal-x/implementer"));
assert!(
!body.contains("{KIND_LENS}")
&& !body.contains("{DETAILS_FILE}")
@@ -4649,11 +4649,11 @@ mod tests {
workspace_root,
verifier_id,
attempt,
model_id: "grok-test",
model_id: "kigi-test",
goal_created_at: 0,
plan_file: None,
plan_baseline_file: None,
implementer_scratch_dir: Path::new("/tmp/grok-goal-test/implementer"),
implementer_scratch_dir: Path::new("/tmp/kigi-goal-test/implementer"),
scratch_dir_ready: true,
skeptic_count,
max_runs: GOAL_CLASSIFIER_MAX_RUNS_DEFAULT,
@@ -4747,7 +4747,7 @@ mod tests {
// and this skeptic's own dir (derived from verifier_id) are both
// present; neither placeholder leaks.
assert!(
p.contains("/tmp/grok-goal-test/implementer"),
p.contains("/tmp/kigi-goal-test/implementer"),
"implementer scratch dir missing in prompt",
);
assert!(
@@ -5676,7 +5676,7 @@ mod tests {
/// `runtime_overrides.model`.
#[tokio::test]
async fn cold_fallback_after_resume_failure_carries_pool0_model_on_request() {
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
use std::sync::Mutex as StdMutex;
// (model, resume_from) per spawn, in spawn order.
@@ -6068,7 +6068,7 @@ mod tests {
#[tokio::test]
async fn channel_spawner_blocks_until_subagent_result() {
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel();
let release = Arc::new(Notify::new());
@@ -6328,7 +6328,7 @@ mod tests {
verifier_id: &vid,
attempt: 1,
kind_lens: "",
implementer_scratch: "/tmp/grok-goal-test/implementer",
implementer_scratch: "/tmp/kigi-goal-test/implementer",
scratch_dir_ready: true,
prior_gaps: None,
};
@@ -1421,7 +1421,7 @@ mod tests {
#[test]
fn evidence_packet_plan_path_with_spaces_and_unicode_round_trips() {
let plan = "/tmp/grok sessions/✓ goal/plan.md";
let plan = "/tmp/kigi sessions/✓ goal/plan.md";
let packet = build_classifier_evidence_packet(
"obj",
ChangesRef::Unavailable,
@@ -104,7 +104,7 @@ impl GoalNotifySender {
}
if let Some(raw) = raw {
let ext = agent_client_protocol::ExtNotification::new(
"x.ai/session_notification",
"kigi/session_notification",
raw.into(),
);
self.gateway.forward_fire_and_forget(ext);
@@ -432,7 +432,7 @@ mod tests {
// — the wire field stays empty so the doc contract holds and we
// don't ship a 1-element vec the pager would collapse anyway.
let mut o = make_base_orchestration();
o.live_tokens_by_model = vec![("grok-4".into(), 5_000)];
o.live_tokens_by_model = vec![("kigi-4".into(), 5_000)];
match build_goal_updated(&o, 0, 0) {
XaiSessionUpdate::GoalUpdated {
live_tokens_by_model,
@@ -445,14 +445,14 @@ mod tests {
}
// ≥2 distinct models are transmitted verbatim.
o.live_tokens_by_model = vec![("grok-4".into(), 5_000), ("grok-3".into(), 3_000)];
o.live_tokens_by_model = vec![("kigi-4".into(), 5_000), ("kigi-3".into(), 3_000)];
match build_goal_updated(&o, 0, 0) {
XaiSessionUpdate::GoalUpdated {
live_tokens_by_model,
..
} => assert_eq!(
live_tokens_by_model,
vec![("grok-4".to_owned(), 5_000), ("grok-3".to_owned(), 3_000)]
vec![("kigi-4".to_owned(), 5_000), ("kigi-3".to_owned(), 3_000)]
),
_ => panic!("expected GoalUpdated"),
}
@@ -21,7 +21,7 @@ use std::sync::Arc;
/// and the parent-side `describe_subagent_type` probe so the gated/probed
/// toolset matches the spawned one.
///
/// [`SubagentRuntimeOverrides::harness_agent_type`]: kigi_tools::implementations::grok_build::task::types::SubagentRuntimeOverrides::harness_agent_type
/// [`SubagentRuntimeOverrides::harness_agent_type`]: kigi_tools::implementations::kigi::task::types::SubagentRuntimeOverrides::harness_agent_type
pub(crate) const GOAL_ROLE_SUBAGENT_TYPE: &str = "general-purpose";
/// Resolved per-role spawn override.
@@ -36,7 +36,7 @@ pub(crate) const GOAL_ROLE_SUBAGENT_TYPE: &str = "general-purpose";
pub(crate) struct RoleSpawnOverride {
/// Resolved, post-auth, post-fail-open model id, or `None` to inherit.
pub model: Option<String>,
/// Resolved harness `agent_type` (e.g. `"grok-build-plan"`)
/// Resolved harness `agent_type` (e.g. `"kigi-plan"`)
/// whose `AgentDefinition` decides the spawned subagent's harness flavor
/// (system prompt + toolset), applied REGARDLESS of the
/// parent agent. `None` ⇒ inherit the session harness. NOT a subagent type —
@@ -254,7 +254,7 @@ pub(crate) fn parse_terminal_response(text: &str) -> bool {
pub(crate) struct ChannelSpawner {
pub(crate) event_tx: tokio::sync::mpsc::UnboundedSender<
kigi_tools::implementations::grok_build::task::types::SubagentEvent,
kigi_tools::implementations::kigi::task::types::SubagentEvent,
>,
pub(crate) parent_session_id: String,
pub(crate) parent_prompt_id: Option<String>,
@@ -299,7 +299,7 @@ impl ChannelSpawner {
model: Option<String>,
harness_agent_type: Option<String>,
) -> Result<String, SpawnError> {
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentEvent, SubagentRequest, SubagentRuntimeOverrides,
};
let (result_tx, result_rx) = tokio::sync::oneshot::channel();
@@ -499,7 +499,7 @@ mod tests {
#[test]
fn planner_template_default_render_preserves_wording_and_has_no_placeholders() {
// Default/inherit render: placeholders resolve to the literal parent
// (grok-build) tool names; guards against accidental wording drift.
// (kigi) tool names; guards against accidental wording drift.
let rendered = RoleToolNames::inherit_defaults().apply(GOAL_PLANNER_PROMPT_TEMPLATE);
assert!(
rendered.contains("with your\n`read_file`/`grep`/`list_dir` tools to clarify scope"),
@@ -597,7 +597,7 @@ mod tests {
#[tokio::test]
async fn channel_spawner_request_is_harness_internal() {
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let spawner = ChannelSpawner {
@@ -740,7 +740,7 @@ mod tests {
context: "",
plan_file: &plan_file,
attempt: 1,
model_id: "grok-test",
model_id: "kigi-test",
tool_names: &RoleToolNames::inherit_defaults(),
inherit_tool_names: &RoleToolNames::inherit_defaults(),
},
@@ -769,7 +769,7 @@ mod tests {
context: "",
plan_file: &plan_file,
attempt: 1,
model_id: "grok-test",
model_id: "kigi-test",
tool_names: &RoleToolNames::inherit_defaults(),
inherit_tool_names: &RoleToolNames::inherit_defaults(),
},
@@ -804,7 +804,7 @@ mod tests {
context: "",
plan_file: &plan_file,
attempt: 1,
model_id: "grok-test",
model_id: "kigi-test",
tool_names: &RoleToolNames::inherit_defaults(),
inherit_tool_names: &RoleToolNames::inherit_defaults(),
},
@@ -846,7 +846,7 @@ mod tests {
context: "",
plan_file: &plan_file,
attempt: 1,
model_id: "grok-test",
model_id: "kigi-test",
tool_names: &RoleToolNames::inherit_defaults(),
inherit_tool_names: &RoleToolNames::inherit_defaults(),
},
@@ -888,7 +888,7 @@ mod tests {
context: "",
plan_file: &plan_file,
attempt: 1,
model_id: "grok-test",
model_id: "kigi-test",
tool_names: &RoleToolNames::inherit_defaults(),
inherit_tool_names: &RoleToolNames::inherit_defaults(),
},
@@ -927,7 +927,7 @@ mod tests {
context: "",
plan_file: &plan_file,
attempt: 1,
model_id: "grok-test",
model_id: "kigi-test",
tool_names: &RoleToolNames::inherit_defaults(),
inherit_tool_names: &RoleToolNames::inherit_defaults(),
},
@@ -953,7 +953,7 @@ mod tests {
context: "",
plan_file: &plan_file,
attempt: 1,
model_id: "grok-test",
model_id: "kigi-test",
tool_names: &RoleToolNames::inherit_defaults(),
inherit_tool_names: &RoleToolNames::inherit_defaults(),
},
@@ -990,7 +990,7 @@ mod tests {
context: "prior conversation\n",
plan_file: &plan_file,
attempt: 1,
model_id: "grok-test",
model_id: "kigi-test",
tool_names: &RoleToolNames::inherit_defaults(),
inherit_tool_names: &RoleToolNames::inherit_defaults(),
},
@@ -1197,7 +1197,7 @@ mod tests {
/// request's `harness_agent_type`, not the subagent_type.
#[tokio::test]
async fn channel_spawner_threads_harness_override_to_request() {
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let spawner = ChannelSpawner {
event_tx: tx,
@@ -1494,7 +1494,7 @@ mod tests {
/// `ChannelSpawner` whose explicit spawn fails still returns `Planned`.
#[tokio::test]
async fn planner_retries_to_inherit_instead_of_failing_closed() {
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
let plan_file = tmp_plan_file("retry-failopen");
let plan_for_coord = plan_file.clone();
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
@@ -1540,7 +1540,7 @@ mod tests {
context: "",
plan_file: &plan_file,
attempt: 1,
model_id: "grok-test",
model_id: "kigi-test",
tool_names: &RoleToolNames::inherit_defaults(),
inherit_tool_names: &RoleToolNames::inherit_defaults(),
},
@@ -1561,7 +1561,7 @@ mod tests {
/// fail-CLOSED cancellation semantics.
#[tokio::test]
async fn planner_cancellation_pauses_as_aborted_without_retry() {
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
use std::sync::atomic::{AtomicUsize, Ordering};
let plan_file = tmp_plan_file("cancel-aborted");
let spawns = Arc::new(AtomicUsize::new(0));
@@ -1601,7 +1601,7 @@ mod tests {
context: "",
plan_file: &plan_file,
attempt: 1,
model_id: "grok-test",
model_id: "kigi-test",
tool_names: &RoleToolNames::inherit_defaults(),
inherit_tool_names: &RoleToolNames::inherit_defaults(),
},
@@ -40,7 +40,7 @@ pub(crate) struct RoleToolNames {
/// `{SEARCH_TOOL}` — `ToolKind::Search` (grep maps here).
pub search: String,
/// `{WRITE_TOOL}` — `ToolKind::Write`, falling back to `ToolKind::Edit`
/// (the default grok-build host's `search_replace` mutator) when `Write`
/// (the default kigi host's `search_replace` mutator) when `Write`
/// is absent from the describe summary.
pub write: String,
/// `{EXECUTE_TOOL}` — `ToolKind::Execute` (terminal/bash maps here).
@@ -99,7 +99,7 @@ impl RoleToolNames {
/// `{WRITE_TOOL}` falls back to the parent `Edit` tool name when the bridge
/// has no `Write` — mirroring [`Self::from_summary`], so the inherit / retry
/// render names the same mutator the subagent actually exposes (e.g.
/// `search_replace` on the default grok-build host) instead of the literal
/// `search_replace` on the default kigi host) instead of the literal
/// `write` default. No `{TOOLSET_TOOLS}` enumeration on the inherit path.
pub(crate) fn from_parent(
read: Option<String>,
@@ -127,14 +127,14 @@ impl RoleToolNames {
/// summary (the `name_override`-aware client name per kind), and
/// `{TOOLSET_TOOLS}` enumerates the toolset.
pub(crate) fn from_summary(
summary: &kigi_tools::implementations::grok_build::task::types::SubagentTypeSummary,
summary: &kigi_tools::implementations::kigi::task::types::SubagentTypeSummary,
) -> Self {
let get = |kind: ToolKind| summary.tool_names.get(&kind).cloned();
Self::from_parts(
get(ToolKind::Read),
get(ToolKind::ListDir),
get(ToolKind::Search),
// The default grok-build host's pre-spawn describe probe exposes only
// The default kigi host's pre-spawn describe probe exposes only
// `Edit` (`search_replace`) as the file mutator — the injection-only
// `write`/`Write` tool is absent there. Without this fallback
// `{WRITE_TOOL}` would render the literal `write` default instead of
@@ -264,7 +264,7 @@ fn enumerate_toolset_tools(tool_names: &std::collections::HashMap<ToolKind, Stri
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use kigi_tools::implementations::grok_build::task::types::SubagentTypeSummary;
use kigi_tools::implementations::kigi::task::types::SubagentTypeSummary;
/// Build a `SubagentTypeSummary` from `(ToolKind, name)` pairs for the
/// per-agent_type rendering tests. Shared with the planner / classifier /
@@ -312,8 +312,8 @@ pub(crate) mod tests {
}
#[test]
fn from_summary_uses_grok_build_names() {
// A grok-build toolset: client names match the literal defaults.
fn from_summary_uses_kigi_names() {
// A kigi toolset: client names match the literal defaults.
let tn = RoleToolNames::from_summary(&summary_with(&[
(ToolKind::Read, "read_file"),
(ToolKind::ListDir, "list_dir"),
@@ -379,7 +379,7 @@ pub(crate) mod tests {
fn web_tools_fall_back_when_absent_from_the_toolset() {
// A summary / parent bridge without WebSearch/WebFetch ⇒ both resolve to
// the stock client names, so the planner prompt still names a real tool
// on the default grok-build host (the stock `web_search`/`web_fetch`).
// on the default kigi host (the stock `web_search`/`web_fetch`).
let summary = RoleToolNames::from_summary(&summary_with(&[(ToolKind::Read, "rd")]));
assert_eq!(summary.web_search, "web_search");
assert_eq!(summary.web_fetch, "web_fetch");
@@ -389,8 +389,8 @@ pub(crate) mod tests {
}
#[test]
fn from_summary_write_falls_back_to_edit_on_default_grok_build_host() {
// Default grok-build host: the pre-spawn describe probe exposes only
fn from_summary_write_falls_back_to_edit_on_default_kigi_host() {
// Default kigi host: the pre-spawn describe probe exposes only
// `Edit` (`search_replace`); `Write` is injection-only and absent. The
// planner gate accepts this toolset, so `{WRITE_TOOL}` must name the
// real mutator (`search_replace`), not the literal `write` default.
@@ -415,7 +415,7 @@ pub(crate) mod tests {
#[test]
fn from_parent_write_falls_back_to_edit_when_bridge_has_no_write() {
// Default grok-build parent bridge: no `Write`, only `Edit`
// Default kigi parent bridge: no `Write`, only `Edit`
// (`search_replace`). The inherit / fail-open render must name the
// real mutator, not the literal `write` default — matching
// `from_summary`'s primary render.
@@ -510,10 +510,10 @@ pub(crate) mod tests {
}
#[test]
fn parent_and_summary_renders_agree_on_default_grok_build_mutator() {
fn parent_and_summary_renders_agree_on_default_kigi_mutator() {
// The explicit-pair `primary` (from_summary) and inherit/fail-open
// `fallback` (from_parent) renders must name the SAME mutator on the
// default grok-build host (Edit-only toolset), so a fail-open retry
// default kigi host (Edit-only toolset), so a fail-open retry
// can never disagree with the first attempt's `{WRITE_TOOL}`.
let primary = RoleToolNames::from_summary(&summary_with(&[
(ToolKind::Read, "read_file"),
@@ -107,7 +107,7 @@ pub(crate) fn strategist_should_fire(consecutive: u32, last_fired: u32, every: u
pub(crate) struct ChannelSpawner {
pub(crate) event_tx: tokio::sync::mpsc::UnboundedSender<
kigi_tools::implementations::grok_build::task::types::SubagentEvent,
kigi_tools::implementations::kigi::task::types::SubagentEvent,
>,
pub(crate) parent_session_id: String,
pub(crate) parent_prompt_id: Option<String>,
@@ -152,7 +152,7 @@ impl ChannelSpawner {
model: Option<String>,
harness_agent_type: Option<String>,
) -> Result<String, SpawnError> {
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentEvent, SubagentRequest, SubagentRuntimeOverrides,
};
let (result_tx, result_rx) = tokio::sync::oneshot::channel();
@@ -541,7 +541,7 @@ mod tests {
#[tokio::test]
async fn channel_spawner_request_is_harness_internal() {
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let spawner = ChannelSpawner {
@@ -580,7 +580,7 @@ mod tests {
/// request's `harness_agent_type`, not the subagent_type.
#[tokio::test]
async fn channel_spawner_threads_harness_override_to_request() {
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let spawner = ChannelSpawner {
event_tx: tx,
@@ -809,7 +809,7 @@ mod tests {
attempt: 3,
consecutive_failures: 5,
every: 2,
model_id: "grok-test",
model_id: "kigi-test",
tool_names: default_tool_names(),
inherit_tool_names: default_tool_names(),
}
@@ -1210,7 +1210,7 @@ mod tests {
use crate::session::goal_role_tools::tests::assert_no_tool_placeholders;
/// Default/inherit render: the tool placeholders resolve to the literal
/// parent (grok-build) names, with no placeholder left behind. Guards
/// parent (kigi) names, with no placeholder left behind. Guards
/// against accidental wording drift in the strategist template.
#[test]
fn strategist_template_default_render_preserves_wording() {
@@ -1229,7 +1229,7 @@ mod tests {
/// explicit `from_summary` path leaves no tool placeholder unresolved.
#[test]
fn strategist_template_renders_per_agent_type_names() {
use kigi_tools::implementations::grok_build::task::types::SubagentTypeSummary;
use kigi_tools::implementations::kigi::task::types::SubagentTypeSummary;
let mut tool_names = std::collections::HashMap::new();
tool_names.insert(
kigi_tools::types::tool::ToolKind::Read,
@@ -82,7 +82,7 @@ pub(crate) trait GoalSummarizerSpawner: Send + Sync {
pub(crate) struct ChannelSpawner {
pub(crate) event_tx: tokio::sync::mpsc::UnboundedSender<
kigi_tools::implementations::grok_build::task::types::SubagentEvent,
kigi_tools::implementations::kigi::task::types::SubagentEvent,
>,
pub(crate) parent_session_id: String,
pub(crate) parent_prompt_id: Option<String>,
@@ -129,7 +129,7 @@ impl ChannelSpawner {
harness_agent_type: Option<String>,
) -> Result<String, SpawnError> {
use kigi_tool_types::SubagentCapabilityMode;
use kigi_tools::implementations::grok_build::task::types::{
use kigi_tools::implementations::kigi::task::types::{
SubagentEvent, SubagentRequest, SubagentRuntimeOverrides,
};
let (result_tx, result_rx) = tokio::sync::oneshot::channel();
@@ -342,7 +342,7 @@ mod tests {
.replace("{DETAILS_FILE}", &details_str)
.replace("{SESSION_TRACES_DIR}", &traces_dir_str);
let rendered = RoleToolNames::inherit_defaults().apply(&with_paths);
// §7 tool placeholders resolve to the default grok-build names.
// §7 tool placeholders resolve to the default kigi names.
assert!(rendered.contains("read_file"));
assert!(rendered.contains("grep"));
assert!(rendered.contains("list_dir"));
@@ -422,7 +422,7 @@ mod tests {
details_file: None,
session_traces_dir: plan.parent().unwrap(),
attempt: 2,
model_id: "grok-test",
model_id: "kigi-test",
tool_names,
}
}
@@ -604,7 +604,7 @@ mod tests {
#[tokio::test]
async fn channel_spawner_request_is_harness_internal_and_read_only() {
use kigi_tool_types::SubagentCapabilityMode;
use kigi_tools::implementations::grok_build::task::types::{SubagentEvent, SubagentResult};
use kigi_tools::implementations::kigi::task::types::{SubagentEvent, SubagentResult};
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let spawner = ChannelSpawner {
@@ -252,7 +252,7 @@ impl GoalHistoryEntry {
// GoalOrchestration (full persisted state)
/// Generate a short opaque identifier used to scope the per-goal
/// scratch root (`<temp_dir>/grok-goal-<id>`) and the verifier
/// scratch root (`<temp_dir>/kigi-goal-<id>`) and the verifier
/// verdict/details files inside it.
///
/// The id is a 12-char prefix of a UUIDv4 simple form — ~48 bits of
@@ -267,13 +267,13 @@ pub(crate) fn generate_verifier_id() -> String {
s
}
/// Private per-goal scratch root: `<temp_dir>/grok-goal-<verifier_id>`.
/// Private per-goal scratch root: `<temp_dir>/kigi-goal-<verifier_id>`.
///
/// Rooted at [`std::env::temp_dir`] (respects `TMPDIR`) and namespaced by the
/// goal's `verifier_id`, so concurrent goals never collide and cleanup of one
/// never touches another. Removed wholesale on every terminal goal transition.
pub(crate) fn goal_scratch_root(verifier_id: &str) -> PathBuf {
std::env::temp_dir().join(format!("grok-goal-{verifier_id}"))
std::env::temp_dir().join(format!("kigi-goal-{verifier_id}"))
}
/// Create (or verify) the goal's scratch root, locked to the owner
@@ -1646,7 +1646,7 @@ mod tests {
/// fresh values on every call. The fixed length is part of the
/// public contract — verifier file paths embed it verbatim, and
/// drift here would silently invalidate the documented
/// `grok-goal-<12 hex chars>` scratch-root format (and the
/// `kigi-goal-<12 hex chars>` scratch-root format (and the
/// 12-hex restore validation in `from_snapshot`).
#[test]
fn generate_verifier_id_is_short_hex_and_unique() {
@@ -2539,7 +2539,7 @@ mod tests {
activate_tracker(&mut t);
t.update_live_progress(
100,
vec![("grok-4".to_owned(), 60), ("grok-3".to_owned(), 40)],
vec![("kigi-4".to_owned(), 60), ("kigi-3".to_owned(), 40)],
200_000,
50,
3,
@@ -3081,11 +3081,11 @@ mod tests {
let mut o = make_base_orchestration();
o.skeptic_model_assignment = vec![
crate::util::config::GoalRoleModel {
model: "grok-4".to_string(),
model: "kigi-4".to_string(),
agent_type: "general-purpose".to_string(),
},
crate::util::config::GoalRoleModel {
model: "grok-4.5".to_string(),
model: "kigi-4.5".to_string(),
agent_type: "cursor".to_string(),
},
];
@@ -3132,7 +3132,7 @@ mod tests {
activate_tracker(&mut t);
t.snapshot_mut().unwrap().skeptic_model_assignment =
vec![crate::util::config::GoalRoleModel {
model: "grok-4".to_string(),
model: "kigi-4".to_string(),
agent_type: "general-purpose".to_string(),
}];
let applied = match ending {
@@ -3170,22 +3170,22 @@ mod tests {
}
/// The scratch path helpers derive the pinned layout from
/// `temp_dir()` + `verifier_id`: a `grok-goal-<vid>` root with an
/// `temp_dir()` + `verifier_id`: a `kigi-goal-<vid>` root with an
/// `implementer/` subdir and per-index `skeptic-<idx>/` subdirs.
#[test]
fn scratch_path_helpers_derive_pinned_layout() {
let root = goal_scratch_root("vid123");
assert_eq!(root, std::env::temp_dir().join("grok-goal-vid123"));
assert_eq!(root, std::env::temp_dir().join("kigi-goal-vid123"));
assert_eq!(
implementer_scratch_dir("vid123"),
std::env::temp_dir()
.join("grok-goal-vid123")
.join("kigi-goal-vid123")
.join("implementer"),
);
assert_eq!(
skeptic_scratch_dir("vid123", 2),
std::env::temp_dir()
.join("grok-goal-vid123")
.join("kigi-goal-vid123")
.join("skeptic-2"),
);
}
@@ -12,7 +12,7 @@ use std::collections::{HashMap, HashSet};
use tokio::sync::{mpsc, oneshot};
/// Coarse lifecycle state of a session as known to the leader/agent.
///
/// A grok session has no
/// A kigi session has no
/// terminal status field on its own — it is a resumable log on disk — so
/// "liveness" is *residency + turn-state*, not a pid. The agent's join-handle
/// supervisor tracks this per session so a panicked actor can be reaped
@@ -103,7 +103,7 @@ pub struct SessionHandle {
/// client behaviors like yolo broadcasts.
pub origin_client: Option<crate::http::OriginClientInfo>,
/// Whether the client that created this session advertised
/// `x.ai/codeNavigation.enabled`. Stored per-session so that in leader
/// `kigi/codeNavigation.enabled`. Stored per-session so that in leader
/// mode a later `initialize()` from a different client cannot retroactively
/// change code-nav eligibility for already-running sessions.
pub code_nav_enabled: bool,
@@ -112,13 +112,13 @@ pub struct SessionHandle {
/// env gate). Stored per-session so subagents inherit it at spawn.
pub ask_user_question_enabled: bool,
/// Plan mode tracker — shared with the session actor via Arc.
/// Exposed so the `x.ai/toggle_plan_mode` handler can toggle plan mode
/// Exposed so the `kigi/toggle_plan_mode` handler can toggle plan mode
/// without going through the session command channel.
pub plan_mode: std::sync::Arc<parking_lot::Mutex<crate::session::plan_mode::PlanModeTracker>>,
/// Debug flag: when set to `true`, the next turn unconditionally triggers
/// auto-compaction regardless of context window usage. Consumed (reset to
/// `false`) atomically on use via `compare_exchange`.
/// Set via `x.ai/debug/arm_auto_compact`.
/// Set via `kigi/debug/arm_auto_compact`.
pub force_compact: std::sync::Arc<std::sync::atomic::AtomicBool>,
pub permission_handle: kigi_workspace::permission::PermissionHandle,
/// The parent SessionActor's live `Auth401AttributionCallback`
@@ -147,7 +147,7 @@ pub struct SessionHandle {
/// Scheduler handle for this session. Subagents inherit the parent's
/// handle so scheduled tasks survive the subagent's exit.
pub scheduler_handle:
Option<kigi_tools::implementations::grok_build::scheduler::types::SchedulerHandle>,
Option<kigi_tools::implementations::kigi::scheduler::types::SchedulerHandle>,
}
impl SessionHandle {
/// Last assistant `model_id` / `model_fingerprint` in conversation (global, not turn-scoped).
@@ -7,7 +7,7 @@
//!
//! The three **common** active-agent sections (background tasks, TODO list,
//! running subagents) are formatted by
//! [`kigi_compaction::reminder`] so grok-chat and grok-build stay in lockstep.
//! [`kigi_compaction::reminder`] so kigi-chat and kigi stay in lockstep.
//! Harness-only sections (edited files, AGENTS.md, skills, MCP, memory) stay here.
use std::path::PathBuf;
@@ -1,9 +1,9 @@
//! grok-build's L5 wiring onto the shared full-replace engine
//! kigi's L5 wiring onto the shared full-replace engine
//! (`kigi_compaction::code_compaction`).
//!
//! The shared engine drives the sample → retry → degenerate/failure
//! classification loop via [`sample_full_replace_summary`](kigi_compaction::sample_full_replace_summary);
//! this module adapts grok-build's transport and telemetry to its two seams:
//! this module adapts kigi's transport and telemetry to its two seams:
//!
//! - [`ShellCompactionSampler`] wraps
//! [`generate_session_compact`](crate::session::helpers::session_compact::generate_session_compact)
@@ -42,7 +42,7 @@ use crate::session::helpers::session_compact::{
};
/// Wraps `generate_session_compact` as the shared engine's
/// [`CompactionSampler`] for grok-build's full-replace pass.
/// [`CompactionSampler`] for kigi's full-replace pass.
///
/// Holds the per-call request context the seam does not carry (tools, client,
/// session, config) and stashes the last successful [`CompactOutput`] so the
@@ -51,8 +51,8 @@ use crate::session::helpers::session_compact::{
///
/// The summarization prompt is selected here by `use_short_prompt` (the
/// short-prompt harness uses the short self-summarization prompt; everyone
/// else the structured grok-build prompt), so the shared `CompactionPrompt`
/// the engine passes is ignored — the engine builds the grok-build prompt,
/// else the structured kigi prompt), so the shared `CompactionPrompt`
/// the engine passes is ignored — the engine builds the kigi prompt,
/// which equals what `build_compaction_chat_history(.., false)` appends, and
/// the short-prompt harness needs its own variant the engine can't produce.
pub(crate) struct ShellCompactionSampler {
@@ -118,7 +118,7 @@ impl CompactionSampler for ShellCompactionSampler {
_timeout: Duration,
) -> Result<LlmCompactionOutput, CompactionSampleError> {
// Append the harness-selected summarization prompt as the final user
// message (compat short vs structured grok-build), ignoring the shared
// message (compat short vs structured kigi), ignoring the shared
// engine's `_prompt` (see the struct doc).
let chat_history = build_compaction_chat_history(
turns.to_vec(),
@@ -151,7 +151,7 @@ impl CompactionSampler for ShellCompactionSampler {
}
}
/// Map grok-build's [`CompactFailure`] onto the shared engine's
/// Map kigi's [`CompactFailure`] onto the shared engine's
/// [`CompactionSampleError`] so the shared retry loop classifies it the same
/// way the in-shell loop did:
///
@@ -207,7 +207,7 @@ struct ObserverState {
last_error_msg: Option<String>,
}
/// [`FullReplaceObserver`] that reproduces grok-build's per-attempt telemetry:
/// [`FullReplaceObserver`] that reproduces kigi's per-attempt telemetry:
/// `CompactionAttempt` rows, rejection counters, the `CompactionRetryDegraded`
/// event, and the warn/error tracing — without the shared engine depending on
/// a telemetry backend.
@@ -328,7 +328,7 @@ mod tests {
.as_deref(),
Some(default_suggest_model())
);
// OAuth catalogs exclude grok-build-0.1 → skip the request entirely,
// OAuth catalogs exclude kigi-0.1 → skip the request entirely,
// never a doomed call (and never the session model).
assert_eq!(
effective_suggest_model(&Pin::Unpinned, None, |_| false),
@@ -61,7 +61,10 @@ pub fn find_latest_compaction_checkpoint(
continue;
};
if env.method != Some("_x.ai/session/update") {
if !env
.method
.is_some_and(crate::session::storage::is_ext_session_update_method)
{
continue;
}
@@ -20,7 +20,7 @@ use reqwest::StatusCode;
/// `<summary_request>` only -- the surrounding `<user_query>` is implicit
/// because we push this as a `ConversationItem::user`.
///
/// All other agents (grok-build, etc.) continue to use the detailed
/// All other agents (kigi, etc.) continue to use the detailed
/// structured prompt built inline in `generate_session_compact`.
pub(crate) const SELF_SUMMARIZATION_PROMPT: &str = r#"<summary_request>
Please summarize the conversation so far. This summary (everything after your
@@ -376,10 +376,10 @@ pub(crate) async fn generate_session_compact(
.with_tool_choice(ToolChoice::none());
}
let sid = session_id.to_string();
message.x_grok_conv_id = Some(sid.clone());
message.x_grok_req_id = Some(format!("xai-compact-{}", uuid::Uuid::new_v4()));
message.x_grok_session_id = Some(sid);
message.x_grok_agent_id = Some(crate::util::agent_id::agent_id());
message.x_kigi_conv_id = Some(sid.clone());
message.x_kigi_req_id = Some(format!("xai-compact-{}", uuid::Uuid::new_v4()));
message.x_kigi_session_id = Some(sid);
message.x_kigi_agent_id = Some(crate::util::agent_id::agent_id());
tracing::info!(
compact_model = % sampling_config.model, num_messages = num_messages,
"Sending compact request (streaming)"
@@ -471,10 +471,10 @@ pub(crate) async fn generate_session_compact(
hosted_tools,
model: Some(sampling_config.model.to_owned()),
temperature: Some(1.0),
x_grok_conv_id: Some(session_id.to_string()),
x_grok_req_id: Some(format!("xai-compact-{}", uuid::Uuid::new_v4())),
x_grok_session_id: Some(session_id.to_string()),
x_grok_agent_id: Some(crate::util::agent_id::agent_id()),
x_kigi_conv_id: Some(session_id.to_string()),
x_kigi_req_id: Some(format!("xai-compact-{}", uuid::Uuid::new_v4())),
x_kigi_session_id: Some(session_id.to_string()),
x_kigi_agent_id: Some(crate::util::agent_id::agent_id()),
..Default::default()
};
let stream_result = client.conversation_stream_responses(request).await;
@@ -593,10 +593,10 @@ pub(crate) async fn generate_session_compact(
hosted_tools,
model: Some(sampling_config.model.to_owned()),
temperature: Some(1.0),
x_grok_conv_id: Some(session_id.to_string()),
x_grok_req_id: Some(format!("xai-compact-{}", uuid::Uuid::new_v4())),
x_grok_session_id: Some(session_id.to_string()),
x_grok_agent_id: Some(crate::util::agent_id::agent_id()),
x_kigi_conv_id: Some(session_id.to_string()),
x_kigi_req_id: Some(format!("xai-compact-{}", uuid::Uuid::new_v4())),
x_kigi_session_id: Some(session_id.to_string()),
x_kigi_agent_id: Some(crate::util::agent_id::agent_id()),
..Default::default()
};
let stream_result = client.conversation_stream_messages(request).await;
@@ -1127,13 +1127,13 @@ mod compacted_history_shape_tests {
);
assert_eq!(compacted.len(), 5);
}
/// Regression guard: grok-build must DROP the working
/// tail post-compaction. A prior change routed grok-build to keep `recent_messages`,
/// Regression guard: kigi must DROP the working
/// tail post-compaction. A prior change routed kigi to keep `recent_messages`,
/// which survive only as `Tool call omitted...` stubs (dead tokens). Mirrors
/// `summary_before_recent_compaction_with_no_user_query_yields_three_messages` for grok-build
/// `summary_before_recent_compaction_with_no_user_query_yields_three_messages` for kigi
/// (`summary_before_recent = false`).
#[tokio::test]
async fn grok_build_compaction_drops_working_tail_regression_206460() {
async fn kigi_compaction_drops_working_tail_regression_206460() {
let conversation = vec![
ConversationItem::system("You are a helpful assistant."),
ConversationItem::user(
@@ -1163,7 +1163,7 @@ mod compacted_history_shape_tests {
let dropped = full.for_compaction();
assert!(
dropped.recent_messages.is_empty(),
"grok-build must drop recent_messages post-compaction",
"kigi must drop recent_messages post-compaction",
);
let compacted = build_compacted_history(
"You are a helpful assistant.",
@@ -1177,7 +1177,7 @@ mod compacted_history_shape_tests {
.iter()
.any(|i| matches!(i, ConversationItem::ToolResult(_))
|| i.text_content() == "Tool call omitted..."),
"no tail (ToolResult or stub) may leak into the grok-build compacted history",
"no tail (ToolResult or stub) may leak into the kigi compacted history",
);
}
/// Verify that the auto-continue prompt (sent after compaction) is also
@@ -62,7 +62,7 @@ pub(crate) fn recap_instruction(tag: &str) -> String {
/// 1. Optionally strips reasoning/thinking blocks (`strip_reasoning`). This is
/// only needed on the Anthropic Messages backend, which rejects thinking
/// blocks sent without a top-level `thinking` config. Every other backend
/// (grok/SGLang via ChatCompletions/Responses) keeps reasoning VERBATIM so
/// (kigi/SGLang via ChatCompletions/Responses) keeps reasoning VERBATIM so
/// the conversation prefix is byte-identical to the last turn and the
/// provider's prefix KV cache stays warm — which is the whole reason we
/// append the instruction after the prefix. Mirrors compaction's
@@ -89,7 +89,7 @@ pub(crate) fn build_recap_items(
}
/// Cap on the effective context window for recap budgeting: the verified
/// `max_prompt_length` for current `grok-build` / `grok-4.5` product backends
/// `max_prompt_length` for current `kigi` / `kigi-4.5` product backends
/// (`500000`). Applied via `min(window, CAP)`, so a smaller real window still
/// wins (e.g. a 256k legacy model or a debug override).
const RECAP_CONTEXT_WINDOW_CAP: u64 = 500_000;
@@ -110,10 +110,10 @@ const RECAP_BUDGET_HEADROOM_TOKENS: u64 = 4_000;
/// `ic_400_prompt_too_long` on long sessions. Not an absolute guarantee — a
/// degenerate tiny window, an oversized retained `System` prefix, or estimator
/// optimism can still exceed the real limit (the 85% + headroom + 500k cap make
/// that unlikely for normal grok-build sessions).
/// that unlikely for normal kigi sessions).
///
/// * Fast path — if the whole snapshot already fits, returns
/// `build_recap_items(...)` verbatim (keeps the grok prefix KV cache warm;
/// `build_recap_items(...)` verbatim (keeps the kigi prefix KV cache warm;
/// honors the caller's `strip_reasoning`).
/// * Over budget — strip reasoning (the prefix cache is lost once we trim),
/// normalize the trailing boundary ([`pop_trailing_tool_run`]),
@@ -136,7 +136,7 @@ pub(crate) fn budget_recap_items(
let snapshot_budget = prompt_budget.saturating_sub(estimate_item_tokens(&instruction));
// Un-stripped estimate is a safe upper bound (stripping only shrinks); the
// verbatim path keeps the grok prefix cache warm.
// verbatim path keeps the kigi prefix cache warm.
let pre_tokens = estimate_conversation_tokens(&conversation);
if pre_tokens <= snapshot_budget {
return build_recap_items(conversation, tag, strip_reasoning);
@@ -690,13 +690,13 @@ mod tests {
}
#[test]
fn budget_over_budget_strips_reasoning_even_on_grok() {
fn budget_over_budget_strips_reasoning_even_on_kigi() {
let conv = vec![
mk_reasoning("r1"),
ConversationItem::assistant("did stuff"),
ConversationItem::user("z".repeat(40_000)),
];
// grok backend => strip_reasoning=false, but the over-budget branch must
// kigi backend => strip_reasoning=false, but the over-budget branch must
// strip reasoning anyway (the prefix cache is already lost once trimmed).
let out = budget_recap_items(conv, "system-reminder", false, 8_000);
assert!(
@@ -707,19 +707,19 @@ mod tests {
}
#[test]
fn budget_fast_path_keeps_reasoning_on_grok() {
fn budget_fast_path_keeps_reasoning_on_kigi() {
let conv = vec![
mk_reasoning("r1"),
ConversationItem::assistant("did stuff"),
ConversationItem::user("small"),
];
// Fits under a large window on grok (strip_reasoning=false) => verbatim,
// Fits under a large window on kigi (strip_reasoning=false) => verbatim,
// reasoning kept so the prefix KV cache stays warm.
let out = budget_recap_items(conv, "system-reminder", false, 256_000);
assert!(
out.iter()
.any(|i| matches!(i, ConversationItem::Reasoning(_))),
"fits path on grok must keep reasoning verbatim"
"fits path on kigi must keep reasoning verbatim"
);
}
@@ -2,7 +2,7 @@
//!
//! That harness uses a separate vision endpoint to describe images
//! rather than passing them inline. When a user message contains image
//! content blocks, the session calls a vision-capable Grok model
//! content blocks, the session calls a vision-capable Kigi model
//! (defaults to the agent's current model unless explicitly overridden)
//! to produce text descriptions that are injected into the turn. Per-image
//! requests are deduplicated via [`ImageDescribeCache`] (same bytes +
@@ -912,11 +912,11 @@ mod tests {
#[test]
fn re_encode_fallback_notice_picks_tag_per_harness() {
let notes = vec!["Image 1 could not be re-encoded under the cap.".to_string()];
let grok = render_re_encode_fallback_notice(&notes, false);
assert!(grok.contains("<system-reminder>"));
assert!(grok.contains("</system-reminder>"));
assert!(!grok.contains("<system_reminder>"));
assert!(grok.contains("<image_re_encode_fallback>"));
let kigi = render_re_encode_fallback_notice(&notes, false);
assert!(kigi.contains("<system-reminder>"));
assert!(kigi.contains("</system-reminder>"));
assert!(!kigi.contains("<system_reminder>"));
assert!(kigi.contains("<image_re_encode_fallback>"));
}
#[test]
fn display_format() {
@@ -1316,12 +1316,12 @@ mod tests {
#[test]
fn image_dropped_notice_picks_tag_per_harness() {
let notes = vec!["Image 5 was dropped before send: corrupt".to_string()];
let grok = render_image_dropped_notice(&notes, false);
assert!(grok.contains("<system-reminder>"));
assert!(grok.contains("</system-reminder>"));
assert!(!grok.contains("<system_reminder>"));
assert!(grok.contains("<image_dropped_notice>"));
assert!(grok.contains("Image 5"));
let kigi = render_image_dropped_notice(&notes, false);
assert!(kigi.contains("<system-reminder>"));
assert!(kigi.contains("</system-reminder>"));
assert!(!kigi.contains("<system_reminder>"));
assert!(kigi.contains("<image_dropped_notice>"));
assert!(kigi.contains("Image 5"));
assert_eq!(render_image_dropped_notice(&[], false), "");
}
/// Large flat-color images compress better as PNG than JPEG; the
@@ -506,7 +506,7 @@ mod tests {
let tagged = apply_mcp_server_policy(
vec![
acp::McpServer::Http(
acp::McpServerHttp::new("grok_com_slack", "https://mcp.slack.com/sse")
acp::McpServerHttp::new("kigi_com_slack", "https://mcp.slack.com/sse")
.headers(vec![]),
),
// Substring-only match must not be denied.
@@ -521,7 +521,7 @@ mod tests {
let slack = tagged
.iter()
.find(|s| mcp_server_name(&s.server) == "grok_com_slack")
.find(|s| mcp_server_name(&s.server) == "kigi_com_slack")
.expect("managed server present in policy output");
assert!(
matches!(
@@ -762,7 +762,7 @@ enabled = false
root: plugin_root.clone(),
canonical_root: plugin_root.clone(),
scope: PluginScope::User,
origin: kigi_agent::plugins::PluginOrigin::UserGrok,
origin: kigi_agent::plugins::PluginOrigin::UserKigi,
trusted: true,
skill_dirs: vec![],
command_dirs: vec![],
@@ -830,7 +830,7 @@ enabled = false
root: plugin_root.clone(),
canonical_root: plugin_root.clone(),
scope: PluginScope::User,
origin: kigi_agent::plugins::PluginOrigin::UserGrok,
origin: kigi_agent::plugins::PluginOrigin::UserKigi,
trusted: true,
skill_dirs: vec![],
command_dirs: vec![],
@@ -904,7 +904,7 @@ enabled = false
root: plugin_root.clone(),
canonical_root: plugin_root.clone(),
scope: PluginScope::User,
origin: kigi_agent::plugins::PluginOrigin::UserGrok,
origin: kigi_agent::plugins::PluginOrigin::UserKigi,
trusted: true,
skill_dirs: vec![],
command_dirs: vec![],
@@ -3,7 +3,7 @@
//! Receives [`kigi_mcp::servers::McpClientEvent`]s emitted by:
//! - per-client transport-liveness watchers
//! ([`kigi_mcp::liveness`]),
//! - the [`kigi_mcp::servers::GrokClientHandler`] (server-pushed
//! - the [`kigi_mcp::servers::KigiClientHandler`] (server-pushed
//! `tools/list_changed` and `resources/list_changed`),
//! - the `ensure_initialized` success/failure path,
//! - the session/managed-config diff path.
@@ -16,7 +16,7 @@
//!
//! Each surviving entry is emitted as an ACP
//! [`agent_client_protocol::ExtNotification`] with method
//! `x.ai/mcp/server_status` and the payload schema defined by
//! `kigi/mcp/server_status` and the payload schema defined by
//! [`McpServerStatusPayload`].
//!
//! ## Doc-comment ↔ implementation contract
@@ -55,7 +55,7 @@ use crate::extensions::mcp::McpServerSource;
pub const COALESCE_WINDOW: Duration = Duration::from_millis(50);
/// Method name for the ACP push.
pub const SERVER_STATUS_METHOD: &str = "x.ai/mcp/server_status";
pub const SERVER_STATUS_METHOD: &str = "kigi/mcp/server_status";
/// JSON payload pushed over ACP. Fields written in camelCase per ACP
/// convention.
@@ -438,7 +438,7 @@ pub fn build_payload(
/// Per-flush side effects:
/// - update `shutting_down` for `TransportClosed` /
/// `ConfigRemoved` keys,
/// - emit one ACP `x.ai/mcp/server_status` push per surviving
/// - emit one ACP `kigi/mcp/server_status` push per surviving
/// buffer entry, via the provided gateway.
///
/// `gateway` is a [`kigi_acp_lib::AcpAgentGatewaySender`] (forwarded
@@ -638,7 +638,7 @@ pub async fn drop_dead_clients(
/// gated on client identity (see [`collect_close_candidates`]).
/// Stale `TransportClosed` keys are stripped from the window so they
/// push no status, emit no disconnect span, and schedule no restart.
/// 3. `flush_window` — emit ACP `x.ai/mcp/server_status` per
/// 3. `flush_window` — emit ACP `kigi/mcp/server_status` per
/// surviving entry.
/// 4. `maybe_schedule_restart` — for every
/// `TransportClosed` / `HandshakeFailed` key, the
@@ -213,7 +213,7 @@ pub trait RestartActions {
/// bubbles up.
async fn respawn_stdio(&self, server: &str) -> Result<(), String>;
/// Push an already-built `x.ai/mcp/server_status` payload to the
/// Push an already-built `kigi/mcp/server_status` payload to the
/// pager. The production impl wraps the dispatcher's gateway
/// sender via [`forward_status`].
fn push_status(&self, payload: &McpServerStatusPayload);
@@ -617,7 +617,7 @@ fn push(
}
/// Serialize a [`McpServerStatusPayload`] and send it to the gateway as an
/// ACP `x.ai/mcp/server_status` notification. Failures are logged and
/// ACP `kigi/mcp/server_status` notification. Failures are logged and
/// dropped — restart-task pushes must not block the session actor.
///
/// Public so production impls and tests can wrap a gateway sender
@@ -1506,7 +1506,7 @@ mod tests {
fn forward_status_uses_dispatcher_method() {
assert_eq!(
crate::session::mcp_dispatcher::SERVER_STATUS_METHOD,
"x.ai/mcp/server_status",
"kigi/mcp/server_status",
"wire method name pinned",
);
// The `forward_status` function uses
@@ -1,6 +1,6 @@
//! Merged session listing — combines local and remote session data.
//!
//! Used by both the ACP `x.ai/session/list` handler and the `grok sessions`
//! Used by both the ACP `kigi/session/list` handler and the `kigi sessions`
//! CLI command. Deduplicates by session ID (remote wins), filters local
//! results by query, and sorts by the same key the picker UI displays
//! (`last_active_at` falling back to `updated_at`) descending.
@@ -36,15 +36,15 @@ pub type PendingInteractions = Arc<Mutex<HashMap<String, PendingKind>>>;
pub enum PendingKind {
/// `request_permission` for a tool action.
Permission,
/// `x.ai/ask_user_question`.
/// `kigi/ask_user_question`.
Question,
/// `x.ai/exit_plan_mode` plan approval.
/// `kigi/exit_plan_mode` plan approval.
PlanApproval,
}
/// Whether a blocking plan-approval reverse-request is parked in `pending`.
///
/// The resume re-park issues `x.ai/exit_plan_mode` from a detached task
/// The resume re-park issues `kigi/exit_plan_mode` from a detached task
/// with no running turn, making it the one parked interaction that also carries a
/// persisted gate (`awaiting_plan_approval`). `session_has_live_work` consults
/// this to keep such a session resident until the decision is answered or a real
@@ -70,7 +70,7 @@ fn broadcast(gateway: &GatewaySender, session_id: &acp::SessionId, update: XaiSe
};
if let Ok(params) = serde_json::value::to_raw_value(&notification) {
gateway.forward_fire_and_forget(acp::ExtNotification::new(
"x.ai/session_notification",
"kigi/session_notification",
params.into(),
));
}
@@ -136,8 +136,8 @@ mod feedback_tests {
Some("could be better".into())
},
feedback_categories: vec![],
model_id: Some("grok-3-fast".into()),
resolved_model_id: Some("grok-4.5".into()),
model_id: Some("kigi-3-fast".into()),
resolved_model_id: Some("kigi-4.5".into()),
model_fingerprint: None,
context_type: None,
request_id: None,
@@ -299,7 +299,7 @@ pub enum PersistenceMsg {
ReplaceChatHistory(Vec<ConversationItem>),
CurrentModel {
model_id: acp::ModelId,
/// The active agent definition name (e.g. `"grok-build"`).
/// The active agent definition name (e.g. `"kigi"`).
/// Persisted in `summary.agent_name` so session resume doesn't depend
/// on the mutable model catalog.
agent_name: Option<String>,
@@ -411,7 +411,7 @@ fn session_exists_for_cwd_in_root(session_id: &str, cwd: &str, sessions_root: &P
///
/// When a remote session is restored, a new local child is created with
/// `summary.parent_session_id == remote_session_id`. On a second
/// `grok -r <remote_id>` in the same cwd, this function returns the already-restored
/// `kigi -r <remote_id>` in the same cwd, this function returns the already-restored
/// child so no duplicate restore is performed.
///
/// If multiple children match (e.g., from pre-fix duplicate restores), the
@@ -521,7 +521,7 @@ fn find_local_child_for_remote_in_root(
}
// Collect all matching children. Multiple can exist when a user ran
// `grok -r <remote_id>` before this fix was deployed.
// `kigi -r <remote_id>` before this fix was deployed.
// Tuple: (updated_at, dir_mtime_nanos, session_id) — all sorted descending.
let mut candidates: Vec<(String, u128, String)> = Vec::new();
@@ -632,7 +632,7 @@ pub fn session_exists_by_id(session_id: &str) -> bool {
}
/// Inner implementation of `session_exists_by_id` that accepts a custom root.
/// Separated so tests can use a tempdir without touching the real grok home.
/// Separated so tests can use a tempdir without touching the real kigi home.
fn session_exists_in_root(session_id: &str, sessions_root: &Path) -> bool {
if !sessions_root.exists() {
return false;
@@ -1908,7 +1908,7 @@ pub(crate) fn io_error_to_acp(e: &io::Error) -> acp::Error {
}
/// Best-effort worktree liveness touch: stamp `last_accessed_at` on the
/// worktree containing this session's cwd so `grok worktree gc` expires by
/// worktree containing this session's cwd so `kigi worktree gc` expires by
/// last use, not creation time. Lives here — not in a `StorageAdapter` —
/// so every session create/load path shares it regardless of backend.
fn spawn_worktree_touch(info: &Info) -> tokio::task::JoinHandle<()> {
@@ -2489,13 +2489,7 @@ mod agent_name_persistence_tests {
#[test]
fn summary_round_trips_various_agent_names() {
for name in [
"cursor",
"grok-build",
"grok-build-plan",
"codex",
"browser-use",
] {
for name in ["cursor", "kigi", "kigi-plan", "codex", "browser-use"] {
let mut summary = Summary::new(
&Info {
id: acp::SessionId::new("test"),
@@ -2645,7 +2639,7 @@ mod session_exists_tests {
#[test]
fn returns_false_when_root_does_not_exist() {
let root = std::path::PathBuf::from("/nonexistent/grok/sessions");
let root = std::path::PathBuf::from("/nonexistent/kigi/sessions");
assert!(!session_exists_in_root("any-id", &root));
}
@@ -2722,7 +2716,7 @@ mod find_summary_by_session_id_tests {
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z",
"num_messages": 0,
"current_model_id": "grok-3",
"current_model_id": "kigi-3",
"head_commit": head_commit,
"head_branch": head_branch
})
@@ -2802,7 +2796,7 @@ mod resumed_sandbox_profile_tests {
"created_at": "2026-01-01T00:00:00Z",
"updated_at": updated_at,
"num_messages": 0,
"current_model_id": "grok-3",
"current_model_id": "kigi-3",
});
if let Some(la) = last_active_at {
summary["last_active_at"] = serde_json::Value::String(la.to_string());
@@ -2873,7 +2867,7 @@ mod resumed_sandbox_profile_tests {
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z",
"num_messages": 0,
"current_model_id": "grok-3",
"current_model_id": "kigi-3",
"parent_session_id": "remote-xyz",
"sandbox_profile": "workspace",
});
@@ -3206,7 +3200,7 @@ mod find_local_child_tests {
assert!(found.is_none());
}
/// Regression: a second `grok -r <remote_id>` must return the existing child
/// Regression: a second `kigi -r <remote_id>` must return the existing child
/// without creating a new restore, not return `None`.
#[test]
fn repeated_resume_returns_existing_child() {
@@ -3305,7 +3299,7 @@ mod resolve_local_session_tests {
// resolve_local_session delegates to the same _in_root helpers tested above,
// so we test the composition logic via the public function indirectly by
// setting up the on-disk structures under a fake grok home.
// setting up the on-disk structures under a fake kigi home.
// For unit isolation, we test the equivalent logic via the inner helpers.
fn setup_session(root: &std::path::Path, cwd: &str, session_id: &str) {
@@ -644,7 +644,7 @@ mod tests {
use kigi_tools::types::template_renderer::TemplateRenderer;
use kigi_tools::types::tool::ToolKind;
use std::collections::HashMap;
/// Build a test TemplateRenderer with standard Grok Build tool mappings.
/// Build a test TemplateRenderer with standard Kigi tool mappings.
fn test_renderer() -> TemplateRenderer {
let tools: HashMap<ToolKind, String> = [
(ToolKind::Edit, "search_replace".to_owned()),
@@ -5,13 +5,13 @@ use serde::Deserialize;
use std::path::PathBuf;
/// Parsed prompt with context and query kept separate.
///
/// Some templates put `<user_query>` last (context first); Grok puts it first.
/// Some templates put `<user_query>` last (context first); Kigi puts it first.
/// Keeping them separate lets the caller truncate context without
/// searching for the query boundary in a flat string.
#[derive(Debug, Clone)]
pub struct ParsedPrompt {
/// Context blocks: `<attached_files>` payloads and resource-link sections.
/// Grok mode may include editor open/focus metadata; the compat mode does not.
/// Kigi mode may include editor open/focus metadata; the compat mode does not.
/// Empty string when there is no context.
pub context: String,
/// The user's query, already wrapped in `<user_query>` tags
@@ -45,7 +45,7 @@ impl ParsedPrompt {
/// Assemble context, query, and skill information into the final message string.
///
/// Layout:
/// - **Grok mode:** `<user_query>` + `<skill_information>` + context
/// - **Kigi mode:** `<user_query>` + `<skill_information>` + context
/// - **Query-last mode:** context + `<user_query>` + `<skill_information>`
///
/// The `<skill_information>` block always follows `<user_query>` immediately
@@ -75,7 +75,7 @@ impl ParsedPrompt {
/// - `<attached_files>` (bare), resource links, then `<user_query>` last
/// - File references use `<code_selection>` tags
///
/// When `is_cursor` is false, produces original Grok-format output:
/// When `is_cursor` is false, produces original Kigi-format output:
/// - `<user_query>` first, then `<system-reminder>` wrapped `<attached_files>` and resource links
/// - File references use `<file_contents>` tags
pub async fn parse_prompt(
@@ -203,7 +203,7 @@ Below are some potentially helpful/relevant pieces of information for figuring o
if !context.is_empty() {
context.push_str("\n\n");
}
context.push_str(&render_resource_links_grok(resource_links));
context.push_str(&render_resource_links_kigi(resource_links));
}
(context, query)
}
@@ -285,9 +285,9 @@ fn render_regular_links(links: &[&acp::ResourceLink]) -> String {
}
s.trim_end_matches('\n').to_string()
}
/// Grok-format resource links: `<focused_files>` / `<open_files>` with
/// Kigi-format resource links: `<focused_files>` / `<open_files>` with
/// metadata inside a `<system-reminder>` wrapper.
fn render_resource_links_grok(resource_links: &[acp::ResourceLink]) -> String {
fn render_resource_links_kigi(resource_links: &[acp::ResourceLink]) -> String {
let mut regular_links = Vec::new();
let mut focused_files = Vec::new();
let mut open_files = Vec::new();
@@ -363,8 +363,8 @@ mod tests {
format!("{query}\n\n{context}")
}
}
/// Shorthand: render + assemble for grok mode.
fn render_grok(
/// Shorthand: render + assemble for kigi mode.
fn render_kigi(
message: &str,
embedded: Vec<String>,
file_refs: Vec<String>,
@@ -475,13 +475,13 @@ mod tests {
assert!(parse_editor_meta(&link).is_none());
}
#[test]
fn test_grok_render_plain_message() {
let result = render_grok("hello", vec![], vec![], &[], false);
fn test_kigi_render_plain_message() {
let result = render_kigi("hello", vec![], vec![], &[], false);
assert_eq!(result, "<user_query>\nhello\n</user_query>");
}
#[test]
fn test_grok_render_with_attachments_uses_system_reminder_wrapper() {
let result = render_grok(
fn test_kigi_render_with_attachments_uses_system_reminder_wrapper() {
let result = render_kigi(
"check this",
vec!["embedded content".into()],
vec![],
@@ -496,25 +496,25 @@ mod tests {
assert!(result.contains("embedded content"));
assert!(
result.starts_with("<user_query>"),
"Grok should start with <user_query>, got: {result}"
"Kigi should start with <user_query>, got: {result}"
);
}
#[test]
fn test_grok_render_user_query_first() {
fn test_kigi_render_user_query_first() {
let link = acp::ResourceLink::new("doc.md", "file:///doc.md")
.title(Some("My Doc".into()))
.size(Some(1024));
let result = render_grok("hello", vec![], vec![], &[link], false);
let result = render_kigi("hello", vec![], vec![], &[link], false);
let uq_pos = result.find("<user_query>").unwrap();
let rr_pos = result.find("Referenced resources:").unwrap();
assert!(
uq_pos < rr_pos,
"Grok: <user_query> ({uq_pos}) should come before resource links ({rr_pos})\ngot: {result}"
"Kigi: <user_query> ({uq_pos}) should come before resource links ({rr_pos})\ngot: {result}"
);
assert!(result.contains("<system-reminder>"));
}
#[test]
fn test_grok_render_resource_links_use_focused_files_format() {
fn test_kigi_render_resource_links_use_focused_files_format() {
let links = vec![
acp::ResourceLink::new("main.rs", "file:///project/src/main.rs").meta(
serde_json::json!({ "source" : "editor", "fileState" : "focused",
@@ -528,7 +528,7 @@ mod tests {
.cloned(),
),
];
let result = render_grok("hello", vec![], vec![], &links, false);
let result = render_kigi("hello", vec![], vec![], &links, false);
assert!(result.contains("<system-reminder>"), "got: {result}");
assert!(result.contains("<focused_files>"), "got: {result}");
assert!(result.contains("<open_files>"), "got: {result}");
@@ -7,7 +7,7 @@
pub use kigi_prompt_queue::{QueueChanged, QueueEntryMeta, QueueEntryWire};
// Outbound method for broadcast_queue_changed. An ACP routing concern, not a queue concern.
pub const QUEUE_CHANGED_METHOD: &str = "x.ai/queue/changed";
pub const QUEUE_CHANGED_METHOD: &str = "kigi/queue/changed";
#[cfg(test)]
mod tests {
@@ -20,7 +20,7 @@ mod tests {
entries: vec![QueueEntryWire {
id: "p1".to_string(),
version: 0,
owner: Some("grok-tui".to_string()),
owner: Some("kigi-tui".to_string()),
last_editor: None,
kind: "prompt".to_string(),
text: "hello".to_string(),
@@ -56,14 +56,14 @@ mod tests {
let entry = QueueEntryWire {
id: "p1".to_string(),
version: 3,
owner: Some("grok-tui".to_string()),
last_editor: Some("grok-vscode".to_string()),
owner: Some("kigi-tui".to_string()),
last_editor: Some("kigi-vscode".to_string()),
kind: "prompt".to_string(),
text: "hello".to_string(),
position: 0,
};
let json = serde_json::to_value(&entry).unwrap();
assert_eq!(json["lastEditor"], "grok-vscode");
assert_eq!(json["lastEditor"], "kigi-vscode");
let round: QueueEntryWire = serde_json::from_value(json).unwrap();
assert_eq!(round, entry);
}
@@ -280,7 +280,7 @@ pub struct SessionSignals {
/// Number of edit-and-retry actions (user rewinds and submits a different prompt)
pub edit_and_retry_count: u32,
// === Bash tool patterns (grok_build) ===
// === Bash tool patterns (kigi) ===
/// Number of times the bash tool was used for a bare `echo "<msg>"` (or close
/// variant). Tracked for usage statistics.
#[serde(default)]
@@ -1928,20 +1928,20 @@ mod tests {
let actor_handle = tokio::spawn(actor.run());
// Set primary model
handle.set_primary_model("grok-3");
handle.set_primary_model("kigi-3");
let snapshot = handle.snapshot().await.unwrap();
assert_eq!(snapshot.primary_model_id, Some("grok-3".to_string()));
assert_eq!(snapshot.models_used, vec!["grok-3".to_string()]);
assert_eq!(snapshot.primary_model_id, Some("kigi-3".to_string()));
assert_eq!(snapshot.models_used, vec!["kigi-3".to_string()]);
// Record additional model usage
handle.record_model_usage("grok-4");
handle.record_model_usage("grok-3"); // Duplicate
handle.record_model_usage("kigi-4");
handle.record_model_usage("kigi-3"); // Duplicate
let snapshot = handle.snapshot().await.unwrap();
assert_eq!(snapshot.models_used.len(), 2);
assert!(snapshot.models_used.contains(&"grok-3".to_string()));
assert!(snapshot.models_used.contains(&"grok-4".to_string()));
assert!(snapshot.models_used.contains(&"kigi-3".to_string()));
assert!(snapshot.models_used.contains(&"kigi-4".to_string()));
handle.shutdown();
actor_handle.await.unwrap();
@@ -2501,7 +2501,7 @@ mod tests {
"bash".to_string(),
"search_replace".to_string(),
],
vec!["grok-3".to_string(), "grok-4".to_string()],
vec!["kigi-3".to_string(), "kigi-4".to_string()],
);
let snapshot = handle.snapshot().await.unwrap();
@@ -2520,21 +2520,21 @@ mod tests {
// Model tracking (newly restored)
assert_eq!(snapshot.models_used.len(), 2);
assert!(snapshot.models_used.contains(&"grok-3".to_string()));
assert!(snapshot.models_used.contains(&"grok-4".to_string()));
assert!(snapshot.models_used.contains(&"kigi-3".to_string()));
assert!(snapshot.models_used.contains(&"kigi-4".to_string()));
// After seeding, new tool calls should accumulate correctly
handle.record_tool_call("bash"); // existing tool
handle.record_tool_call("grep"); // new tool
handle.record_model_usage("grok-3"); // existing model
handle.record_model_usage("grok-4.5"); // new model
handle.record_model_usage("kigi-3"); // existing model
handle.record_model_usage("kigi-4.5"); // new model
let snapshot = handle.snapshot().await.unwrap();
assert_eq!(snapshot.tool_call_count, 14); // 12 + 2
assert_eq!(snapshot.tools_used.len(), 4); // bash not duplicated, grep added
assert!(snapshot.tools_used.contains(&"grep".to_string()));
assert_eq!(snapshot.models_used.len(), 3); // grok-3 not duplicated, grok-5 added
assert!(snapshot.models_used.contains(&"grok-4.5".to_string()));
assert_eq!(snapshot.models_used.len(), 3); // kigi-3 not duplicated, kigi-5 added
assert!(snapshot.models_used.contains(&"kigi-4.5".to_string()));
handle.shutdown();
actor_handle.await.unwrap();
@@ -2553,7 +2553,7 @@ mod tests {
handle1.record_tool_failure("bash");
handle1.record_error();
handle1.record_assistant_message();
handle1.record_model_usage("grok-3");
handle1.record_model_usage("kigi-3");
// Record inference metrics with ITL intervals for turn 1
handle1.record_inference_metrics(InferenceLatencyStats {
@@ -2572,7 +2572,7 @@ mod tests {
handle1.record_tool_call("search_replace");
handle1.record_cancellation();
handle1.record_assistant_message();
handle1.record_model_usage("grok-4");
handle1.record_model_usage("kigi-4");
handle1.increment_turn(); // turn 3
handle1.record_tool_call("bash");
@@ -2639,14 +2639,14 @@ mod tests {
assert!(restored.tools_used.contains(&"read_file".to_string()));
assert!(restored.tools_used.contains(&"search_replace".to_string()));
assert_eq!(restored.models_used.len(), 2);
assert!(restored.models_used.contains(&"grok-3".to_string()));
assert!(restored.models_used.contains(&"grok-4".to_string()));
assert!(restored.models_used.contains(&"kigi-3".to_string()));
assert!(restored.models_used.contains(&"kigi-4".to_string()));
assert_eq!(restored.latency_sample_count, 2);
assert_eq!(restored.avg_time_to_first_token_ms, 150);
assert_eq!(restored.avg_response_time_ms, 1500);
assert_eq!(restored.min_time_to_first_token_ms, 100);
assert_eq!(restored.max_time_to_first_token_ms, 200);
// ITL stats must survive the restore (regression test for grok-critique bug)
// ITL stats must survive the restore (regression test for kigi-critique bug)
assert_eq!(
restored.itl_p50_ms, snapshot.itl_p50_ms,
"itl_p50_ms should survive restore"
@@ -2669,7 +2669,7 @@ mod tests {
assert_eq!(restored.itl_sample_count, 1);
// Phase 2b: Take a turn-end snapshot *without* recording new ITL data.
// This is the exact scenario the grok-critique bug describes: the
// This is the exact scenario the kigi-critique bug describes: the
// TakeTurnEndSnapshot handler calls update_session_itl_percentiles()
// which must NOT wipe persisted ITL p50/p99 when itl_digest is None.
handle2.increment_turn(); // turn 4 (no ITL data recorded this turn)
@@ -2694,7 +2694,7 @@ mod tests {
handle2.increment_turn(); // turn 5
handle2.record_tool_call("grep"); // new tool
handle2.record_tool_call("bash"); // existing tool (should dedup)
handle2.record_model_usage("grok-3"); // existing model (should dedup)
handle2.record_model_usage("kigi-3"); // existing model (should dedup)
handle2.record_error();
handle2.record_assistant_message();
@@ -2709,7 +2709,7 @@ mod tests {
assert_eq!(after_turn.error_count, 3); // 2 + 1
assert_eq!(after_turn.tools_used.len(), 4); // bash not duplicated, grep added
assert!(after_turn.tools_used.contains(&"grep".to_string()));
assert_eq!(after_turn.models_used.len(), 2); // grok-3 not duplicated
assert_eq!(after_turn.models_used.len(), 2); // kigi-3 not duplicated
// Latency: (100+200+300)/3 = 200
assert_eq!(after_turn.latency_sample_count, 3);
assert_eq!(after_turn.avg_time_to_first_token_ms, 200);
@@ -534,7 +534,7 @@ pub(crate) fn builtin_commands(availability: CommandAvailability) -> Vec<acp::Av
.collect()
}
// ── x.ai/commands/list ext method ────────────────────────────────
// ── kigi/commands/list ext method ────────────────────────────────
#[derive(serde::Deserialize)]
pub(crate) struct ListCommandsRequest {
@@ -731,7 +731,7 @@ impl BuiltinAction {
/// How to rewrite the user's prompt when a slash command resolves to a skill.
///
/// - `RewriteToRun` (default): replace `/foo args` with `"run /foo args"`,
/// matching today's Grok Build flow that calls our dedicated `skill` tool.
/// matching today's Kigi flow that calls our dedicated `skill` tool.
/// - `Passthrough`: leave the prompt verbatim. Some templates use this —
/// the model is trained to spot a leading `/<name>`, look it up in the
/// `<agent_skills>` listing, and call the Read tool on `fullPath`.
@@ -1081,7 +1081,7 @@ fn parse_slash_prefix(prompt_blocks: &[acp::ContentBlock]) -> Option<(&str, &str
/// default: the model derives the cadence from the request and asks when none
/// is given.
fn build_loop_prompt_blocks(args: &str) -> Vec<acp::ContentBlock> {
use kigi_tools::implementations::grok_build::{loop_schedule_instruction, loop_usage_message};
use kigi_tools::implementations::kigi::{loop_schedule_instruction, loop_usage_message};
let text = if args.trim().is_empty() {
loop_usage_message().to_string()
@@ -1685,9 +1685,7 @@ mod tests {
#[test]
fn loop_prompt_matches_pager_wording() {
// The shell and pager must stay textually identical so they don't drift.
use kigi_tools::implementations::grok_build::{
loop_schedule_instruction, loop_usage_message,
};
use kigi_tools::implementations::kigi::{loop_schedule_instruction, loop_usage_message};
assert_eq!(loop_text(""), loop_usage_message());
assert_eq!(
loop_text("2h run tests"),
@@ -616,7 +616,7 @@ fn transform_session_id_in_update(
/// 2. Truncates at the last complete turn boundary. A complete turn runs
/// `User → Assistant → (matching ToolResults)`, possibly across multiple
/// Assistant/ToolResult cycles, with `Reasoning` siblings interleaved
/// throughout (real grok-build turns emit `[reasoning, assistant, tool
/// throughout (real kigi turns emit `[reasoning, assistant, tool
/// results, reasoning, assistant, ...]`). The scan treats everything
/// except `Assistant` as transparent and only advances the boundary when an
/// Assistant closes every tool call it made, so it survives reasoning
@@ -121,7 +121,7 @@ async fn test_jsonl_round_trip() {
.unwrap();
let plan_state = create_test_plan_state();
adapter.write_plan_state(&info, &plan_state).await.unwrap();
let new_model = acp::ModelId::new("grok-4.3");
let new_model = acp::ModelId::new("kigi-4.3");
adapter.update_current_model(&info, &new_model).await.unwrap();
let loaded = adapter.load_session(&info).await.unwrap();
assert_eq!(loaded.summary.info.id, info.id);
@@ -456,12 +456,12 @@ async fn test_subagent_notifications_round_trip() {
.len()
);
let spawned_json: serde_json::Value = serde_json::from_str(lines[0]).unwrap();
assert_eq!(spawned_json["method"], "_x.ai/session/update");
assert_eq!(spawned_json["method"], "_kigi/session/update");
let spawned_update = &spawned_json["params"]["update"];
assert_eq!(spawned_update["sessionUpdate"], "subagent_spawned");
assert_eq!(spawned_update["subagent_id"], "child-001");
let finished_json: serde_json::Value = serde_json::from_str(lines[1]).unwrap();
assert_eq!(finished_json["method"], "_x.ai/session/update");
assert_eq!(finished_json["method"], "_kigi/session/update");
let finished_update = &finished_json["params"]["update"];
assert_eq!(finished_update["sessionUpdate"], "subagent_finished");
assert_eq!(finished_update["tool_calls"], 5);
@@ -742,13 +742,13 @@ async fn test_copy_session_data_with_model_override() {
};
let options = CopySessionOptions {
parent_session_id: Some("source-model-test".to_string()),
new_model_id: Some("grok-3".to_string()),
new_model_id: Some("kigi-3".to_string()),
target_prompt_index: None,
..Default::default()
};
adapter.copy_session_data(&source_info, &target_info, options).await.unwrap();
let loaded = adapter.load_session(&target_info).await.unwrap();
assert_eq!(loaded.summary.current_model_id.0.as_ref(), "grok-3");
assert_eq!(loaded.summary.current_model_id.0.as_ref(), "kigi-3");
assert_eq!(loaded.summary.parent_session_id, Some("source-model-test".to_string()));
}
#[tokio::test]
@@ -1108,8 +1108,8 @@ async fn test_append_feedback_creates_file_and_persists() {
rating_value: Some(1),
feedback_text: None,
feedback_categories: vec![],
model_id: Some("grok-3-fast".into()),
resolved_model_id: Some("grok-4.5".into()),
model_id: Some("kigi-3-fast".into()),
resolved_model_id: Some("kigi-4.5".into()),
model_fingerprint: None,
context_type: None,
request_id: None,
@@ -1163,7 +1163,7 @@ async fn test_copy_session_data_copies_tool_state() {
.await
.unwrap();
let tool_state_json = serde_json::json!(
{ "state" : { "grok_build.TodoState" : { "todos" : [] } } }
{ "state" : { "kigi.TodoState" : { "todos" : [] } } }
);
let source_dir = adapter.session_dir(&source_info);
std::fs::write(
@@ -2250,7 +2250,7 @@ fn load_lines(lines: &[&str]) -> Vec<ConversationItem> {
}
/// Real-shape legacy fixture from a web-search session.
/// The assistant carries `reasoning: { text, encrypted, id }` inline —
/// the legacy grok-build / Opus / chat-completions shape.
/// the legacy kigi / Opus / chat-completions shape.
/// BackendToolCall sits as its own sibling line (it was already a
/// sibling variant in the legacy shape).
#[test]
@@ -2260,7 +2260,7 @@ fn read_chat_history_upgrades_legacy_singular_reasoning_to_sibling() {
r#"{"type":"system","content":"You are helpful."}"#,
r#"{"type":"user","content":[{"type":"text","text":"cats and dogs"}]}"#,
r#"{"type":"backend_tool_call","kind":{"tool_type":"web_search","id":"ws_legacy_1","status":"completed","action":{"type":"search","query":"cats and dogs","sources":[]}}}"#,
r#"{"type":"assistant","content":"results...","reasoning":{"text":"the results are about cats","encrypted":"enc-blob","id":"rs_legacy"},"model_id":"grok-build"}"#,
r#"{"type":"assistant","content":"results...","reasoning":{"text":"the results are about cats","encrypted":"enc-blob","id":"rs_legacy"},"model_id":"kigi"}"#,
],
);
assert_eq!(
@@ -2336,11 +2336,11 @@ fn read_chat_history_handles_hybrid_legacy_and_post_pr_lines() {
r#"{"type":"system","content":"sys"}"#,
r#"{"type":"user","content":[{"type":"text","text":"q1"}]}"#,
r#"{"type":"backend_tool_call","kind":{"tool_type":"web_search","id":"ws_legacy_1","status":"completed","action":{"type":"search","query":"q1","sources":[]}}}"#,
r#"{"type":"assistant","content":"a1","reasoning":{"text":"legacy thinking","encrypted":"enc","id":"rs_legacy"},"model_id":"grok-build"}"#,
r#"{"type":"assistant","content":"a1","reasoning":{"text":"legacy thinking","encrypted":"enc","id":"rs_legacy"},"model_id":"kigi"}"#,
r#"{"type":"user","content":[{"type":"text","text":"q2"}]}"#,
r#"{"type":"reasoning","id":"rs_postpr","summary":[{"type":"summary_text","text":"new thinking"}]}"#,
r#"{"type":"backend_tool_call","kind":{"tool_type":"web_search","id":"ws_postpr","status":"completed","action":{"type":"search","query":"q2","sources":[]}}}"#,
r#"{"type":"assistant","content":"a2","model_id":"grok-build"}"#,
r#"{"type":"assistant","content":"a2","model_id":"kigi"}"#,
],
);
let kinds: Vec<&'static str> = items
@@ -2381,7 +2381,7 @@ fn read_chat_history_handles_hybrid_legacy_and_post_pr_lines() {
};
assert_eq!(legacy_assistant.content.as_ref(), "a1");
assert_eq!(
legacy_assistant.model_id.as_deref(), Some("grok-build"),
legacy_assistant.model_id.as_deref(), Some("kigi"),
"model_id preserved across the upgrade"
);
let ConversationItem::Reasoning(reconstructed) = &items[3] else {
@@ -2402,7 +2402,7 @@ fn read_chat_history_is_idempotent_on_post_pr_sessions() {
r#"{"type":"system","content":"sys"}"#,
r#"{"type":"user","content":[{"type":"text","text":"q"}]}"#,
r#"{"type":"reasoning","id":"rs_x","summary":[{"type":"summary_text","text":"thought"}]}"#,
r#"{"type":"assistant","content":"a","model_id":"grok-build"}"#,
r#"{"type":"assistant","content":"a","model_id":"kigi"}"#,
],
);
let kinds: Vec<&'static str> = items
@@ -2516,7 +2516,7 @@ fn read_chat_history_skips_merged_line_from_interrupted_append() {
let good_1 = r#"{"type":"user","content":[{"type":"text","text":"kept"}]}"#;
let partial = r#"{"type":"assistant","content":"cut mid-wri"#;
let merged_onto = r#"{"type":"user","content":[{"type":"text","text":"lost"}]}"#;
let good_2 = r#"{"type":"assistant","content":"after","model_id":"grok-build"}"#;
let good_2 = r#"{"type":"assistant","content":"after","model_id":"kigi"}"#;
let raw = format!("{good_1}\n{partial}{merged_onto}\n{good_2}\n");
let temp_dir = TempDir::new().unwrap();
let (_, _, items) = load_raw_chat(&temp_dir, raw.as_bytes());
@@ -92,8 +92,22 @@ impl Iterator for UpdatesIterator {
/// Method name for standard ACP session/update notifications.
const ACP_SESSION_UPDATE_METHOD: &str = "session/update";
/// Method name for xAI extension session/update notifications.
pub(crate) const XAI_SESSION_UPDATE_METHOD: &str = "_x.ai/session/update";
/// Method name for extension session/update notifications.
pub(crate) const XAI_SESSION_UPDATE_METHOD: &str = "_kigi/session/update";
/// Pre-rebrand spelling of [`XAI_SESSION_UPDATE_METHOD`], as written into
/// `updates.jsonl` by builds that predate the `kigi/` extension-method
/// rename. READ-SIDE ALIAS ONLY: parsing accepts both spellings so existing
/// session files keep loading; the write side always emits
/// [`XAI_SESSION_UPDATE_METHOD`].
pub(crate) const LEGACY_XAI_SESSION_UPDATE_METHOD: &str = "_x.ai/session/update";
/// Whether `method` names the extension session-update rail, accepting the
/// current spelling and the legacy pre-rebrand spelling (persisted session
/// files written by older builds).
pub(crate) fn is_ext_session_update_method(method: &str) -> bool {
method == XAI_SESSION_UPDATE_METHOD || method == LEGACY_XAI_SESSION_UPDATE_METHOD
}
/// A unified session update that can be either an ACP notification or an xAI extension notification.
/// This allows storing all session updates in chronological order.
@@ -153,7 +167,7 @@ pub(crate) struct SessionUpdateEnvelope {
#[serde(default)]
pub timestamp: u64,
/// The method name identifying the update type.
/// Either "session/update" for ACP or "_x.ai/session/update" for xAI extensions.
/// Either "session/update" for ACP or "_kigi/session/update" for xAI extensions.
pub method: String,
/// The actual notification payload.
pub params: serde_json::Value,
@@ -183,7 +197,7 @@ impl SessionUpdateEnvelope {
/// Convert this envelope back into a SessionUpdate.
pub(crate) fn into_update(self) -> Result<SessionUpdate, serde_json::Error> {
if self.method == XAI_SESSION_UPDATE_METHOD {
if is_ext_session_update_method(&self.method) {
let notification: SessionNotification = serde_json::from_value(self.params)?;
Ok(SessionUpdate::Xai(Box::new(notification)))
} else {
@@ -224,7 +238,7 @@ impl SessionUpdateEnvelope {
// Try to parse as envelope first (has "method" + "params")
if let Ok(envelope) = serde_json::from_str::<BorrowedEnvelope<'_>>(line) {
let raw_params = envelope.params.get();
return if envelope.method == Some(XAI_SESSION_UPDATE_METHOD) {
return if envelope.method.is_some_and(is_ext_session_update_method) {
let notification: SessionNotification = serde_json::from_str(raw_params)?;
Ok(SessionUpdate::Xai(Box::new(notification)))
} else {
@@ -774,7 +788,7 @@ pub(crate) fn filter_rewind_lines<'a>(lines: Vec<&'a str>) -> Vec<&'a str> {
for line in &lines {
let (raw_params, is_xai) = if let Ok(env) = serde_json::from_str::<RawLinePeek<'_>>(line) {
let raw = env.params.map(|p| p.get()).unwrap_or(line);
let xai = env.method == Some(XAI_SESSION_UPDATE_METHOD);
let xai = env.method.is_some_and(is_ext_session_update_method);
(raw, xai)
} else {
(*line, false)
@@ -923,7 +937,7 @@ pub fn load_updates_for_replay(
load_updates_for_replay_from_dir(&session_dir)
}
/// Like [`load_updates_for_replay`], but resolves the session under a specific grok home.
/// Like [`load_updates_for_replay`], but resolves the session under a specific kigi home.
pub fn load_updates_for_replay_at(
session_id: &str,
kigi_home: &std::path::Path,
@@ -1666,7 +1680,7 @@ pub(crate) fn parse_prompt_extract_event(line: &str) -> PromptExtractEvent {
// Step 1: try to extract the envelope (method + raw params).
let (raw_params, is_xai) = if let Ok(env) = serde_json::from_str::<RawLinePeek<'_>>(line) {
let raw = env.params.map(|p| p.get()).unwrap_or(line);
let xai = env.method == Some(XAI_SESSION_UPDATE_METHOD);
let xai = env.method.is_some_and(is_ext_session_update_method);
(raw, xai)
} else {
// Not a valid envelope → try legacy format: the line IS the params.
@@ -1739,7 +1753,7 @@ mod tests {
/// Wrap a xAI notification as the envelope stored in updates.jsonl.
fn xai_envelope(session_update_json: &str) -> String {
format!(
r#"{{"timestamp":1,"method":"_x.ai/session/update","params":{{"sessionId":"s","update":{session_update_json}}}}}"#
r#"{{"timestamp":1,"method":"_kigi/session/update","params":{{"sessionId":"s","update":{session_update_json}}}}}"#
)
}
@@ -2479,7 +2493,7 @@ mod tests {
r#"{"eventId":"ev1"}"#,
);
// xAI-style line persisted by an older binary: no _meta at all.
let old_xai = r#"{"timestamp":2,"method":"_x.ai/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"hook_annotation","message":"trailing"}}}"#;
let old_xai = r#"{"timestamp":2,"method":"_kigi/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"hook_annotation","message":"trailing"}}}"#;
let raw = format!("{a1}\n{old_xai}\n");
let prepared = prepare_replay_lines(&raw, Some("ev1"));
@@ -2490,7 +2504,7 @@ mod tests {
assert_eq!(prepared.lines.len(), 2, "full history is replayed");
// Same history with the trailing line stamped resolves incrementally.
let new_xai = r#"{"timestamp":2,"method":"_x.ai/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"hook_annotation","message":"trailing"},"_meta":{"eventId":"ev2"}}}"#;
let new_xai = r#"{"timestamp":2,"method":"_kigi/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"hook_annotation","message":"trailing"},"_meta":{"eventId":"ev2"}}}"#;
let raw = format!("{a1}\n{new_xai}\n");
let prepared = prepare_replay_lines(&raw, Some("ev1"));
assert!(!prepared.mark_replay);
@@ -2961,12 +2975,12 @@ mod tests {
fn prepare_replay_reports_spawn_without_finish() {
let spawn = |id: &str, child: &str| {
format!(
r#"{{"method":"_x.ai/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"subagent_spawned","subagent_id":"{id}","parent_session_id":"s","child_session_id":"{child}","subagent_type":"general-purpose","description":"task"}},"_meta":{{"eventId":"s-1"}}}}}}"#
r#"{{"method":"_kigi/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"subagent_spawned","subagent_id":"{id}","parent_session_id":"s","child_session_id":"{child}","subagent_type":"general-purpose","description":"task"}},"_meta":{{"eventId":"s-1"}}}}}}"#
)
};
let finish = |id: &str| {
format!(
r#"{{"method":"_x.ai/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"subagent_finished","subagent_id":"{id}","child_session_id":"c{id}","status":"completed","tool_calls":0,"turns":0,"duration_ms":0}},"_meta":{{"eventId":"s-2"}}}}}}"#
r#"{{"method":"_kigi/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"subagent_finished","subagent_id":"{id}","child_session_id":"c{id}","status":"completed","tool_calls":0,"turns":0,"duration_ms":0}},"_meta":{{"eventId":"s-2"}}}}}}"#
)
};
// `a` spawns and finishes (paired); `b` only spawns (orphan).
@@ -3139,4 +3153,38 @@ mod tests {
SessionUpdate::Acp(_) => panic!("expected Xai variant"),
}
}
/// Session files written before the `kigi/` extension-method rename carry
/// the legacy `_…/session/update` method name. The read-side alias must
/// keep those lines loading as extension updates; the write side always
/// emits the current [`XAI_SESSION_UPDATE_METHOD`].
#[test]
fn from_str_accepts_legacy_pre_rebrand_method_name() {
let line = format!(
r#"{{"timestamp":1,"method":"{LEGACY_XAI_SESSION_UPDATE_METHOD}","params":{{"sessionId":"s","update":{{"sessionUpdate":"memory_flush_started"}}}}}}"#
);
let update = SessionUpdateEnvelope::from_str(&line).unwrap();
match &update {
SessionUpdate::Xai(notif) => {
assert_eq!(
notif.update,
crate::extensions::notification::SessionUpdate::MemoryFlushStarted
);
}
SessionUpdate::Acp(_) => panic!("expected Xai variant for legacy method name"),
}
// Both spellings classify as the extension rail; an unrelated method
// does not.
assert!(is_ext_session_update_method(XAI_SESSION_UPDATE_METHOD));
assert!(is_ext_session_update_method(
LEGACY_XAI_SESSION_UPDATE_METHOD
));
assert!(!is_ext_session_update_method("session/update"));
// Write side: envelopes produced today carry the current name only.
let reserialized = serde_json::to_string(&update).unwrap();
assert!(reserialized.contains(XAI_SESSION_UPDATE_METHOD));
assert!(!reserialized.contains(LEGACY_XAI_SESSION_UPDATE_METHOD));
}
}
@@ -8,7 +8,7 @@
//! The index is bootstrapped (all sessions indexed) on first search.
//! After that, individual sessions are re-indexed on save/title update
//! via `notify_session_updated()`. Because the SQLite DB is shared with
//! other concurrently running grok processes (which may wipe or downgrade
//! other concurrently running kigi processes (which may wipe or downgrade
//! it — older binaries drop-and-restamp the schema on open), every
//! subsequent search re-verifies the on-disk completed-bootstrap marker
//! and re-runs the full bootstrap when it is missing.
@@ -27,7 +27,7 @@ use super::search_fts::{SessionDoc, SessionSearchIndex, SessionSearchRow};
use super::search_remote_sync;
use super::{
ContentPeek, PromptExtractEvent, RawLinePeek, RawParamsPeek, StorageAdapter,
XAI_SESSION_UPDATE_METHOD, collect_prompts_from_events,
collect_prompts_from_events, is_ext_session_update_method,
};
use crate::session::info::Info;
use crate::session::persistence::Summary;
@@ -133,7 +133,7 @@ struct SearchManagerState {
///
/// Requires an active tokio runtime on first access (spawns tasks).
///
/// TODO: When multiple grok processes run concurrently, they each have
/// TODO: When multiple kigi processes run concurrently, they each have
/// their own `SearchIndexManager` writing to the same SQLite database.
/// WAL mode prevents corruption, but redundant work is done. Consider
/// adding reindex claim coordination (like the memory system's
@@ -921,7 +921,7 @@ fn collect_all_indexable_content_single_pass(updates_path: &Path) -> io::Result<
let (raw_params, is_xai) = if let Ok(env) = serde_json::from_str::<RawLinePeek<'_>>(trimmed)
{
let raw = env.params.map(|p| p.get()).unwrap_or(trimmed);
let xai = env.method == Some(XAI_SESSION_UPDATE_METHOD);
let xai = env.method.is_some_and(is_ext_session_update_method);
(raw, xai)
} else {
(trimmed, false)
@@ -938,7 +938,7 @@ fn collect_all_indexable_content_single_pass(updates_path: &Path) -> io::Result<
// Content events (user messages, assistant responses, tool calls,
// thoughts) come from the standard ACP protocol ("session/update").
// Control events (rewind markers) come from xAI extensions
// ("_x.ai/session/update"). Dispatch on source first, then tag.
// ("_kigi/session/update"). Dispatch on source first, then tag.
if !is_xai {
// ── ACP content events ──────────────────────────────────
match tag {
@@ -1176,7 +1176,7 @@ fn collect_delta_content(updates_path: &Path, offset: u64) -> io::Result<DeltaRe
let (raw_params, is_xai) = if let Ok(env) = serde_json::from_str::<RawLinePeek<'_>>(trimmed)
{
let raw = env.params.map(|p| p.get()).unwrap_or(trimmed);
let xai = env.method == Some(XAI_SESSION_UPDATE_METHOD);
let xai = env.method.is_some_and(is_ext_session_update_method);
(raw, xai)
} else {
(trimmed, false)
@@ -1389,7 +1389,7 @@ mod tests {
fn xai_update(session_update_json: &str) -> String {
format!(
r#"{{"timestamp":1,"method":"_x.ai/session/update","params":{{"sessionId":"s","update":{session_update_json}}}}}"#
r#"{{"timestamp":1,"method":"_kigi/session/update","params":{{"sessionId":"s","update":{session_update_json}}}}}"#
)
}
@@ -111,7 +111,7 @@ impl SessionSearchIndex {
.unwrap_or(None);
// One-way ratchet: drop only on UPGRADE (stored < current). Multiple
// grok generations share this DB (stable vs alpha); an equality check
// kigi generations share this DB (stable vs alpha); an equality check
// made each binary wipe the other's index in a ping-pong that left
// search empty mid-rebootstrap. A newer index is safe to read: bumps
// regenerate content only (table schema is column-identical), and the
@@ -709,7 +709,7 @@ mod tests {
index
.upsert_doc(&test_doc("s1", "Rust debugging", "borrow checker"))
.unwrap();
// Simulate an index owned by a newer grok generation that has
// Simulate an index owned by a newer kigi generation that has
// completed a bootstrap.
index
.set_meta("session_search_schema_version", "5")

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