§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:
@@ -11,7 +11,7 @@
|
||||
//! picks chain back to back, reproducing the continuous
|
||||
//! `.git/index.lock` / HEAD-move churn of an agent-run rebase.
|
||||
//!
|
||||
//! With the fs-watch machinery on (`x.ai/hunkTracker` + `x.ai/gitHeadChanged`
|
||||
//! With the fs-watch machinery on (`kigi/hunkTracker` + `kigi/gitHeadChanged`
|
||||
//! advertised), fsnotify merges rapid lock cycles into one operation and the
|
||||
//! session defers debounce fires while an op is in flight, so one rebase
|
||||
//! costs at most a couple of `refresh_all_baselines` scans (each scoped to
|
||||
@@ -425,7 +425,7 @@ fn git_rebase_refresh_storm_e2e() {
|
||||
let server = mock_rt
|
||||
.block_on(MockInferenceServer::start())
|
||||
.expect("mock server");
|
||||
let kigi_home = TempDir::new().expect("grok home");
|
||||
let kigi_home = TempDir::new().expect("kigi home");
|
||||
|
||||
// SAFETY: the only live threads are the mock runtime's workers, which
|
||||
// serve HTTP and never read the process environment.
|
||||
@@ -450,8 +450,8 @@ fn git_rebase_refresh_storm_e2e() {
|
||||
let on = agent_rt.block_on(run_storm(
|
||||
&server,
|
||||
Some(json!({
|
||||
"x.ai/hunkTracker": { "mode": "agent_only" },
|
||||
"x.ai/gitHeadChanged": true,
|
||||
"kigi/hunkTracker": { "mode": "agent_only" },
|
||||
"kigi/gitHeadChanged": true,
|
||||
})),
|
||||
&counter,
|
||||
"machinery-on",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"""
|
||||
Memory System Integration Tests — Full Suite.
|
||||
|
||||
Launches grok agent stdio in isolated environments ($HOME override)
|
||||
Launches kigi agent stdio in isolated environments ($HOME override)
|
||||
with pre-populated memory files. Tests the entire memory lifecycle:
|
||||
indexing, search, embeddings, flush, session-end, compaction, pruning.
|
||||
|
||||
@@ -67,7 +67,7 @@ def section(t):
|
||||
|
||||
|
||||
class AcpClient:
|
||||
"""Talks ACP (JSON-RPC over NDJSON) to a grok agent stdio subprocess."""
|
||||
"""Talks ACP (JSON-RPC over NDJSON) to a kigi agent stdio subprocess."""
|
||||
|
||||
def __init__(self, proc, cwd=None):
|
||||
self.proc = proc
|
||||
@@ -163,38 +163,38 @@ class AcpClient:
|
||||
|
||||
|
||||
class IsolatedEnv:
|
||||
"""Creates a fully isolated grok environment with custom $HOME.
|
||||
"""Creates a fully isolated kigi environment with custom $HOME.
|
||||
|
||||
The agent subprocess gets a fake $HOME with:
|
||||
~/.grok/auth.json (copied from real home)
|
||||
~/.grok/memory/ (pre-populated by tests)
|
||||
~/.grok/logs/ (memory.log appears here)
|
||||
~/.kigi/auth.json (copied from real home)
|
||||
~/.kigi/memory/ (pre-populated by tests)
|
||||
~/.kigi/logs/ (memory.log appears here)
|
||||
|
||||
And a workspace directory used as cwd when spawning the agent.
|
||||
"""
|
||||
|
||||
def __init__(self, workspace_name="test-project"):
|
||||
self.root = tempfile.mkdtemp(prefix="grok-memtest-")
|
||||
self.root = tempfile.mkdtemp(prefix="kigi-memtest-")
|
||||
self.fake_home = os.path.join(self.root, "home")
|
||||
self.workspace = os.path.join(self.root, "workspace", workspace_name)
|
||||
os.makedirs(self.fake_home)
|
||||
os.makedirs(self.workspace)
|
||||
|
||||
# Create .grok dirs in fake home
|
||||
self.grok_home = os.path.join(self.fake_home, ".grok")
|
||||
self.memory_dir = os.path.join(self.grok_home, "memory")
|
||||
self.logs_dir = os.path.join(self.grok_home, "logs")
|
||||
# Create .kigi dirs in fake home
|
||||
self.kigi_home = os.path.join(self.fake_home, ".kigi")
|
||||
self.memory_dir = os.path.join(self.kigi_home, "memory")
|
||||
self.logs_dir = os.path.join(self.kigi_home, "logs")
|
||||
os.makedirs(self.memory_dir)
|
||||
os.makedirs(self.logs_dir)
|
||||
|
||||
# Copy auth from real home
|
||||
real_auth = os.path.expanduser("~/.grok/auth.json")
|
||||
real_auth = os.path.expanduser("~/.kigi/auth.json")
|
||||
if os.path.isfile(real_auth):
|
||||
shutil.copy2(real_auth, os.path.join(self.grok_home, "auth.json"))
|
||||
shutil.copy2(real_auth, os.path.join(self.kigi_home, "auth.json"))
|
||||
|
||||
def write_config(self, toml_str):
|
||||
"""Write global ~/.grok/config.toml (where the agent reads config)."""
|
||||
with open(os.path.join(self.grok_home, "config.toml"), "w") as f:
|
||||
"""Write global ~/.kigi/config.toml (where the agent reads config)."""
|
||||
with open(os.path.join(self.kigi_home, "config.toml"), "w") as f:
|
||||
f.write(toml_str)
|
||||
|
||||
def write_global_memory(self, content):
|
||||
@@ -266,9 +266,9 @@ class IsolatedEnv:
|
||||
return sorted(files)
|
||||
|
||||
def spawn_agent(self, extra_env=None):
|
||||
binary = os.environ.get("KIGI_BINARY", "grok")
|
||||
binary = os.environ.get("KIGI_BINARY", "kigi")
|
||||
if not shutil.which(binary) and not os.path.isfile(binary):
|
||||
print(f"{R}grok binary not found: {binary}{N}")
|
||||
print(f"{R}kigi binary not found: {binary}{N}")
|
||||
sys.exit(1)
|
||||
env = os.environ.copy()
|
||||
env["HOME"] = self.fake_home
|
||||
@@ -671,7 +671,7 @@ def test_fts_special_characters():
|
||||
* Use C++ for performance-critical code
|
||||
* Configure with --enable-feature=fast_path
|
||||
* Email: dev@example.com
|
||||
* Path: /usr/local/bin/grok
|
||||
* Path: /usr/local/bin/kigi
|
||||
* Version >= 2.0.0
|
||||
""")
|
||||
client = env.spawn_agent()
|
||||
@@ -1344,10 +1344,10 @@ def test_multiple_workspace_isolation():
|
||||
beta_workspace = os.path.join(env1.root, "workspace", "project-beta")
|
||||
os.makedirs(beta_workspace, exist_ok=True)
|
||||
|
||||
# Config is already in global ~/.grok/config.toml from env1.write_config
|
||||
# Config is already in global ~/.kigi/config.toml from env1.write_config
|
||||
|
||||
# Start agent in workspace beta (reuse env1's fake home)
|
||||
binary = os.environ.get("KIGI_BINARY", "grok")
|
||||
binary = os.environ.get("KIGI_BINARY", "kigi")
|
||||
env_vars = os.environ.copy()
|
||||
env_vars["HOME"] = env1.fake_home
|
||||
env_vars["KIGI_MEMORY"] = "1"
|
||||
|
||||
@@ -83,7 +83,7 @@ async fn empty_dk_response_marker_binds_the_verified_deployment_id() {
|
||||
assert!(kigi_shell::managed_config::managed_policy_gate().is_ok());
|
||||
}
|
||||
|
||||
/// A signature-rejected sync surfaces as failure in BOTH `grok setup` and the
|
||||
/// A signature-rejected sync surfaces as failure in BOTH `kigi setup` and the
|
||||
/// post-login sync — never as Installed/NoChange while nothing was persisted.
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
//! - Same-type model switching (no rebuild)
|
||||
//! - Session resume
|
||||
//!
|
||||
//! Each test spawns a real `grok agent stdio` process, speaks the full ACP
|
||||
//! Each test spawns a real `kigi agent stdio` process, speaks the full ACP
|
||||
//! protocol, and asserts on the inference request bodies (system prompt) and
|
||||
//! stderr tracing output to verify the correct harness was used.
|
||||
//!
|
||||
@@ -39,7 +39,7 @@ fn invalidate_models_cache(home: &std::path::Path) {
|
||||
}
|
||||
}
|
||||
/// Start a mock server with two models:
|
||||
/// - `default-model`: no agent_type (→ defaults to "grok-build")
|
||||
/// - `default-model`: no agent_type (→ defaults to "kigi")
|
||||
async fn dual_model_server() -> MockInferenceServer {
|
||||
MockInferenceServer::start_with_models(vec![
|
||||
MockModelEntry::new("default-model"),
|
||||
@@ -49,8 +49,8 @@ async fn dual_model_server() -> MockInferenceServer {
|
||||
.expect("start mock server")
|
||||
}
|
||||
/// Start a mock server with two models that share the same agent_type:
|
||||
/// - `model-a`: no agent_type (→ "grok-build")
|
||||
/// - `model-b`: no agent_type (→ "grok-build")
|
||||
/// - `model-a`: no agent_type (→ "kigi")
|
||||
/// - `model-b`: no agent_type (→ "kigi")
|
||||
async fn same_type_server() -> MockInferenceServer {
|
||||
MockInferenceServer::start_with_models(vec![
|
||||
MockModelEntry::new("model-a"),
|
||||
@@ -60,17 +60,17 @@ async fn same_type_server() -> MockInferenceServer {
|
||||
.expect("start mock server")
|
||||
}
|
||||
/// Session created with a model that has no `agent_type` should use the
|
||||
/// `grok-build` harness. The system prompt sent to the LLM should contain
|
||||
/// the grok-build identity string.
|
||||
/// `kigi` harness. The system prompt sent to the LLM should contain
|
||||
/// the kigi identity string.
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_default_model_uses_grok_build_harness() {
|
||||
async fn test_default_model_uses_kigi_harness() {
|
||||
with_local_set(|| async {
|
||||
let server = MockInferenceServer::start()
|
||||
.await
|
||||
.expect("start mock server");
|
||||
let workdir = git_workdir();
|
||||
let client = GrokStdioClient::spawn(&server, workdir.path()).await;
|
||||
let client = KigiStdioClient::spawn(&server, workdir.path()).await;
|
||||
client.initialize_with_timeout().await;
|
||||
let session_id = client.create_session_with_timeout(workdir.path()).await;
|
||||
let result = client.prompt_with_timeout(&session_id, "say hello").await;
|
||||
@@ -79,8 +79,8 @@ async fn test_default_model_uses_grok_build_harness() {
|
||||
.last_system_prompt()
|
||||
.expect("should have at least one inference request");
|
||||
assert!(
|
||||
sys_prompt.contains("Grok") || sys_prompt.contains("grok"),
|
||||
"default model should use grok-build harness\nsystem prompt preview: {}",
|
||||
sys_prompt.contains("Kigi") || sys_prompt.contains("kigi"),
|
||||
"default model should use kigi harness\nsystem prompt preview: {}",
|
||||
&sys_prompt[..sys_prompt.len().min(500)]
|
||||
);
|
||||
})
|
||||
@@ -94,7 +94,7 @@ async fn test_same_type_model_switch_no_rebuild() {
|
||||
with_local_set(|| async {
|
||||
let server = same_type_server().await;
|
||||
let workdir = git_workdir();
|
||||
let client = GrokStdioClient::spawn(&server, workdir.path()).await;
|
||||
let client = KigiStdioClient::spawn(&server, workdir.path()).await;
|
||||
client.initialize_with_timeout().await;
|
||||
let session_id = client
|
||||
.create_session_with_model_timeout(workdir.path(), "model-a")
|
||||
@@ -128,7 +128,7 @@ async fn test_session_resume_preserves_harness() {
|
||||
.await
|
||||
.expect("start mock server");
|
||||
let workdir = git_workdir();
|
||||
let mut writer = GrokStdioClient::spawn(&server, workdir.path()).await;
|
||||
let mut writer = KigiStdioClient::spawn(&server, workdir.path()).await;
|
||||
writer.initialize_with_timeout().await;
|
||||
let session_id = writer.create_session_with_timeout(workdir.path()).await;
|
||||
let result = writer.prompt_with_timeout(&session_id, "say hello").await;
|
||||
@@ -139,7 +139,7 @@ async fn test_session_resume_preserves_harness() {
|
||||
let shared_home = writer.take_home();
|
||||
invalidate_models_cache(shared_home.path());
|
||||
drop(writer);
|
||||
let reader = GrokStdioClient::spawn_with_home(&server, workdir.path(), shared_home).await;
|
||||
let reader = KigiStdioClient::spawn_with_home(&server, workdir.path(), shared_home).await;
|
||||
reader.initialize_with_timeout().await;
|
||||
let _ = reader
|
||||
.load_session_with_timeout(&session_id, workdir.path())
|
||||
@@ -153,16 +153,16 @@ async fn test_session_resume_preserves_harness() {
|
||||
let resumed_sys_prompt = server
|
||||
.last_system_prompt()
|
||||
.expect("should have captured resumed system prompt");
|
||||
let original_has_grok =
|
||||
original_sys_prompt.contains("Grok") || original_sys_prompt.contains("grok");
|
||||
let resumed_has_grok =
|
||||
resumed_sys_prompt.contains("Grok") || resumed_sys_prompt.contains("grok");
|
||||
let original_has_kigi =
|
||||
original_sys_prompt.contains("Kigi") || original_sys_prompt.contains("kigi");
|
||||
let resumed_has_kigi =
|
||||
resumed_sys_prompt.contains("Kigi") || resumed_sys_prompt.contains("kigi");
|
||||
assert_eq!(
|
||||
original_has_grok,
|
||||
resumed_has_grok,
|
||||
original_has_kigi,
|
||||
resumed_has_kigi,
|
||||
"resumed session should use the same harness as the original\n\
|
||||
original identity markers: grok={original_has_grok}\n\
|
||||
resumed identity markers: grok={resumed_has_grok}\n\
|
||||
original identity markers: kigi={original_has_kigi}\n\
|
||||
resumed identity markers: kigi={resumed_has_kigi}\n\
|
||||
original prompt (first 300): {}\n\
|
||||
resumed prompt (first 300): {}",
|
||||
&original_sys_prompt[..original_sys_prompt.len().min(300)],
|
||||
@@ -172,172 +172,159 @@ async fn test_session_resume_preserves_harness() {
|
||||
.await;
|
||||
}
|
||||
/// A model that doesn't declare `agent_type` in its metadata should
|
||||
/// default to `"grok-build"`. This exercises the serde default.
|
||||
/// default to `"kigi"`. This exercises the serde default.
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_model_without_agent_type_defaults_to_grok_build() {
|
||||
async fn test_model_without_agent_type_defaults_to_kigi() {
|
||||
with_local_set(|| async {
|
||||
let server = MockInferenceServer::start_with_models(
|
||||
vec![MockModelEntry::new("no-agent-type-model"),],
|
||||
)
|
||||
.await
|
||||
.expect("start mock server");
|
||||
let workdir = git_workdir();
|
||||
let client = GrokStdioClient::spawn(&server, workdir.path()).await;
|
||||
client.initialize_with_timeout().await;
|
||||
let session_id = client
|
||||
.create_session_with_model_timeout(workdir.path(), "no-agent-type-model")
|
||||
.await;
|
||||
let result = client.prompt_with_timeout(&session_id, "say hello").await;
|
||||
assert!(result.is_ok(), "prompt failed: {:?}", result.err());
|
||||
let sys_prompt = server
|
||||
.last_system_prompt()
|
||||
.expect("should have at least one inference request");
|
||||
assert!(
|
||||
sys_prompt.contains("Grok") || sys_prompt.contains("grok"),
|
||||
"model without agent_type should default to grok-build harness\nsystem prompt preview: {}",
|
||||
& sys_prompt[..sys_prompt.len().min(500)]
|
||||
);
|
||||
})
|
||||
.await;
|
||||
let server = MockInferenceServer::start_with_models(vec![MockModelEntry::new(
|
||||
"no-agent-type-model",
|
||||
)])
|
||||
.await
|
||||
.expect("start mock server");
|
||||
let workdir = git_workdir();
|
||||
let client = KigiStdioClient::spawn(&server, workdir.path()).await;
|
||||
client.initialize_with_timeout().await;
|
||||
let session_id = client
|
||||
.create_session_with_model_timeout(workdir.path(), "no-agent-type-model")
|
||||
.await;
|
||||
let result = client.prompt_with_timeout(&session_id, "say hello").await;
|
||||
assert!(result.is_ok(), "prompt failed: {:?}", result.err());
|
||||
let sys_prompt = server
|
||||
.last_system_prompt()
|
||||
.expect("should have at least one inference request");
|
||||
assert!(
|
||||
sys_prompt.contains("Kigi") || sys_prompt.contains("kigi"),
|
||||
"model without agent_type should default to kigi harness\nsystem prompt preview: {}",
|
||||
&sys_prompt[..sys_prompt.len().min(500)]
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
/// The `KIGI_AGENT` escape hatch should override the model's agent_type.
|
||||
/// Setting `KIGI_AGENT=grok-build` with an alternate-agent model should use
|
||||
/// grok-build harness.
|
||||
/// Setting `KIGI_AGENT=kigi` with an alternate-agent model should use
|
||||
/// kigi harness.
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_grok_agent_env_overrides_model_agent_type() {
|
||||
async fn test_kigi_agent_env_overrides_model_agent_type() {
|
||||
with_local_set(|| async {
|
||||
let server = dual_model_server().await;
|
||||
let workdir = git_workdir();
|
||||
let binary = grok_binary();
|
||||
let home = tempfile::TempDir::new().expect("create temp home");
|
||||
let mut cmd = tokio::process::Command::new(&binary);
|
||||
cmd.args(["agent", "stdio"])
|
||||
.current_dir(workdir.path())
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.kill_on_drop(true);
|
||||
kigi_test_support::env::test_env_cmd_tokio(
|
||||
&mut cmd,
|
||||
&server.url(),
|
||||
home.path(),
|
||||
);
|
||||
cmd.env("KIGI_AGENT", "grok-build");
|
||||
let mut child = cmd.spawn().expect("spawn grok");
|
||||
let outgoing = child.stdin.take().unwrap();
|
||||
let incoming = child.stdout.take().unwrap();
|
||||
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
|
||||
let outgoing = outgoing.compat_write();
|
||||
let incoming = incoming.compat();
|
||||
let incoming = kigi_acp_lib::LineBufferedRead::spawn_local(incoming);
|
||||
use agent_client_protocol as acp;
|
||||
struct NoopClient;
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl acp::Client for NoopClient {
|
||||
async fn request_permission(
|
||||
&self,
|
||||
args: acp::RequestPermissionRequest,
|
||||
) -> acp::Result<acp::RequestPermissionResponse> {
|
||||
let outcome = args
|
||||
.options
|
||||
.iter()
|
||||
.find(|o| o.kind == acp::PermissionOptionKind::AllowOnce)
|
||||
.or(args.options.first())
|
||||
.map(|o| acp::RequestPermissionOutcome::Selected(
|
||||
let server = dual_model_server().await;
|
||||
let workdir = git_workdir();
|
||||
let binary = kigi_binary();
|
||||
let home = tempfile::TempDir::new().expect("create temp home");
|
||||
let mut cmd = tokio::process::Command::new(&binary);
|
||||
cmd.args(["agent", "stdio"])
|
||||
.current_dir(workdir.path())
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.kill_on_drop(true);
|
||||
kigi_test_support::env::test_env_cmd_tokio(&mut cmd, &server.url(), home.path());
|
||||
cmd.env("KIGI_AGENT", "kigi");
|
||||
let mut child = cmd.spawn().expect("spawn kigi");
|
||||
let outgoing = child.stdin.take().unwrap();
|
||||
let incoming = child.stdout.take().unwrap();
|
||||
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
|
||||
let outgoing = outgoing.compat_write();
|
||||
let incoming = incoming.compat();
|
||||
let incoming = kigi_acp_lib::LineBufferedRead::spawn_local(incoming);
|
||||
use agent_client_protocol as acp;
|
||||
struct NoopClient;
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl acp::Client for NoopClient {
|
||||
async fn request_permission(
|
||||
&self,
|
||||
args: acp::RequestPermissionRequest,
|
||||
) -> acp::Result<acp::RequestPermissionResponse> {
|
||||
let outcome = args
|
||||
.options
|
||||
.iter()
|
||||
.find(|o| o.kind == acp::PermissionOptionKind::AllowOnce)
|
||||
.or(args.options.first())
|
||||
.map(|o| {
|
||||
acp::RequestPermissionOutcome::Selected(
|
||||
acp::SelectedPermissionOutcome::new(o.option_id.clone()),
|
||||
))
|
||||
.unwrap_or(acp::RequestPermissionOutcome::Cancelled);
|
||||
Ok(acp::RequestPermissionResponse::new(outcome))
|
||||
}
|
||||
async fn session_notification(
|
||||
&self,
|
||||
_args: acp::SessionNotification,
|
||||
) -> acp::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
})
|
||||
.unwrap_or(acp::RequestPermissionOutcome::Cancelled);
|
||||
Ok(acp::RequestPermissionResponse::new(outcome))
|
||||
}
|
||||
let (conn, handle_io) = acp::ClientSideConnection::new(
|
||||
NoopClient,
|
||||
outgoing,
|
||||
incoming,
|
||||
|fut| {
|
||||
tokio::task::spawn_local(fut);
|
||||
},
|
||||
);
|
||||
tokio::task::spawn_local(handle_io);
|
||||
let _init = tokio::time::timeout(
|
||||
Duration::from_secs(20),
|
||||
conn
|
||||
.initialize(
|
||||
acp::InitializeRequest::new(acp::ProtocolVersion::V1)
|
||||
.client_capabilities(
|
||||
acp::ClientCapabilities::new()
|
||||
.fs(acp::FileSystemCapabilities::new())
|
||||
.terminal(false),
|
||||
)
|
||||
.meta(
|
||||
serde_json::json!(
|
||||
{ "startupHints" : { "nonInteractive" : true,
|
||||
"skipGitStatus" : true, "skipProjectLayout" : true },
|
||||
"clientType" : "test-client", "clientVersion" : "0.0.0-test"
|
||||
}
|
||||
)
|
||||
.as_object()
|
||||
.cloned(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("init timed out")
|
||||
.expect("init failed");
|
||||
conn.authenticate(
|
||||
acp::AuthenticateRequest::new(acp::AuthMethodId::new("xai.api_key"))
|
||||
.meta(
|
||||
serde_json::json!({ "headless" : true }).as_object().cloned(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("auth failed");
|
||||
let session = tokio::time::timeout(
|
||||
Duration::from_secs(20),
|
||||
conn
|
||||
.new_session(
|
||||
acp::NewSessionRequest::new(workdir.path().to_path_buf())
|
||||
.meta(
|
||||
serde_json::json!({ "modelId" : "cursor-model" })
|
||||
.as_object()
|
||||
.cloned(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("session/new timed out")
|
||||
.expect("session/new failed");
|
||||
let _prompt = tokio::time::timeout(
|
||||
Duration::from_secs(30),
|
||||
conn
|
||||
.prompt(
|
||||
acp::PromptRequest::new(
|
||||
session.session_id.clone(),
|
||||
vec![
|
||||
acp::ContentBlock::Text(acp::TextContent::new("say hello"))
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("prompt timed out")
|
||||
.expect("prompt failed");
|
||||
let sys_prompt = server
|
||||
.last_system_prompt()
|
||||
.expect("should have inference request");
|
||||
assert!(
|
||||
sys_prompt.contains("Grok") || sys_prompt.contains("grok"),
|
||||
"KIGI_AGENT=grok-build should override cursor model's agent_type\nsystem prompt preview: {}",
|
||||
& sys_prompt[..sys_prompt.len().min(500)]
|
||||
);
|
||||
})
|
||||
.await;
|
||||
async fn session_notification(
|
||||
&self,
|
||||
_args: acp::SessionNotification,
|
||||
) -> acp::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
let (conn, handle_io) =
|
||||
acp::ClientSideConnection::new(NoopClient, outgoing, incoming, |fut| {
|
||||
tokio::task::spawn_local(fut);
|
||||
});
|
||||
tokio::task::spawn_local(handle_io);
|
||||
let _init = tokio::time::timeout(
|
||||
Duration::from_secs(20),
|
||||
conn.initialize(
|
||||
acp::InitializeRequest::new(acp::ProtocolVersion::V1)
|
||||
.client_capabilities(
|
||||
acp::ClientCapabilities::new()
|
||||
.fs(acp::FileSystemCapabilities::new())
|
||||
.terminal(false),
|
||||
)
|
||||
.meta(
|
||||
serde_json::json!(
|
||||
{ "startupHints" : { "nonInteractive" : true,
|
||||
"skipGitStatus" : true, "skipProjectLayout" : true },
|
||||
"clientType" : "test-client", "clientVersion" : "0.0.0-test"
|
||||
}
|
||||
)
|
||||
.as_object()
|
||||
.cloned(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("init timed out")
|
||||
.expect("init failed");
|
||||
conn.authenticate(
|
||||
acp::AuthenticateRequest::new(acp::AuthMethodId::new("xai.api_key")).meta(
|
||||
serde_json::json!({ "headless" : true })
|
||||
.as_object()
|
||||
.cloned(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("auth failed");
|
||||
let session = tokio::time::timeout(
|
||||
Duration::from_secs(20),
|
||||
conn.new_session(
|
||||
acp::NewSessionRequest::new(workdir.path().to_path_buf()).meta(
|
||||
serde_json::json!({ "modelId" : "cursor-model" })
|
||||
.as_object()
|
||||
.cloned(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("session/new timed out")
|
||||
.expect("session/new failed");
|
||||
let _prompt = tokio::time::timeout(
|
||||
Duration::from_secs(30),
|
||||
conn.prompt(acp::PromptRequest::new(
|
||||
session.session_id.clone(),
|
||||
vec![acp::ContentBlock::Text(acp::TextContent::new("say hello"))],
|
||||
)),
|
||||
)
|
||||
.await
|
||||
.expect("prompt timed out")
|
||||
.expect("prompt failed");
|
||||
let sys_prompt = server
|
||||
.last_system_prompt()
|
||||
.expect("should have inference request");
|
||||
assert!(
|
||||
sys_prompt.contains("Kigi") || sys_prompt.contains("kigi"),
|
||||
"KIGI_AGENT=kigi should override cursor model's agent_type\nsystem prompt preview: {}",
|
||||
&sys_prompt[..sys_prompt.len().min(500)]
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//! Built-binary end-to-end tests for the grok (kigi-tui) binary.
|
||||
//! Built-binary end-to-end tests for the kigi (kigi-tui) binary.
|
||||
//!
|
||||
//! These tests verify that the built grok binary works end-to-end against a mock
|
||||
//! These tests verify that the built kigi binary works end-to-end against a mock
|
||||
//! inference server. They catch dynamic linking failures (libgit2/OpenSSL),
|
||||
//! session initialization crashes, and protocol regressions.
|
||||
//!
|
||||
//! The tests exercise:
|
||||
//! - **Smoke** (`grok --version`): binary loads without crashing
|
||||
//! - **ACP stdio** (`grok agent stdio`): full protocol lifecycle via ClientSideConnection
|
||||
//! - **Smoke** (`kigi --version`): binary loads without crashing
|
||||
//! - **ACP stdio** (`kigi agent stdio`): full protocol lifecycle via ClientSideConnection
|
||||
//!
|
||||
//! Tests are `#[ignore]`d by default — they require a pre-built binary.
|
||||
//!
|
||||
@@ -17,7 +17,7 @@
|
||||
//!
|
||||
//! In CI, set `KIGI_BINARY` to point at the release artifact:
|
||||
//! ```bash
|
||||
//! KIGI_BINARY=./artifacts/grok-0.1.159-linux-x86_64 \
|
||||
//! KIGI_BINARY=./artifacts/kigi-0.1.159-linux-x86_64 \
|
||||
//! cargo test -p kigi-shell --test test_built_binary_e2e -- --ignored
|
||||
//! ```
|
||||
|
||||
@@ -50,9 +50,9 @@ async fn single_model_server(model: &str, backend: &str) -> MockInferenceServer
|
||||
.expect("start mock server")
|
||||
}
|
||||
|
||||
async fn grok_build_server() -> MockInferenceServer {
|
||||
async fn kigi_server() -> MockInferenceServer {
|
||||
MockInferenceServer::start_with_models(vec![
|
||||
MockModelEntry::with_agent_type("grok-4.5", "grok-build")
|
||||
MockModelEntry::with_agent_type("kigi-4.5", "kigi")
|
||||
.with_api_backend("responses")
|
||||
.with_supports_backend_search(true),
|
||||
])
|
||||
@@ -112,7 +112,7 @@ async fn run_headless_with_env(
|
||||
env: &[(&str, &str)],
|
||||
) -> HeadlessResult {
|
||||
let home = tempfile::TempDir::new().expect("create temp home");
|
||||
let mut cmd = tokio::process::Command::new(grok_binary());
|
||||
let mut cmd = tokio::process::Command::new(kigi_binary());
|
||||
cmd.args(args)
|
||||
.current_dir(cwd)
|
||||
.stdin(std::process::Stdio::null())
|
||||
@@ -133,7 +133,7 @@ async fn run_headless_with_env(
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn test_version_exits_zero() {
|
||||
let binary = grok_binary();
|
||||
let binary = kigi_binary();
|
||||
let output = Command::new(&binary)
|
||||
.arg("--version")
|
||||
.output()
|
||||
@@ -141,7 +141,7 @@ async fn test_version_exits_zero() {
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"grok --version failed (exit {:?}):\n{}",
|
||||
"kigi --version failed (exit {:?}):\n{}",
|
||||
output.status.code(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
@@ -153,7 +153,7 @@ async fn test_version_exits_zero() {
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn test_version_with_crash_handler_exits_zero() {
|
||||
let binary = grok_binary();
|
||||
let binary = kigi_binary();
|
||||
let output = Command::new(&binary)
|
||||
.arg("--version")
|
||||
.env("KIGI_CRASH_HANDLER", "1")
|
||||
@@ -162,7 +162,7 @@ async fn test_version_with_crash_handler_exits_zero() {
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"grok --version with KIGI_CRASH_HANDLER=1 failed (exit {:?}):\n{}",
|
||||
"kigi --version with KIGI_CRASH_HANDLER=1 failed (exit {:?}):\n{}",
|
||||
output.status.code(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
@@ -183,7 +183,7 @@ async fn test_headless_session_in_git_repo() {
|
||||
let workdir = git_workdir();
|
||||
let result = run_headless(&server, &["-p", "say hello", "--yolo"], workdir.path()).await;
|
||||
|
||||
assert_headless_success(&result, "grok -p in git repo", Some(&server));
|
||||
assert_headless_success(&result, "kigi -p in git repo", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
assert!(
|
||||
server.request_count() > 0,
|
||||
@@ -197,7 +197,7 @@ async fn test_headless_session_in_git_repo() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Verify grok works in a non-git directory (exercises the fallback codepath
|
||||
/// Verify kigi works in a non-git directory (exercises the fallback codepath
|
||||
/// where libgit2 discovers there's no repo instead of initializing one).
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
@@ -210,14 +210,14 @@ async fn test_headless_session_in_non_git_dir() {
|
||||
|
||||
let result = run_headless(&server, &["-p", "say hello", "--yolo"], workdir.path()).await;
|
||||
|
||||
assert_headless_success(&result, "grok -p in non-git dir", Some(&server));
|
||||
assert_headless_success(&result, "kigi -p in non-git dir", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn test_headless_tools_allowlist_keeps_enabled_web_tools() {
|
||||
let server = grok_build_server().await;
|
||||
let server = kigi_server().await;
|
||||
server.preset_allow_access();
|
||||
let workdir = git_workdir();
|
||||
|
||||
@@ -235,7 +235,7 @@ async fn test_headless_tools_allowlist_keeps_enabled_web_tools() {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_headless_success(&result, "grok -p --tools with web tools", Some(&server));
|
||||
assert_headless_success(&result, "kigi -p --tools with web tools", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
let names = inference_tool_names(&server);
|
||||
for expected in ["read_file", "grep", "list_dir", "web_search", "web_fetch"] {
|
||||
@@ -275,7 +275,7 @@ async fn test_headless_tools_allowlist_keeps_enabled_web_tools() {
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn test_headless_tools_allowlist_does_not_fail_open_for_disabled_web_fetch() {
|
||||
let server = grok_build_server().await;
|
||||
let server = kigi_server().await;
|
||||
server.set_settings(serde_json::json!({
|
||||
"allow_access": true,
|
||||
"web_fetch_enabled": false,
|
||||
@@ -298,7 +298,7 @@ async fn test_headless_tools_allowlist_does_not_fail_open_for_disabled_web_fetch
|
||||
|
||||
assert_headless_success(
|
||||
&result,
|
||||
"grok -p --tools with disabled web_fetch",
|
||||
"kigi -p --tools with disabled web_fetch",
|
||||
Some(&server),
|
||||
);
|
||||
assert_no_crashes(&result.stderr);
|
||||
@@ -315,7 +315,7 @@ async fn test_headless_tools_allowlist_does_not_fail_open_for_disabled_web_fetch
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn test_headless_terminal_only_allowlist_is_foreground_only() {
|
||||
let server = grok_build_server().await;
|
||||
let server = kigi_server().await;
|
||||
let workdir = git_workdir();
|
||||
|
||||
let result = run_headless(
|
||||
@@ -325,7 +325,7 @@ async fn test_headless_terminal_only_allowlist_is_foreground_only() {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_headless_success(&result, "grok -p --tools run_terminal_cmd", Some(&server));
|
||||
assert_headless_success(&result, "kigi -p --tools run_terminal_cmd", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
let request = inference_request(&server);
|
||||
let terminal = request["tools"]
|
||||
@@ -386,7 +386,7 @@ async fn test_headless_free_usage_exhausted_prints_paywall_message() {
|
||||
assert_no_crashes(&result.stderr);
|
||||
let combined = format!("{}\n{}", result.stdout, result.stderr);
|
||||
assert!(
|
||||
combined.contains("reached your free Grok Build usage limit"),
|
||||
combined.contains("reached your free Kigi usage limit"),
|
||||
"expected the free-usage paywall message\nstdout:\n{}\nstderr tail:\n{}",
|
||||
result.stdout,
|
||||
stderr_tail(&result.stderr, 1000)
|
||||
@@ -421,7 +421,7 @@ async fn test_headless_streaming_json_output() {
|
||||
|
||||
assert_headless_success(
|
||||
&result,
|
||||
"grok -p --output-format streaming-json",
|
||||
"kigi -p --output-format streaming-json",
|
||||
Some(&server),
|
||||
);
|
||||
assert_no_crashes(&result.stderr);
|
||||
@@ -468,19 +468,19 @@ async fn test_headless_streaming_json_output() {
|
||||
async fn test_headless_json_reports_server_cost() {
|
||||
use kigi_test_support::scripted::SseEvent;
|
||||
|
||||
let server = single_model_server("grok-4.5", "chat_completions").await;
|
||||
let server = single_model_server("kigi-4.5", "chat_completions").await;
|
||||
let chunk = |body: serde_json::Value| SseEvent::data(body.to_string());
|
||||
server.enqueue_response(
|
||||
"/v1/chat/completions",
|
||||
kigi_test_support::scripted::ScriptedResponse::sse(vec![
|
||||
chunk(serde_json::json!({
|
||||
"id": "chatcmpl-cost", "object": "chat.completion.chunk", "created": 0,
|
||||
"model": "grok-4.5",
|
||||
"model": "kigi-4.5",
|
||||
"choices": [{ "index": 0, "delta": { "content": "4" }, "finish_reason": "stop" }]
|
||||
})),
|
||||
chunk(serde_json::json!({
|
||||
"id": "chatcmpl-cost", "object": "chat.completion.chunk", "created": 0,
|
||||
"model": "grok-4.5", "choices": [],
|
||||
"model": "kigi-4.5", "choices": [],
|
||||
"usage": {
|
||||
"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15,
|
||||
"cost_in_usd_ticks": 1_234_500_000_i64
|
||||
@@ -498,7 +498,7 @@ async fn test_headless_json_reports_server_cost() {
|
||||
"what is 2+2",
|
||||
"--yolo",
|
||||
"--model",
|
||||
"grok-4.5",
|
||||
"kigi-4.5",
|
||||
"--max-turns",
|
||||
"1",
|
||||
"--output-format",
|
||||
@@ -508,7 +508,7 @@ async fn test_headless_json_reports_server_cost() {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_headless_success(&result, "grok -p (scripted cost)", Some(&server));
|
||||
assert_headless_success(&result, "kigi -p (scripted cost)", Some(&server));
|
||||
let output = parse_stdout_json(&result);
|
||||
assert_eq!(output["total_cost_usd"], 0.12345);
|
||||
assert_eq!(output["total_cost_usd_ticks"], 1_234_500_000_i64);
|
||||
@@ -528,7 +528,7 @@ async fn test_headless_json_reports_server_cost() {
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn test_headless_json_reports_usage_on_max_turns() {
|
||||
let server = single_model_server("grok-4.5", "chat_completions").await;
|
||||
let server = single_model_server("kigi-4.5", "chat_completions").await;
|
||||
server.enqueue_response(
|
||||
"/v1/chat/completions",
|
||||
kigi_test_support::scripted::ScriptedResponse::sse(
|
||||
@@ -537,7 +537,7 @@ async fn test_headless_json_reports_usage_on_max_turns() {
|
||||
"call-1",
|
||||
"read_file",
|
||||
r#"{"path":"README.md"}"#,
|
||||
"grok-4.5",
|
||||
"kigi-4.5",
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -550,7 +550,7 @@ async fn test_headless_json_reports_usage_on_max_turns() {
|
||||
"read the readme",
|
||||
"--yolo",
|
||||
"--model",
|
||||
"grok-4.5",
|
||||
"kigi-4.5",
|
||||
"--max-turns",
|
||||
"1",
|
||||
"--output-format",
|
||||
@@ -569,7 +569,7 @@ async fn test_headless_json_reports_usage_on_max_turns() {
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn test_headless_streaming_json_usage() {
|
||||
let server = single_model_server("grok-4.5", "chat_completions").await;
|
||||
let server = single_model_server("kigi-4.5", "chat_completions").await;
|
||||
let workdir = git_workdir();
|
||||
let result = run_headless(
|
||||
&server,
|
||||
@@ -578,7 +578,7 @@ async fn test_headless_streaming_json_usage() {
|
||||
"say hello",
|
||||
"--yolo",
|
||||
"--model",
|
||||
"grok-4.5",
|
||||
"kigi-4.5",
|
||||
"--output-format",
|
||||
"streaming-json",
|
||||
],
|
||||
@@ -604,7 +604,7 @@ async fn test_headless_streaming_json_usage() {
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn headless_json_schema_chat_completions_uses_response_format() {
|
||||
let server = single_model_server("grok-4.5", "chat_completions").await;
|
||||
let server = single_model_server("kigi-4.5", "chat_completions").await;
|
||||
server.set_response(r#"{"name":"Alice","age":30}"#);
|
||||
|
||||
let workdir = git_workdir();
|
||||
@@ -615,7 +615,7 @@ async fn headless_json_schema_chat_completions_uses_response_format() {
|
||||
"extract name and age",
|
||||
"--yolo",
|
||||
"--model",
|
||||
"grok-4.5",
|
||||
"kigi-4.5",
|
||||
"--json-schema",
|
||||
r#"{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"}},"required":["name","age"],"additionalProperties":false}"#,
|
||||
"--max-turns",
|
||||
@@ -627,7 +627,7 @@ async fn headless_json_schema_chat_completions_uses_response_format() {
|
||||
|
||||
assert_headless_success(
|
||||
&result,
|
||||
"grok -p --json-schema (chat_completions)",
|
||||
"kigi -p --json-schema (chat_completions)",
|
||||
Some(&server),
|
||||
);
|
||||
assert_no_crashes(&result.stderr);
|
||||
@@ -663,7 +663,7 @@ async fn headless_json_schema_chat_completions_uses_response_format() {
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn headless_json_schema_responses_uses_text_format() {
|
||||
let server = single_model_server("grok-4.5", "responses").await;
|
||||
let server = single_model_server("kigi-4.5", "responses").await;
|
||||
server.set_response(r#"{"name":"Alice","age":30}"#);
|
||||
|
||||
let workdir = git_workdir();
|
||||
@@ -674,7 +674,7 @@ async fn headless_json_schema_responses_uses_text_format() {
|
||||
"extract name and age",
|
||||
"--yolo",
|
||||
"--model",
|
||||
"grok-4.5",
|
||||
"kigi-4.5",
|
||||
"--json-schema",
|
||||
NAME_AGE_SCHEMA,
|
||||
"--max-turns",
|
||||
@@ -684,7 +684,7 @@ async fn headless_json_schema_responses_uses_text_format() {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_headless_success(&result, "grok -p --json-schema (responses)", Some(&server));
|
||||
assert_headless_success(&result, "kigi -p --json-schema (responses)", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
|
||||
let output = parse_stdout_json(&result);
|
||||
@@ -740,7 +740,7 @@ async fn headless_json_schema_messages_backend_uses_structured_output_tool() {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_headless_success(&result, "grok -p --json-schema (messages)", Some(&server));
|
||||
assert_headless_success(&result, "kigi -p --json-schema (messages)", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
|
||||
let output = parse_stdout_json(&result);
|
||||
@@ -824,7 +824,7 @@ async fn headless_json_schema_messages_validates_text_when_tool_not_called() {
|
||||
|
||||
assert_headless_success(
|
||||
&result,
|
||||
"grok -p --json-schema (messages, text)",
|
||||
"kigi -p --json-schema (messages, text)",
|
||||
Some(&server),
|
||||
);
|
||||
assert_no_crashes(&result.stderr);
|
||||
@@ -871,7 +871,7 @@ async fn headless_json_schema_messages_retries_on_schema_violation() {
|
||||
|
||||
assert_headless_success(
|
||||
&result,
|
||||
"grok -p --json-schema (messages, retry)",
|
||||
"kigi -p --json-schema (messages, retry)",
|
||||
Some(&server),
|
||||
);
|
||||
assert_no_crashes(&result.stderr);
|
||||
@@ -887,7 +887,7 @@ async fn headless_json_schema_messages_retries_on_schema_violation() {
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn invalid_json_schema_disables_structured_output_and_surfaces_error() {
|
||||
let server = single_model_server("grok-4.5", "chat_completions").await;
|
||||
let server = single_model_server("kigi-4.5", "chat_completions").await;
|
||||
server.set_response(r#"{"name":"Alice","age":30}"#);
|
||||
|
||||
let workdir = git_workdir();
|
||||
@@ -898,7 +898,7 @@ async fn invalid_json_schema_disables_structured_output_and_surfaces_error() {
|
||||
"extract name and age",
|
||||
"--yolo",
|
||||
"--model",
|
||||
"grok-4.5",
|
||||
"kigi-4.5",
|
||||
// Valid JSON object, but `pattern` is an invalid regex → schema
|
||||
// compilation (`jsonschema::validator_for`) fails.
|
||||
"--json-schema",
|
||||
@@ -912,7 +912,7 @@ async fn invalid_json_schema_disables_structured_output_and_surfaces_error() {
|
||||
|
||||
assert_headless_success(
|
||||
&result,
|
||||
"grok -p --json-schema (invalid schema)",
|
||||
"kigi -p --json-schema (invalid schema)",
|
||||
Some(&server),
|
||||
);
|
||||
assert_no_crashes(&result.stderr);
|
||||
@@ -950,9 +950,9 @@ async fn invalid_json_schema_disables_structured_output_and_surfaces_error() {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ACP stdio tests (grok agent stdio)
|
||||
// ACP stdio tests (kigi agent stdio)
|
||||
//
|
||||
// These test the agent as a server: spawn `grok agent stdio`, speak the full
|
||||
// These test the agent as a server: spawn `kigi agent stdio`, speak the full
|
||||
// ACP protocol over pipes, verify the lifecycle works end-to-end.
|
||||
// ============================================================================
|
||||
|
||||
@@ -965,7 +965,7 @@ async fn test_stdio_full_session_lifecycle() {
|
||||
with_local_set(|| async {
|
||||
let server = MockInferenceServer::start().await.expect("start mock server");
|
||||
let workdir = git_workdir();
|
||||
let client = GrokStdioClient::spawn(&server, workdir.path()).await;
|
||||
let client = KigiStdioClient::spawn(&server, workdir.path()).await;
|
||||
|
||||
// Initialize and authenticate
|
||||
let init_resp = client.initialize_with_timeout().await;
|
||||
@@ -1001,7 +1001,7 @@ async fn test_stdio_full_session_lifecycle() {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Verify that x.ai/session/close frees the session.
|
||||
/// Verify that kigi/session/close frees the session.
|
||||
/// Creates a session, closes it via ext_method, then verifies session/info
|
||||
/// returns an empty response (session no longer exists).
|
||||
#[tokio::test]
|
||||
@@ -1012,7 +1012,7 @@ async fn test_stdio_session_close() {
|
||||
.await
|
||||
.expect("start mock server");
|
||||
let workdir = git_workdir();
|
||||
let client = GrokStdioClient::spawn(&server, workdir.path()).await;
|
||||
let client = KigiStdioClient::spawn(&server, workdir.path()).await;
|
||||
|
||||
client.initialize_with_timeout().await;
|
||||
let session_id = client.create_session_with_timeout(workdir.path()).await;
|
||||
@@ -1020,7 +1020,7 @@ async fn test_stdio_session_close() {
|
||||
// Session should be alive — session/info returns data with sessionId
|
||||
let info_resp = client
|
||||
.ext_method(
|
||||
"x.ai/session/info",
|
||||
"kigi/session/info",
|
||||
serde_json::json!({ "sessionId": session_id.0.as_ref() }),
|
||||
)
|
||||
.await;
|
||||
@@ -1039,7 +1039,7 @@ async fn test_stdio_session_close() {
|
||||
// Close the session
|
||||
let close_resp = client
|
||||
.ext_method(
|
||||
"x.ai/session/close",
|
||||
"kigi/session/close",
|
||||
serde_json::json!({ "sessionId": session_id.0.as_ref() }),
|
||||
)
|
||||
.await;
|
||||
@@ -1053,7 +1053,7 @@ async fn test_stdio_session_close() {
|
||||
// Session should be gone — session/info returns empty result (no sessionId)
|
||||
let info_after = client
|
||||
.ext_method(
|
||||
"x.ai/session/info",
|
||||
"kigi/session/info",
|
||||
serde_json::json!({ "sessionId": session_id.0.as_ref() }),
|
||||
)
|
||||
.await;
|
||||
@@ -1074,7 +1074,7 @@ async fn test_stdio_prompt_then_immediate_load_session() {
|
||||
with_local_set(|| async {
|
||||
let server = MockInferenceServer::start().await.expect("start mock server");
|
||||
let workdir = git_workdir();
|
||||
let mut writer = GrokStdioClient::spawn(&server, workdir.path()).await;
|
||||
let mut writer = KigiStdioClient::spawn(&server, workdir.path()).await;
|
||||
|
||||
let init_resp = writer.initialize_with_timeout().await;
|
||||
assert!(
|
||||
@@ -1095,7 +1095,7 @@ async fn test_stdio_prompt_then_immediate_load_session() {
|
||||
let shared_home = writer.take_home();
|
||||
drop(writer);
|
||||
|
||||
let reader = GrokStdioClient::spawn_with_home(&server, workdir.path(), shared_home).await;
|
||||
let reader = KigiStdioClient::spawn_with_home(&server, workdir.path(), shared_home).await;
|
||||
reader.initialize_with_timeout().await;
|
||||
let _ = reader
|
||||
.load_session_with_timeout(&session_id, workdir.path())
|
||||
@@ -1252,7 +1252,7 @@ async fn test_stdio_xcode_escaped_slash_methods_get_responses() {
|
||||
// ── Config test harness ─────────────────────────────────────────────────────
|
||||
|
||||
/// Isolated headless run with a custom `~/.kigi/`. Clean env (no leaked
|
||||
/// host credentials). Write config files into `grok_dir()` before `run()`.
|
||||
/// host credentials). Write config files into `kigi_dir()` before `run()`.
|
||||
struct ConfigTestHarness {
|
||||
home: tempfile::TempDir,
|
||||
workdir: tempfile::TempDir,
|
||||
@@ -1277,7 +1277,7 @@ impl ConfigTestHarness {
|
||||
}
|
||||
}
|
||||
|
||||
fn grok_dir(&self) -> std::path::PathBuf {
|
||||
fn kigi_dir(&self) -> std::path::PathBuf {
|
||||
self.home.path().join(".kigi")
|
||||
}
|
||||
|
||||
@@ -1287,7 +1287,7 @@ impl ConfigTestHarness {
|
||||
}
|
||||
|
||||
async fn run(&self) -> HeadlessResult {
|
||||
let mut cmd = tokio::process::Command::new(grok_binary());
|
||||
let mut cmd = tokio::process::Command::new(kigi_binary());
|
||||
cmd.args(["-p", "say hello", "--yolo"])
|
||||
.current_dir(self.workdir.path())
|
||||
.stdin(std::process::Stdio::null())
|
||||
@@ -1296,10 +1296,10 @@ impl ConfigTestHarness {
|
||||
.kill_on_drop(true)
|
||||
.env_clear()
|
||||
.env("HOME", self.home.path())
|
||||
// Windows resolves `~` via USERPROFILE, not HOME — pin the grok
|
||||
// Windows resolves `~` via USERPROFILE, not HOME — pin the kigi
|
||||
// home explicitly so the sandbox holds on all platforms (see
|
||||
// `test_env_cmd_tokio`).
|
||||
.env("KIGI_SHARE_DIR", self.grok_dir())
|
||||
.env("KIGI_SHARE_DIR", self.kigi_dir())
|
||||
.env("PATH", std::env::var("PATH").unwrap_or_default());
|
||||
for (k, v) in &self.env {
|
||||
cmd.env(k, v);
|
||||
@@ -1310,14 +1310,14 @@ impl ConfigTestHarness {
|
||||
|
||||
// ── Enterprise managed config tests ────────────────────────────────────────
|
||||
|
||||
/// Enterprise BYOK: managed_config.toml overrides grok-build with a custom
|
||||
/// Enterprise BYOK: managed_config.toml overrides kigi with a custom
|
||||
/// endpoint + env_key. Mock rejects unauthenticated requests with 401.
|
||||
/// Regression guard for the 0.1.220 authentication regression.
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn test_headless_managed_config_byok_sends_authorized_requests() {
|
||||
let server = MockInferenceServer::start_with_required_auth(
|
||||
vec![MockModelEntry::new("grok-4.5")],
|
||||
vec![MockModelEntry::new("kigi-4.5")],
|
||||
"test-byok-secret-token",
|
||||
)
|
||||
.await
|
||||
@@ -1325,7 +1325,7 @@ async fn test_headless_managed_config_byok_sends_authorized_requests() {
|
||||
|
||||
let mut h = ConfigTestHarness::new(&server);
|
||||
std::fs::write(
|
||||
h.grok_dir().join("managed_config.toml"),
|
||||
h.kigi_dir().join("managed_config.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[endpoints]
|
||||
@@ -1337,10 +1337,10 @@ api_backend = "responses"
|
||||
base_url = "{url}"
|
||||
context_window = 500000
|
||||
env_key = "KIGI_TEST_BYOK_TOKEN"
|
||||
model = "grok-4.5"
|
||||
model = "kigi-4.5"
|
||||
|
||||
[models]
|
||||
default = "grok-4.5"
|
||||
default = "kigi-4.5"
|
||||
"#,
|
||||
url = server.url()
|
||||
),
|
||||
@@ -1369,7 +1369,7 @@ default = "grok-4.5"
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn headless_reasoning_efforts_payload_parses_and_legacy_effort_rides_wire() {
|
||||
let server = MockInferenceServer::start_with_models(vec![
|
||||
MockModelEntry::new("grok-4.5")
|
||||
MockModelEntry::new("kigi-4.5")
|
||||
.with_api_backend("chat_completions")
|
||||
.with_supports_reasoning_effort(true)
|
||||
.with_reasoning_effort("xhigh")
|
||||
@@ -1390,7 +1390,7 @@ async fn headless_reasoning_efforts_payload_parses_and_legacy_effort_rides_wire(
|
||||
"hi",
|
||||
"--yolo",
|
||||
"--model",
|
||||
"grok-4.5",
|
||||
"kigi-4.5",
|
||||
"--max-turns",
|
||||
"1",
|
||||
],
|
||||
@@ -1398,7 +1398,7 @@ async fn headless_reasoning_efforts_payload_parses_and_legacy_effort_rides_wire(
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_headless_success(&result, "grok -p reasoning_efforts list", Some(&server));
|
||||
assert_headless_success(&result, "kigi -p reasoning_efforts list", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
|
||||
// The legacy effort scalar rides the chat-completions request unchanged.
|
||||
@@ -1527,7 +1527,7 @@ async fn test_headless_timeout_exit_kills_pending_background_task() {
|
||||
|
||||
assert_headless_success(
|
||||
&result,
|
||||
"grok -p with pending background task",
|
||||
"kigi -p with pending background task",
|
||||
Some(&server),
|
||||
);
|
||||
assert_no_crashes(&result.stderr);
|
||||
@@ -1565,7 +1565,7 @@ async fn test_headless_no_wait_exit_kills_background_task() {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_headless_success(&result, "grok -p --no-wait-for-background", Some(&server));
|
||||
assert_headless_success(&result, "kigi -p --no-wait-for-background", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
|
||||
let pid = read_task_pid(&pid_file);
|
||||
@@ -1630,7 +1630,7 @@ async fn test_headless_waits_for_short_background_task_and_exits_clean() {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_headless_success(&result, "grok -p with short background task", Some(&server));
|
||||
assert_headless_success(&result, "kigi -p with short background task", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
assert!(
|
||||
marker.exists(),
|
||||
|
||||
@@ -70,7 +70,7 @@ async fn update_config_does_not_leak_requirements_into_user_config() {
|
||||
// --- Act ---
|
||||
// Simulate an unrelated config write (e.g. persisting a model preference).
|
||||
kigi_shell::util::config::update_config(|cfg| {
|
||||
cfg.models.default = Some("grok-3".to_string());
|
||||
cfg.models.default = Some("kigi-3".to_string());
|
||||
})
|
||||
.await
|
||||
.expect("update_config should succeed");
|
||||
@@ -89,7 +89,7 @@ async fn update_config_does_not_leak_requirements_into_user_config() {
|
||||
);
|
||||
|
||||
// Also verify the unrelated write succeeded.
|
||||
assert_eq!(user_cfg.models.default.as_deref(), Some("grok-3"));
|
||||
assert_eq!(user_cfg.models.default.as_deref(), Some("kigi-3"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! End-to-end tests for the `--debug` firehose file logging.
|
||||
//!
|
||||
//! Runs the built grok binary against the mock inference server with a
|
||||
//! Runs the built kigi binary against the mock inference server with a
|
||||
//! caller-owned `$KIGI_SHARE_DIR`, then inspects `~/.kigi/debug/`:
|
||||
//! - the `--debug` FLAG drives the firehose end to end through the master switch:
|
||||
//! a live `agent` session launched with `--debug` writes a non-empty per-session
|
||||
@@ -68,7 +68,7 @@ fn firehose_txt_files(home: &Path) -> Vec<PathBuf> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Build a headless `grok -p` command with a pinned `$KIGI_SHARE_DIR` so the firehose
|
||||
/// Build a headless `kigi -p` command with a pinned `$KIGI_SHARE_DIR` so the firehose
|
||||
/// lands under `<home>/.kigi/debug`. Firehose env knobs are cleared so the test
|
||||
/// is hermetic regardless of the developer's shell.
|
||||
fn debug_cmd(
|
||||
@@ -77,7 +77,7 @@ fn debug_cmd(
|
||||
workdir: &Path,
|
||||
extra: &[&str],
|
||||
) -> tokio::process::Command {
|
||||
let mut cmd = tokio::process::Command::new(grok_binary());
|
||||
let mut cmd = tokio::process::Command::new(kigi_binary());
|
||||
cmd.args(["-p", "say hi", "--yolo", "--output-format", "json"])
|
||||
.args(extra)
|
||||
.arg("--cwd")
|
||||
@@ -99,9 +99,9 @@ fn debug_cmd(
|
||||
|
||||
/// Poll up to 50×100ms for the per-session firehose at `path` to become non-empty
|
||||
/// (its worker flushes asynchronously while the agent process stays alive), then
|
||||
/// assert it carries first-party (`xai_grok`) content. Panics with the captured
|
||||
/// assert it carries first-party (`xai_kigi`) content. Panics with the captured
|
||||
/// stderr tail if it never fills. Shared by the live-agent tests.
|
||||
async fn read_session_firehose_when_ready(path: &Path, client: &GrokStdioClient) -> String {
|
||||
async fn read_session_firehose_when_ready(path: &Path, client: &KigiStdioClient) -> String {
|
||||
let mut content = None;
|
||||
for _ in 0..50 {
|
||||
if let Ok(text) = std::fs::read_to_string(path)
|
||||
@@ -121,7 +121,7 @@ async fn read_session_firehose_when_ready(path: &Path, client: &GrokStdioClient)
|
||||
// The firehose filter routes first-party crate logs here; assert that rather
|
||||
// than a bare non-empty check.
|
||||
assert!(
|
||||
content.contains("xai_grok"),
|
||||
content.contains("xai_kigi"),
|
||||
"session firehose {path:?} should contain first-party logs, got {} bytes",
|
||||
content.len()
|
||||
);
|
||||
@@ -131,7 +131,7 @@ async fn read_session_firehose_when_ready(path: &Path, client: &GrokStdioClient)
|
||||
/// `--debug` (headless) runs cleanly: arg-parsing + the master switch + tracing
|
||||
/// init don't crash. Per-session routing + content is proven deterministically by
|
||||
/// the live `agent` tests (incl. `debug_flag_master_switch_enables_firehose`); a
|
||||
/// headless `grok -p` client is near-silent, so its lazily-opened firehose may
|
||||
/// headless `kigi -p` client is near-silent, so its lazily-opened firehose may
|
||||
/// legitimately stay empty here — file existence is intentionally not asserted.
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
@@ -145,7 +145,7 @@ async fn debug_flag_enables_firehose_without_crashing() {
|
||||
let cmd = debug_cmd(&server, home.path(), workdir.path(), &["--debug"]);
|
||||
let result = run_headless_with_cmd(cmd).await;
|
||||
|
||||
assert_headless_success(&result, "grok --debug headless", Some(&server));
|
||||
assert_headless_success(&result, "kigi --debug headless", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ async fn no_debug_flag_writes_no_debug_dir() {
|
||||
let cmd = debug_cmd(&server, home.path(), workdir.path(), &[]);
|
||||
let result = run_headless_with_cmd(cmd).await;
|
||||
|
||||
assert_headless_success(&result, "grok headless (no --debug)", Some(&server));
|
||||
assert_headless_success(&result, "kigi headless (no --debug)", Some(&server));
|
||||
assert!(
|
||||
firehose_txt_files(home.path()).is_empty(),
|
||||
"no firehose *.txt expected without --debug, found: {:?}",
|
||||
@@ -186,7 +186,7 @@ async fn agent_session_writes_named_session_file() {
|
||||
let kigi_home = home.path().join(".kigi");
|
||||
let kigi_home_str = kigi_home.to_string_lossy().into_owned();
|
||||
|
||||
let client = GrokStdioClient::spawn_with_home_and_env(
|
||||
let client = KigiStdioClient::spawn_with_home_and_env(
|
||||
&server,
|
||||
workdir.path(),
|
||||
home,
|
||||
@@ -235,11 +235,11 @@ async fn debug_flag_master_switch_enables_firehose() {
|
||||
let kigi_home = home.path().join(".kigi");
|
||||
let kigi_home_str = kigi_home.to_string_lossy().into_owned();
|
||||
|
||||
// Drive `grok --debug agent stdio`: the master switch (which runs before
|
||||
// Drive `kigi --debug agent stdio`: the master switch (which runs before
|
||||
// the agent dispatch) must be what enables the firehose — NOT a direct
|
||||
// KIGI_DEBUG_LOG env. The spawn helper clears inherited firehose toggles,
|
||||
// so the `--debug` flag is the only thing that can enable logging here.
|
||||
let client = GrokStdioClient::spawn_with_home_env_and_args(
|
||||
let client = KigiStdioClient::spawn_with_home_env_and_args(
|
||||
&server,
|
||||
workdir.path(),
|
||||
home,
|
||||
@@ -292,7 +292,7 @@ async fn debug_file_flag_writes_single_file_and_bypasses_routing() {
|
||||
);
|
||||
let result = run_headless_with_cmd(cmd).await;
|
||||
|
||||
assert_headless_success(&result, "grok --debug-file", Some(&server));
|
||||
assert_headless_success(&result, "kigi --debug-file", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
assert!(
|
||||
explicit.exists(),
|
||||
@@ -310,7 +310,7 @@ async fn debug_file_flag_writes_single_file_and_bypasses_routing() {
|
||||
/// `KIGI_LOG_FILE=<path>` (no `--debug`) writes that exact file (back-compat).
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary; run with --ignored
|
||||
async fn grok_log_file_explicit_path_is_written() {
|
||||
async fn kigi_log_file_explicit_path_is_written() {
|
||||
let server = MockInferenceServer::start()
|
||||
.await
|
||||
.expect("start mock server");
|
||||
@@ -322,7 +322,7 @@ async fn grok_log_file_explicit_path_is_written() {
|
||||
cmd.env("KIGI_LOG_FILE", &custom);
|
||||
let result = run_headless_with_cmd(cmd).await;
|
||||
|
||||
assert_headless_success(&result, "grok KIGI_LOG_FILE=path", Some(&server));
|
||||
assert_headless_success(&result, "kigi KIGI_LOG_FILE=path", Some(&server));
|
||||
assert_no_crashes(&result.stderr);
|
||||
assert!(
|
||||
custom.exists(),
|
||||
|
||||
@@ -518,7 +518,7 @@ async fn doomed_then_reasoning_only_empty_coexist() {
|
||||
|
||||
/// `[doom_loop_recovery] enabled = true` in `config.toml` reaches the wire
|
||||
/// through the real binary: the session TURN request (marked by
|
||||
/// `x-grok-turn-idx`) carries the opt-in header. Aux side-queries the binary
|
||||
/// `x-kigi-turn-idx`) carries the opt-in header. Aux side-queries the binary
|
||||
/// also fires at `/v1/responses` (e.g. session-title generation) must NOT
|
||||
/// carry it — they collect without the actor's retry loop, so an armed
|
||||
/// abort there could only fail them, never resample. The recovery behavior
|
||||
@@ -546,7 +546,7 @@ async fn headless_config_enables_doom_loop_check_header() {
|
||||
)
|
||||
.expect("write config.toml");
|
||||
|
||||
let mut cmd = tokio::process::Command::new(kigi_test_support::grok_binary());
|
||||
let mut cmd = tokio::process::Command::new(kigi_test_support::kigi_binary());
|
||||
cmd.args(["-p", "say hi", "--yolo", "--output-format", "json"])
|
||||
.arg("--cwd")
|
||||
.arg(workdir.path())
|
||||
@@ -568,11 +568,11 @@ async fn headless_config_enables_doom_loop_check_header() {
|
||||
.iter()
|
||||
.filter(|e| e.method == "POST" && e.path.contains("/responses"))
|
||||
.collect();
|
||||
// The session turn carries `x-grok-turn-idx`; aux side-queries (session
|
||||
// The session turn carries `x-kigi-turn-idx`; aux side-queries (session
|
||||
// title, etc.) do not.
|
||||
let (turns, aux): (Vec<_>, Vec<_>) = responses_posts
|
||||
.into_iter()
|
||||
.partition(|e| e.header("x-grok-turn-idx").is_some());
|
||||
.partition(|e| e.header("x-kigi-turn-idx").is_some());
|
||||
assert!(
|
||||
!turns.is_empty(),
|
||||
"no session turn POST /v1/responses logged; requests:\n{}",
|
||||
@@ -580,7 +580,7 @@ async fn headless_config_enables_doom_loop_check_header() {
|
||||
);
|
||||
for turn in turns {
|
||||
assert_eq!(
|
||||
turn.header("x-grok-doom-loop-check"),
|
||||
turn.header("x-kigi-doom-loop-check"),
|
||||
Some("true"),
|
||||
"[doom_loop_recovery] enabled must reach the turn request header; requests:\n{}",
|
||||
server.request_log_summary()
|
||||
@@ -588,7 +588,7 @@ async fn headless_config_enables_doom_loop_check_header() {
|
||||
}
|
||||
for side_query in aux {
|
||||
assert_eq!(
|
||||
side_query.header("x-grok-doom-loop-check"),
|
||||
side_query.header("x-kigi-doom-loop-check"),
|
||||
None::<&str>,
|
||||
"the session policy must not leak into aux side-query clients; requests:\n{}",
|
||||
server.request_log_summary()
|
||||
|
||||
@@ -29,7 +29,7 @@ async fn responses_api_reasoning_only_is_classified_as_reasoning_only() {
|
||||
"/v1/responses",
|
||||
ScriptedResponse::sse(responses_api_reasoning_only_events(
|
||||
"let me think carefully about this",
|
||||
"grok-test",
|
||||
"kigi-test",
|
||||
)),
|
||||
);
|
||||
let client = create_test_client(&server.url(), ApiBackend::Responses);
|
||||
|
||||
@@ -18,7 +18,7 @@ async fn create_test_session(storage: &JsonlStorageAdapter, session_id: &str, cw
|
||||
cwd: cwd.to_string(),
|
||||
};
|
||||
|
||||
let model_id = acp::ModelId::new("grok-code-fast-1");
|
||||
let model_id = acp::ModelId::new("kigi-code-fast-1");
|
||||
storage.init_session(&info, model_id).await.unwrap();
|
||||
|
||||
// Add some chat messages
|
||||
@@ -58,7 +58,7 @@ async fn test_fork_session_creates_new_session_with_parent_tracking() {
|
||||
|
||||
let options = kigi_shell::session::storage::CopySessionOptions {
|
||||
parent_session_id: Some("source-session-123".to_string()),
|
||||
new_model_id: Some("grok-3".to_string()),
|
||||
new_model_id: Some("kigi-3".to_string()),
|
||||
target_prompt_index: None,
|
||||
..Default::default()
|
||||
};
|
||||
@@ -77,7 +77,7 @@ async fn test_fork_session_creates_new_session_with_parent_tracking() {
|
||||
|
||||
assert_eq!(loaded.summary.info.id.to_string(), "fork-session-456");
|
||||
assert_eq!(loaded.summary.info.cwd, "/new/path");
|
||||
assert_eq!(loaded.summary.current_model_id, acp::ModelId::new("grok-3"));
|
||||
assert_eq!(loaded.summary.current_model_id, acp::ModelId::new("kigi-3"));
|
||||
assert_eq!(
|
||||
loaded.summary.parent_session_id,
|
||||
Some("source-session-123".to_string())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! End-to-end test for the global `[models]` defaults.
|
||||
//!
|
||||
//! Runs the built grok binary against the mock inference server with a
|
||||
//! Runs the built kigi binary against the mock inference server with a
|
||||
//! caller-owned `$KIGI_SHARE_DIR` whose `config.toml` sets every global `[models]`
|
||||
//! default. Asserts the turn succeeds with all of them set and that the
|
||||
//! wire-observable one — `extra_headers` — reaches the `/v1/chat/completions`
|
||||
@@ -47,7 +47,7 @@ stream_tool_calls = true
|
||||
)
|
||||
.expect("write config.toml");
|
||||
|
||||
let mut cmd = tokio::process::Command::new(grok_binary());
|
||||
let mut cmd = tokio::process::Command::new(kigi_binary());
|
||||
cmd.args(["-p", "say hi", "--yolo", "--output-format", "json"])
|
||||
.arg("--cwd")
|
||||
.arg(workdir.path())
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! Scenario (mirrors the field report "clients see `unknown session id` after
|
||||
//! the leader dies"):
|
||||
//!
|
||||
//! 1. Two stdio clients (`grok agent --leader stdio`) share one leader.
|
||||
//! 1. Two stdio clients (`kigi agent --leader stdio`) share one leader.
|
||||
//! 2. Each creates its own session and completes a prompt round-trip.
|
||||
//! 3. The leader is killed with SIGKILL (crash, no graceful shutdown).
|
||||
//! 4. Each client's bridge must reconnect (re-electing / spawning a fresh
|
||||
|
||||
@@ -1024,7 +1024,7 @@ async fn test_session_new_valid_default_model_injected() {
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities {
|
||||
yolo_mode: false,
|
||||
default_model: Some("grok-3-fast".to_string()),
|
||||
default_model: Some("kigi-3-fast".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
@@ -1039,7 +1039,7 @@ async fn test_session_new_valid_default_model_injected() {
|
||||
|
||||
// modelId should be injected from default_model
|
||||
let meta = &json["params"]["_meta"];
|
||||
assert_eq!(meta["modelId"], "grok-3-fast");
|
||||
assert_eq!(meta["modelId"], "kigi-3-fast");
|
||||
|
||||
client.cancel();
|
||||
cancel.cancel();
|
||||
@@ -1216,7 +1216,7 @@ async fn test_two_clients_session_isolation() {
|
||||
|
||||
/// Multi-client model switch: when one TUI client switches models on a
|
||||
/// session shared with another TUI client, the leader must fan the
|
||||
/// `x.ai/session_notification` (carrying the `ModelChanged` update) out
|
||||
/// `kigi/session_notification` (carrying the `ModelChanged` update) out
|
||||
/// to **every** subscriber of that session — not just the invoker — so
|
||||
/// the follower client mirrors the new model in its UI.
|
||||
///
|
||||
@@ -1241,7 +1241,7 @@ async fn test_set_model_broadcasts_to_session_subscribers() {
|
||||
// Two TUIs connected to the same leader, sharing one session.
|
||||
let mut invoker = LeaderClient::connect(
|
||||
sock_path.clone(),
|
||||
"grok-tui-A",
|
||||
"kigi-tui-A",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities::default(),
|
||||
)
|
||||
@@ -1249,7 +1249,7 @@ async fn test_set_model_broadcasts_to_session_subscribers() {
|
||||
.unwrap();
|
||||
let mut follower = LeaderClient::connect(
|
||||
sock_path,
|
||||
"grok-tui-B",
|
||||
"kigi-tui-B",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities::default(),
|
||||
)
|
||||
@@ -1289,7 +1289,7 @@ async fn test_set_model_broadcasts_to_session_subscribers() {
|
||||
// Invoker sends `session/setModel` for the shared session.
|
||||
invoker
|
||||
.send(format!(
|
||||
r#"{{"jsonrpc":"2.0","id":42,"method":"session/setModel","params":{{"sessionId":"{}","modelId":"grok-4"}}}}"#,
|
||||
r#"{{"jsonrpc":"2.0","id":42,"method":"session/setModel","params":{{"sessionId":"{}","modelId":"kigi-4"}}}}"#,
|
||||
shared_sid
|
||||
))
|
||||
.unwrap();
|
||||
@@ -1297,7 +1297,7 @@ async fn test_set_model_broadcasts_to_session_subscribers() {
|
||||
let json: serde_json::Value = serde_json::from_str(&received).unwrap();
|
||||
let setmodel_ns_id = json["id"].as_str().unwrap().to_string();
|
||||
assert_eq!(json["method"], "session/setModel");
|
||||
assert_eq!(json["params"]["modelId"], "grok-4");
|
||||
assert_eq!(json["params"]["modelId"], "kigi-4");
|
||||
|
||||
// Simulate the agent's two outputs for a successful switch:
|
||||
//
|
||||
@@ -1310,12 +1310,12 @@ async fn test_set_model_broadcasts_to_session_subscribers() {
|
||||
// BEFORE the response in `model_switch::apply`, so it must arrive at
|
||||
// each subscriber's recv() first.
|
||||
let broadcast = format!(
|
||||
r#"{{"jsonrpc":"2.0","method":"x.ai/session_notification","params":{{"sessionId":"{}","update":{{"sessionUpdate":"model_changed","model_id":"grok-4","reasoning_effort":"high"}}}}}}"#,
|
||||
r#"{{"jsonrpc":"2.0","method":"kigi/session_notification","params":{{"sessionId":"{}","update":{{"sessionUpdate":"model_changed","model_id":"kigi-4","reasoning_effort":"high"}}}}}}"#,
|
||||
shared_sid
|
||||
);
|
||||
response_tx.send(broadcast.clone()).unwrap();
|
||||
let response = format!(
|
||||
r#"{{"jsonrpc":"2.0","result":{{"meta":{{"model":"grok-4"}}}},"id":"{}"}}"#,
|
||||
r#"{{"jsonrpc":"2.0","result":{{"meta":{{"model":"kigi-4"}}}},"id":"{}"}}"#,
|
||||
setmodel_ns_id
|
||||
);
|
||||
response_tx.send(response).unwrap();
|
||||
@@ -1335,10 +1335,10 @@ async fn test_set_model_broadcasts_to_session_subscribers() {
|
||||
.expect("timeout waiting for broadcast on invoker")
|
||||
.expect("invoker channel closed");
|
||||
let inv1: serde_json::Value = serde_json::from_str(&invoker_msg1).unwrap();
|
||||
assert_eq!(inv1["method"], "x.ai/session_notification");
|
||||
assert_eq!(inv1["method"], "kigi/session_notification");
|
||||
assert_eq!(inv1["params"]["sessionId"], shared_sid);
|
||||
assert_eq!(inv1["params"]["update"]["sessionUpdate"], "model_changed");
|
||||
assert_eq!(inv1["params"]["update"]["model_id"], "grok-4");
|
||||
assert_eq!(inv1["params"]["update"]["model_id"], "kigi-4");
|
||||
assert_eq!(inv1["params"]["update"]["reasoning_effort"], "high");
|
||||
|
||||
let invoker_msg2 = tokio::time::timeout(Duration::from_secs(2), invoker.recv())
|
||||
@@ -1350,7 +1350,7 @@ async fn test_set_model_broadcasts_to_session_subscribers() {
|
||||
inv2["id"], 42,
|
||||
"response id must be restored to the invoker's original"
|
||||
);
|
||||
assert_eq!(inv2["result"]["meta"]["model"], "grok-4");
|
||||
assert_eq!(inv2["result"]["meta"]["model"], "kigi-4");
|
||||
|
||||
// --- Follower: must receive the broadcast (this is the fix — before
|
||||
// this notification existed, the follower's status bar / `/model`
|
||||
@@ -1365,10 +1365,10 @@ async fn test_set_model_broadcasts_to_session_subscribers() {
|
||||
)
|
||||
.expect("follower channel closed");
|
||||
let f: serde_json::Value = serde_json::from_str(&follower_msg).unwrap();
|
||||
assert_eq!(f["method"], "x.ai/session_notification");
|
||||
assert_eq!(f["method"], "kigi/session_notification");
|
||||
assert_eq!(f["params"]["sessionId"], shared_sid);
|
||||
assert_eq!(f["params"]["update"]["sessionUpdate"], "model_changed");
|
||||
assert_eq!(f["params"]["update"]["model_id"], "grok-4");
|
||||
assert_eq!(f["params"]["update"]["model_id"], "kigi-4");
|
||||
assert_eq!(f["params"]["update"]["reasoning_effort"], "high");
|
||||
|
||||
// Follower must NOT see the namespaced setModel response — the
|
||||
@@ -1403,7 +1403,7 @@ async fn test_capabilities_not_injected_into_non_session_new() {
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities {
|
||||
yolo_mode: true,
|
||||
default_model: Some("grok-3-fast".to_string()),
|
||||
default_model: Some("kigi-3-fast".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
@@ -1533,7 +1533,7 @@ async fn test_cancel_prompt_id_meta_passes_through_with_two_clients() {
|
||||
|
||||
let client_a = LeaderClient::connect(
|
||||
sock_path.clone(),
|
||||
"grok-pager",
|
||||
"kigi-pager",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities::default(),
|
||||
)
|
||||
@@ -1541,7 +1541,7 @@ async fn test_cancel_prompt_id_meta_passes_through_with_two_clients() {
|
||||
.unwrap();
|
||||
let client_b = LeaderClient::connect(
|
||||
sock_path,
|
||||
"grok-pager",
|
||||
"kigi-pager",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities::default(),
|
||||
)
|
||||
@@ -1611,14 +1611,14 @@ async fn test_extension_method_roundtrip() {
|
||||
.unwrap();
|
||||
|
||||
// Send an extension method call (e.g., fuzzy search open)
|
||||
let ext_call = r#"{"jsonrpc":"2.0","id":50,"method":"_x.ai/search/fuzzy/open","params":{"sessionId":"sess-123","hidden":false}}"#;
|
||||
let ext_call = r#"{"jsonrpc":"2.0","id":50,"method":"_kigi/search/fuzzy/open","params":{"sessionId":"sess-123","hidden":false}}"#;
|
||||
client.send(ext_call.to_string()).unwrap();
|
||||
|
||||
let received = acp_rx.recv().await.unwrap();
|
||||
let json: serde_json::Value = serde_json::from_str(&received).unwrap();
|
||||
|
||||
// Method should be preserved, ID should be namespaced
|
||||
assert_eq!(json["method"], "_x.ai/search/fuzzy/open");
|
||||
assert_eq!(json["method"], "_kigi/search/fuzzy/open");
|
||||
let namespaced_id = json["id"].as_str().unwrap();
|
||||
assert!(namespaced_id.contains(ID_NAMESPACE_SEP));
|
||||
assert!(namespaced_id.ends_with("|50"));
|
||||
@@ -1795,7 +1795,7 @@ async fn test_session_ownership_cleanup_on_disconnect() {
|
||||
// in sync. Also verifies the eviction was actually sent.
|
||||
let eviction = acp_rx.recv().await.unwrap();
|
||||
let eviction_json: serde_json::Value = serde_json::from_str(&eviction).unwrap();
|
||||
assert_eq!(eviction_json["method"], "x.ai/internal/evict_sessions");
|
||||
assert_eq!(eviction_json["method"], "kigi/internal/evict_sessions");
|
||||
|
||||
// Connect a NEW client — server should still be running
|
||||
let mut client2 = LeaderClient::connect(
|
||||
@@ -1824,7 +1824,7 @@ async fn test_session_ownership_cleanup_on_disconnect() {
|
||||
// client2 should NOT receive the dead-session notification.
|
||||
// Send a second notification without a sessionId — this one SHOULD
|
||||
// arrive via fallback routing, proving client2 is alive and connected.
|
||||
let probe = r#"{"jsonrpc":"2.0","method":"x.ai/probe","params":{"ping":true}}"#;
|
||||
let probe = r#"{"jsonrpc":"2.0","method":"kigi/probe","params":{"ping":true}}"#;
|
||||
response_tx.send(probe.to_string()).unwrap();
|
||||
|
||||
let recv = tokio::time::timeout(Duration::from_secs(2), client2.recv())
|
||||
@@ -1857,7 +1857,7 @@ async fn test_code_nav_capable_client_gets_true_injected_into_session_new() {
|
||||
// Web client that advertised code-nav capability during registration.
|
||||
let web_client = LeaderClient::connect(
|
||||
sock_path,
|
||||
"grok-web",
|
||||
"kigi-web",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities {
|
||||
code_nav_enabled: true,
|
||||
@@ -1894,7 +1894,7 @@ async fn test_non_code_nav_client_gets_false_injected_into_session_new() {
|
||||
// TUI client with no code-nav capability.
|
||||
let tui_client = LeaderClient::connect(
|
||||
sock_path,
|
||||
"grok-tui",
|
||||
"kigi-tui",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities {
|
||||
code_nav_enabled: false,
|
||||
@@ -1933,7 +1933,7 @@ async fn test_leader_code_nav_client_isolation() {
|
||||
// Web client with code-nav capability.
|
||||
let web_client = LeaderClient::connect(
|
||||
sock_path.clone(),
|
||||
"grok-web",
|
||||
"kigi-web",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities {
|
||||
code_nav_enabled: true,
|
||||
@@ -1946,7 +1946,7 @@ async fn test_leader_code_nav_client_isolation() {
|
||||
// TUI client without code-nav capability.
|
||||
let tui_client = LeaderClient::connect(
|
||||
sock_path,
|
||||
"grok-tui",
|
||||
"kigi-tui",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities {
|
||||
code_nav_enabled: false,
|
||||
@@ -1994,7 +1994,7 @@ async fn test_code_nav_capability_injected_into_session_load() {
|
||||
|
||||
let web_client = LeaderClient::connect(
|
||||
sock_path,
|
||||
"grok-web",
|
||||
"kigi-web",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities {
|
||||
code_nav_enabled: true,
|
||||
@@ -2021,7 +2021,7 @@ async fn test_code_nav_capability_injected_into_session_load() {
|
||||
cancel.cancel();
|
||||
}
|
||||
|
||||
/// Verify that an `x.ai/code/status` extension request is forwarded to the
|
||||
/// Verify that an `kigi/code/status` extension request is forwarded to the
|
||||
/// agent with the correct method, sessionId, and cwd in the params.
|
||||
///
|
||||
/// This tests the routing boundary between leader and agent for the
|
||||
@@ -2033,7 +2033,7 @@ async fn test_code_status_ext_request_forwarded_to_agent() {
|
||||
|
||||
let web_client = LeaderClient::connect(
|
||||
sock_path,
|
||||
"grok-web",
|
||||
"kigi-web",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities {
|
||||
code_nav_enabled: true,
|
||||
@@ -2043,15 +2043,15 @@ async fn test_code_status_ext_request_forwarded_to_agent() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Send x.ai/code/status with a sessionId — the leader must forward it to the agent.
|
||||
let status_req = r#"{"jsonrpc":"2.0","id":42,"method":"extensions/ext","params":{"method":"x.ai/code/status","params":{"sessionId":"sess-web-1","cwd":"/repo"}}}"#;
|
||||
// Send kigi/code/status with a sessionId — the leader must forward it to the agent.
|
||||
let status_req = r#"{"jsonrpc":"2.0","id":42,"method":"extensions/ext","params":{"method":"kigi/code/status","params":{"sessionId":"sess-web-1","cwd":"/repo"}}}"#;
|
||||
web_client.send(status_req.to_string()).unwrap();
|
||||
|
||||
let forwarded = acp_rx.recv().await.unwrap();
|
||||
let json: serde_json::Value = serde_json::from_str(&forwarded).unwrap();
|
||||
|
||||
assert_eq!(json["method"], "extensions/ext");
|
||||
assert_eq!(json["params"]["method"], "x.ai/code/status");
|
||||
assert_eq!(json["params"]["method"], "kigi/code/status");
|
||||
assert_eq!(json["params"]["params"]["sessionId"], "sess-web-1");
|
||||
assert_eq!(json["params"]["params"]["cwd"], "/repo");
|
||||
|
||||
@@ -2284,7 +2284,7 @@ async fn test_connect_waits_for_leader_ready() {
|
||||
let connect_start = tokio::time::Instant::now();
|
||||
let mut client = LeaderClient::connect(
|
||||
sock_path,
|
||||
"grok-tui",
|
||||
"kigi-tui",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities::default(),
|
||||
)
|
||||
@@ -2337,7 +2337,7 @@ async fn test_connect_waits_for_leader_ready() {
|
||||
|
||||
// ── Version mismatch notification ────────────────────────────────────
|
||||
|
||||
/// Integration test: a connected client receives `x.ai/leader/version_mismatch`
|
||||
/// Integration test: a connected client receives `kigi/leader/version_mismatch`
|
||||
/// when its `client_version` differs from the leader's version.
|
||||
///
|
||||
/// Uses `leader_version_override` so the test bypasses the `"unknown"` constant
|
||||
@@ -2406,7 +2406,7 @@ async fn test_version_mismatch_notification_sent_to_client() {
|
||||
.expect("channel closed");
|
||||
|
||||
let json: serde_json::Value = serde_json::from_str(&msg).unwrap();
|
||||
assert_eq!(json["method"], "x.ai/leader/version_mismatch");
|
||||
assert_eq!(json["method"], "kigi/leader/version_mismatch");
|
||||
assert_eq!(json["params"]["clientVersion"], "test-client-0.1.157");
|
||||
assert_eq!(json["params"]["leaderVersion"], "test-leader-0.1.150");
|
||||
|
||||
@@ -2729,7 +2729,7 @@ async fn test_initialize_injected_when_not_first_message() {
|
||||
|
||||
let client = LeaderClient::connect(
|
||||
sock_path,
|
||||
"grok-tui",
|
||||
"kigi-tui",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities::default(),
|
||||
)
|
||||
@@ -2762,7 +2762,7 @@ async fn test_initialize_injected_when_not_first_message() {
|
||||
.and_then(|v| v.as_str());
|
||||
assert_eq!(
|
||||
client_id,
|
||||
Some("grok-tui"),
|
||||
Some("kigi-tui"),
|
||||
"clientIdentifier must be injected into initialize even when it is not the first message"
|
||||
);
|
||||
|
||||
@@ -2782,7 +2782,7 @@ async fn test_leader_code_nav_isolation_end_to_end() {
|
||||
// Web client with code-nav capability.
|
||||
let web_client = LeaderClient::connect(
|
||||
sock_path.clone(),
|
||||
"grok-web",
|
||||
"kigi-web",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities {
|
||||
code_nav_enabled: true,
|
||||
@@ -2795,7 +2795,7 @@ async fn test_leader_code_nav_isolation_end_to_end() {
|
||||
// TUI client without code-nav capability.
|
||||
let tui_client = LeaderClient::connect(
|
||||
sock_path,
|
||||
"grok-tui",
|
||||
"kigi-tui",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities {
|
||||
code_nav_enabled: false,
|
||||
@@ -2824,13 +2824,13 @@ async fn test_leader_code_nav_isolation_end_to_end() {
|
||||
serde_json::json!(false)
|
||||
);
|
||||
|
||||
// Web client sends x.ai/code/status (the primary non-starting code-nav call).
|
||||
let status_with_session = r#"{"jsonrpc":"2.0","id":10,"method":"extensions/ext","params":{"method":"x.ai/code/status","params":{"sessionId":"web-session","cwd":"/repo"}}}"#;
|
||||
// Web client sends kigi/code/status (the primary non-starting code-nav call).
|
||||
let status_with_session = r#"{"jsonrpc":"2.0","id":10,"method":"extensions/ext","params":{"method":"kigi/code/status","params":{"sessionId":"web-session","cwd":"/repo"}}}"#;
|
||||
web_client.send(status_with_session.to_string()).unwrap();
|
||||
|
||||
let status_fwd = acp_rx.recv().await.unwrap();
|
||||
let status_json: serde_json::Value = serde_json::from_str(&status_fwd).unwrap();
|
||||
assert_eq!(status_json["params"]["method"], "x.ai/code/status");
|
||||
assert_eq!(status_json["params"]["method"], "kigi/code/status");
|
||||
assert_eq!(status_json["params"]["params"]["sessionId"], "web-session");
|
||||
|
||||
web_client.cancel();
|
||||
@@ -3146,7 +3146,7 @@ async fn test_hung_agent_leaves_transport_healthy_and_forwards_cancel() {
|
||||
assert_eq!(cancel_json["method"], "session/cancel");
|
||||
|
||||
// Unrelated traffic still round-trips on the same connection.
|
||||
let probe = r#"{"jsonrpc":"2.0","method":"x.ai/probe","params":{"ping":true}}"#;
|
||||
let probe = r#"{"jsonrpc":"2.0","method":"kigi/probe","params":{"ping":true}}"#;
|
||||
response_tx.send(probe.to_string()).unwrap();
|
||||
let recv = tokio::time::timeout(Duration::from_secs(2), client.recv())
|
||||
.await
|
||||
@@ -3209,7 +3209,7 @@ async fn test_sever_mid_rpc_orphans_response_and_replay_recovers() {
|
||||
// signal that the server processed the disconnect.
|
||||
let evict = acp_rx.recv().await.unwrap();
|
||||
let evict_json: serde_json::Value = serde_json::from_str(&evict).unwrap();
|
||||
assert_eq!(evict_json["method"], "x.ai/internal/evict_sessions");
|
||||
assert_eq!(evict_json["method"], "kigi/internal/evict_sessions");
|
||||
|
||||
// The agent completes the turn anyway: durable terminal notification plus
|
||||
// the RPC response addressed to the dead client.
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
//! Binaries are resolved per role:
|
||||
//! - `KIGI_BINARY_LEADER` — the binary that elects the initial leader
|
||||
//! (typically the latest released stable, e.g. fetched from
|
||||
//! `https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-<ver>-linux-x86_64`).
|
||||
//! `https://storage.googleapis.com/kigi-public-artifacts/cli/kigi-<ver>-linux-x86_64`).
|
||||
//! - `KIGI_BINARY_CLIENT` — the second client (typically a freshly built main).
|
||||
//!
|
||||
//! All tests are `#[ignore]`d: they need two pre-built binaries and spawn real
|
||||
//! leader subprocesses. On-demand today — no CI lane runs them; invoke with:
|
||||
//!
|
||||
//! ```bash
|
||||
//! KIGI_BINARY_LEADER=/path/to/grok-old KIGI_BINARY_CLIENT=/path/to/grok-new \
|
||||
//! KIGI_BINARY_LEADER=/path/to/kigi-old KIGI_BINARY_CLIENT=/path/to/kigi-new \
|
||||
//! cargo test -p kigi-shell --test test_leader_version_skew -- --ignored --nocapture
|
||||
//! ```
|
||||
|
||||
@@ -202,7 +202,7 @@ async fn old_client_adopts_new_leader_and_still_functions() {
|
||||
.expect("old client prompt on new leader failed");
|
||||
|
||||
// The leader records the client/leader version mismatch (the
|
||||
// x.ai/leader/version_mismatch notification's server-side warn).
|
||||
// kigi/leader/version_mismatch notification's server-side warn).
|
||||
let deadline = tokio::time::Instant::now() + Duration::from_secs(10);
|
||||
let mut saw_mismatch = false;
|
||||
while tokio::time::Instant::now() < deadline {
|
||||
@@ -221,7 +221,7 @@ async fn old_client_adopts_new_leader_and_still_functions() {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// `grok update`'s relaunch signal against a REAL old leader: connect,
|
||||
/// `kigi update`'s relaunch signal against a REAL old leader: connect,
|
||||
/// require `relaunch_v1`, send `RelaunchForUpdate`, and the leader exits so
|
||||
/// the surviving client re-elects. Mirrors the private
|
||||
/// `signal_leaders_to_relaunch` in `kigi-bin/src/main.rs` (which is
|
||||
@@ -260,7 +260,7 @@ async fn relaunch_for_update_drives_real_old_leader_to_exit() {
|
||||
// The update-signal body, against the sandboxed socket.
|
||||
let control = LeaderClient::connect(
|
||||
home.path().join(".kigi").join("leader.sock"),
|
||||
"grok-pager-update",
|
||||
"kigi-pager-update",
|
||||
ClientMode::Stdio,
|
||||
ClientCapabilities::default(),
|
||||
)
|
||||
|
||||
@@ -242,7 +242,7 @@ fn rule(action: RuleAction, pattern: &str) -> PermissionRule {
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn mcp_tool_grant_persists_and_short_circuits_next_request() {
|
||||
run_actor_test(ClientType::GrokPager, |handle, gw, cwd| async move {
|
||||
run_actor_test(ClientType::KigiPager, |handle, gw, cwd| async move {
|
||||
// First request prompts; user picks tool-scope.
|
||||
gw.expect_allow_always_mcp_tool("linear__list");
|
||||
let d = request(&handle, mcp("linear__list"), "1").await;
|
||||
@@ -300,7 +300,7 @@ async fn mcp_tool_grant_persists_and_short_circuits_next_request() {
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn fallback_client_plain_allow_always_persists_mcp_tool() {
|
||||
// Regression: Generic / GrokWeb / Extension clients
|
||||
// Regression: Generic / KigiWeb / Extension clients
|
||||
// submit the legacy `"always-allow"` option id. The prompter maps that
|
||||
// to plain `PromptOutcome::AllowAlways`, and the manager's plain arm
|
||||
// must persist tool-scope into `allowed_mcp_tools`.
|
||||
@@ -362,7 +362,7 @@ async fn policy_ask_suppresses_mcp_tool_allowlist() {
|
||||
make_session_id(),
|
||||
gw.sender.clone(),
|
||||
cwd.clone(),
|
||||
ClientType::GrokPager,
|
||||
ClientType::KigiPager,
|
||||
Some(policy),
|
||||
vec![], // deny_read_globs
|
||||
vec![],
|
||||
@@ -412,7 +412,7 @@ async fn policy_ask_suppresses_mcp_server_allowlist() {
|
||||
make_session_id(),
|
||||
gw.sender.clone(),
|
||||
cwd.clone(),
|
||||
ClientType::GrokPager,
|
||||
ClientType::KigiPager,
|
||||
Some(policy),
|
||||
vec![], // deny_read_globs
|
||||
vec![],
|
||||
@@ -456,7 +456,7 @@ async fn policy_deny_takes_precedence_over_mcp_allowlist() {
|
||||
make_session_id(),
|
||||
gw.sender.clone(),
|
||||
cwd.clone(),
|
||||
ClientType::GrokPager,
|
||||
ClientType::KigiPager,
|
||||
Some(policy),
|
||||
vec![], // deny_read_globs
|
||||
vec![],
|
||||
@@ -482,7 +482,7 @@ async fn policy_allow_short_circuits_before_mcp_allowlist() {
|
||||
let policy = PermissionConfig::new(vec![rule(RuleAction::Allow, "linear__*")]);
|
||||
|
||||
run_actor_test_with_policy(
|
||||
ClientType::GrokPager,
|
||||
ClientType::KigiPager,
|
||||
Some(policy),
|
||||
|handle, _gw, _cwd| async move {
|
||||
let d = request(&handle, mcp("linear__list"), "1").await;
|
||||
@@ -499,7 +499,7 @@ async fn empty_server_prefix_falls_back_to_tool_scope() {
|
||||
// somehow makes it through, the prompter must downgrade to tool-scope
|
||||
// and persist via `AllowAlwaysMcpTool` — never write an empty server
|
||||
// prefix into `allowed_mcp_servers`.
|
||||
run_actor_test(ClientType::GrokPager, |handle, gw, cwd| async move {
|
||||
run_actor_test(ClientType::KigiPager, |handle, gw, cwd| async move {
|
||||
let meta = serde_json::json!({
|
||||
"kind": "server",
|
||||
"server": "",
|
||||
@@ -531,7 +531,7 @@ async fn allow_always_mcp_tool_ignores_client_supplied_tool_name() {
|
||||
// only. The manager MUST persist the name from `AccessKind::MCPTool`
|
||||
// so a buggy or malicious client cannot whitelist a different tool
|
||||
// than the one the user saw in the prompt.
|
||||
run_actor_test(ClientType::GrokPager, |handle, gw, cwd| async move {
|
||||
run_actor_test(ClientType::KigiPager, |handle, gw, cwd| async move {
|
||||
// Request approves `linear__list`, but the response claims a
|
||||
// different tool name (e.g. `notion__fetch`).
|
||||
let meta = serde_json::json!({
|
||||
@@ -571,7 +571,7 @@ async fn allow_always_mcp_server_rejects_mismatched_prefix() {
|
||||
// canonical server prefix derived from the access kind. On mismatch,
|
||||
// the manager downgrades to tool-scope on the access-kind name -- the
|
||||
// smallest blast radius the user actually approved.
|
||||
run_actor_test(ClientType::GrokPager, |handle, gw, cwd| async move {
|
||||
run_actor_test(ClientType::KigiPager, |handle, gw, cwd| async move {
|
||||
// Approve `linear__list` (canonical server prefix is `linear`),
|
||||
// but the client claims `notion` as the server.
|
||||
let meta = serde_json::json!({
|
||||
@@ -613,7 +613,7 @@ async fn allow_always_mcp_server_rejects_mismatched_prefix() {
|
||||
async fn allow_always_mcp_server_persists_canonical_prefix_on_match() {
|
||||
// Sanity: a client that supplies the correct canonical prefix
|
||||
// succeeds (this is the common case post-fix).
|
||||
run_actor_test(ClientType::GrokPager, |handle, gw, cwd| async move {
|
||||
run_actor_test(ClientType::KigiPager, |handle, gw, cwd| async move {
|
||||
let meta = serde_json::json!({
|
||||
"kind": "server",
|
||||
"server": "linear",
|
||||
@@ -645,7 +645,7 @@ async fn allow_always_mcp_server_downgrades_when_access_has_no_separator() {
|
||||
// malformed `ToolInput::MCPTool`), the canonical prefix is None and
|
||||
// server-scope is unreachable. The manager downgrades to tool-scope
|
||||
// on the raw access name rather than persisting the client prefix.
|
||||
run_actor_test(ClientType::GrokPager, |handle, gw, cwd| async move {
|
||||
run_actor_test(ClientType::KigiPager, |handle, gw, cwd| async move {
|
||||
let meta = serde_json::json!({
|
||||
"kind": "server",
|
||||
"server": "linear",
|
||||
@@ -681,7 +681,7 @@ async fn dont_ask_policy_denies_without_prompting() {
|
||||
// No scripted gateway responses: if the manager tried to prompt,
|
||||
// the gateway would block forever, proving dont_ask short-circuits.
|
||||
run_actor_test_with_policy(
|
||||
ClientType::GrokPager,
|
||||
ClientType::KigiPager,
|
||||
Some(policy),
|
||||
|handle, _gw, _cwd| async move {
|
||||
let d = request(&handle, mcp("linear__list"), "1").await;
|
||||
@@ -716,7 +716,7 @@ async fn deny_rule_enforced_in_yolo_mode_bash() {
|
||||
}]);
|
||||
|
||||
run_actor_test_full(
|
||||
ClientType::GrokPager,
|
||||
ClientType::KigiPager,
|
||||
Some(policy),
|
||||
true,
|
||||
|handle, _gw, _cwd| async move {
|
||||
@@ -752,7 +752,7 @@ async fn deny_rule_enforced_in_yolo_mode_mcp() {
|
||||
}]);
|
||||
|
||||
run_actor_test_full(
|
||||
ClientType::GrokPager,
|
||||
ClientType::KigiPager,
|
||||
Some(policy),
|
||||
true,
|
||||
|handle, _gw, _cwd| async move {
|
||||
@@ -783,7 +783,7 @@ async fn deny_rule_enforced_in_yolo_mode_edit() {
|
||||
}]);
|
||||
|
||||
run_actor_test_full(
|
||||
ClientType::GrokPager,
|
||||
ClientType::KigiPager,
|
||||
Some(policy),
|
||||
true,
|
||||
|handle, _gw, _cwd| async move {
|
||||
@@ -814,7 +814,7 @@ async fn deny_rule_enforced_in_yolo_mode_web_fetch() {
|
||||
}]);
|
||||
|
||||
run_actor_test_full(
|
||||
ClientType::GrokPager,
|
||||
ClientType::KigiPager,
|
||||
Some(policy),
|
||||
true,
|
||||
|handle, _gw, _cwd| async move {
|
||||
@@ -848,7 +848,7 @@ async fn deny_rule_enforced_in_yolo_mode_web_fetch() {
|
||||
#[serial]
|
||||
async fn yolo_mode_without_deny_rules_approves_everything() {
|
||||
run_actor_test_full(
|
||||
ClientType::GrokPager,
|
||||
ClientType::KigiPager,
|
||||
None,
|
||||
true,
|
||||
|handle, _gw, _cwd| async move {
|
||||
|
||||
@@ -71,7 +71,7 @@ async fn test_refusal_turn_completes_with_single_messages_request() {
|
||||
with_local_set(|| async {
|
||||
let server = refusal_messages_server().await;
|
||||
let workdir = git_workdir();
|
||||
let client = GrokStdioClient::spawn(&server, workdir.path()).await;
|
||||
let client = KigiStdioClient::spawn(&server, workdir.path()).await;
|
||||
|
||||
client.initialize_with_timeout().await;
|
||||
let session_id = client
|
||||
|
||||
@@ -255,7 +255,7 @@ async fn test_chat_completions_streaming_tool_calls() {
|
||||
let server = MockInferenceServer::start().await.unwrap();
|
||||
server.enqueue_response(
|
||||
"/v1/chat/completions",
|
||||
ScriptedResponse::sse(chat_completion_tool_call_stream(tool_calls, "grok-test")),
|
||||
ScriptedResponse::sse(chat_completion_tool_call_stream(tool_calls, "kigi-test")),
|
||||
);
|
||||
let client = create_test_client(&server.url(), ApiBackend::ChatCompletions);
|
||||
|
||||
@@ -296,7 +296,7 @@ async fn test_chat_completions_with_reasoning() {
|
||||
ScriptedResponse::sse(chat_completion_with_reasoning_stream(
|
||||
"Let me think about this",
|
||||
"The answer is 42",
|
||||
"grok-test",
|
||||
"kigi-test",
|
||||
)),
|
||||
);
|
||||
let client = create_test_client(&server.url(), ApiBackend::ChatCompletions);
|
||||
@@ -344,7 +344,7 @@ async fn chat_completions_collect_synthesizes_reasoning_sibling() {
|
||||
ScriptedResponse::sse(chat_completion_with_reasoning_stream(
|
||||
"Let me think about this",
|
||||
"The answer is 42",
|
||||
"grok-test",
|
||||
"kigi-test",
|
||||
)),
|
||||
);
|
||||
let client = create_test_client(&server.url(), ApiBackend::ChatCompletions);
|
||||
@@ -453,7 +453,7 @@ async fn chat_completions_upgrade_folds_reconstructed_reasoning_into_request() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Upgrade path, grok-build / Responses API: a legacy session whose
|
||||
/// Upgrade path, kigi / Responses API: a legacy session whose
|
||||
/// assistant carries inline `reasoning: {text, encrypted, id}` must, on
|
||||
/// load, reconstruct a sibling Reasoning item that round-trips back to
|
||||
/// the Responses API as a **typed** `reasoning` input item — `summary`,
|
||||
@@ -462,7 +462,7 @@ async fn chat_completions_upgrade_folds_reconstructed_reasoning_into_request() {
|
||||
/// through `reasoning_item_text`.
|
||||
#[tokio::test]
|
||||
async fn responses_upgrade_roundtrips_reconstructed_reasoning_as_typed_input() {
|
||||
// 1. Seed a legacy grok-build chat_history.jsonl (inline reasoning
|
||||
// 1. Seed a legacy kigi chat_history.jsonl (inline reasoning
|
||||
// with encrypted_content + id — the older shape).
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::write(
|
||||
@@ -472,7 +472,7 @@ async fn responses_upgrade_roundtrips_reconstructed_reasoning_as_typed_input() {
|
||||
"\n",
|
||||
r#"{"type":"user","content":[{"type":"text","text":"q1"}]}"#,
|
||||
"\n",
|
||||
r#"{"type":"assistant","content":"a1","reasoning":{"text":"legacy grok-build reasoning","encrypted":"ENC_BLOB_xyz","id":"rs_grokbuild_legacy"},"model_id":"grok-build"}"#,
|
||||
r#"{"type":"assistant","content":"a1","reasoning":{"text":"legacy kigi reasoning","encrypted":"ENC_BLOB_xyz","id":"rs_kigibuild_legacy"},"model_id":"kigi"}"#,
|
||||
"\n",
|
||||
),
|
||||
)
|
||||
@@ -513,7 +513,7 @@ async fn responses_upgrade_roundtrips_reconstructed_reasoning_as_typed_input() {
|
||||
});
|
||||
assert_eq!(
|
||||
reasoning.get("id").and_then(Value::as_str),
|
||||
Some("rs_grokbuild_legacy"),
|
||||
Some("rs_kigibuild_legacy"),
|
||||
"reasoning id preserved"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -528,7 +528,7 @@ async fn responses_upgrade_roundtrips_reconstructed_reasoning_as_typed_input() {
|
||||
);
|
||||
assert_eq!(
|
||||
summary[0].get("text").and_then(Value::as_str),
|
||||
Some("legacy grok-build reasoning")
|
||||
Some("legacy kigi reasoning")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -550,7 +550,7 @@ async fn messages_upgrade_emits_reconstructed_reasoning_as_thinking_block() {
|
||||
"\n",
|
||||
r#"{"type":"user","content":[{"type":"text","text":"q1"}]}"#,
|
||||
"\n",
|
||||
r#"{"type":"assistant","content":"a1","reasoning":{"text":"legacy anthropic thinking","encrypted":"SIGNATURE_abc","id":""},"model_id":"grok-4.5"}"#,
|
||||
r#"{"type":"assistant","content":"a1","reasoning":{"text":"legacy anthropic thinking","encrypted":"SIGNATURE_abc","id":""},"model_id":"kigi-4.5"}"#,
|
||||
"\n",
|
||||
),
|
||||
)
|
||||
@@ -738,7 +738,7 @@ async fn test_responses_api_streaming_tool_call() {
|
||||
"call_xyz789",
|
||||
"bash",
|
||||
r#"{"command": "ls -la"}"#,
|
||||
"grok-test",
|
||||
"kigi-test",
|
||||
)),
|
||||
);
|
||||
let client = create_test_client(&server.url(), ApiBackend::Responses);
|
||||
@@ -781,7 +781,7 @@ async fn test_responses_api_with_reasoning_and_encrypted_content() {
|
||||
"Let me think step by step about this problem.",
|
||||
Some("enc_base64_encrypted_reasoning_chain_data"),
|
||||
"The answer based on my reasoning is 42.",
|
||||
"grok-test",
|
||||
"kigi-test",
|
||||
)),
|
||||
);
|
||||
let client = create_test_client(&server.url(), ApiBackend::Responses);
|
||||
@@ -848,7 +848,7 @@ async fn test_responses_api_reasoning_without_encrypted() {
|
||||
"I need to analyze the code carefully.",
|
||||
None, // No encrypted content
|
||||
"Here is my analysis.",
|
||||
"grok-test",
|
||||
"kigi-test",
|
||||
)),
|
||||
);
|
||||
let client = create_test_client(&server.url(), ApiBackend::Responses);
|
||||
@@ -955,7 +955,7 @@ async fn test_stream_error_during_streaming() {
|
||||
"id": "chatcmpl-test123",
|
||||
"object": "chat.completion.chunk",
|
||||
"created": 1234567890,
|
||||
"model": "grok-test",
|
||||
"model": "kigi-test",
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"delta": {"role": "assistant", "content": "Hello"},
|
||||
@@ -1089,8 +1089,11 @@ async fn test_request_includes_headers() {
|
||||
let request = server.requests().pop().unwrap();
|
||||
|
||||
assert_eq!(request.header("authorization"), Some("Bearer test-api-key"));
|
||||
assert_eq!(request.header("x-grok-conv-id"), Some("conv-12345"));
|
||||
assert_eq!(request.header("x-grok-req-id"), Some("req-67890"));
|
||||
// PRD F3: auth is a plain bearer — the legacy proxy's tracking marker
|
||||
// headers are never sent on the wire, even when conv/req ids are set on
|
||||
// the request (they remain client-internal plumbing).
|
||||
assert_eq!(request.header("x-kigi-conv-id"), None);
|
||||
assert_eq!(request.header("x-kigi-req-id"), None);
|
||||
}
|
||||
|
||||
/// The session writes the resolved `x-compaction-at` value into
|
||||
@@ -1179,7 +1182,7 @@ async fn test_responses_api_request_format() {
|
||||
}
|
||||
|
||||
/// The sampler owns the doom-loop opt-in: setting
|
||||
/// `SamplerConfig::doom_loop_recovery` puts `x-grok-doom-loop-check` on the
|
||||
/// `SamplerConfig::doom_loop_recovery` puts `x-kigi-doom-loop-check` on the
|
||||
/// wire AND arms the collector, and the server's named check event is
|
||||
/// absorbed mid-stream without disturbing the typed event flow.
|
||||
#[tokio::test]
|
||||
@@ -1216,7 +1219,7 @@ async fn test_doom_loop_check_enabled_sends_header_and_absorbs_check_event() {
|
||||
|
||||
let logged = server.requests().pop().unwrap();
|
||||
assert!(logged.path.contains("/responses"));
|
||||
assert_eq!(logged.header("x-grok-doom-loop-check"), Some("true"));
|
||||
assert_eq!(logged.header("x-kigi-doom-loop-check"), Some("true"));
|
||||
}
|
||||
|
||||
/// With the check disabled no header goes on the wire, and check frames from
|
||||
@@ -1257,7 +1260,7 @@ async fn test_doom_loop_check_disabled_sends_no_header_and_drops_check_frames()
|
||||
|
||||
let logged = server.requests().pop().unwrap();
|
||||
assert!(logged.path.contains("/responses"));
|
||||
assert_eq!(logged.header("x-grok-doom-loop-check"), None);
|
||||
assert_eq!(logged.header("x-kigi-doom-loop-check"), None);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! session's `subagents/` dir and flips any stale `running` meta (not tracked by
|
||||
//! the live coordinator) to `cancelled` (mechanism A, the meta pass).
|
||||
//!
|
||||
//! This test spawns a real `grok agent stdio` process, seeds an orphaned
|
||||
//! This test spawns a real `kigi agent stdio` process, seeds an orphaned
|
||||
//! `running` meta on disk, resumes the session, and asserts the meta was
|
||||
//! reconciled to `cancelled`.
|
||||
//!
|
||||
@@ -57,7 +57,7 @@ async fn resume_reconciles_orphaned_running_subagent() {
|
||||
let workdir = git_workdir();
|
||||
|
||||
// Phase 1: create a real session, then take its home so we can seed it.
|
||||
let mut writer = GrokStdioClient::spawn(&server, workdir.path()).await;
|
||||
let mut writer = KigiStdioClient::spawn(&server, workdir.path()).await;
|
||||
writer.initialize_with_timeout().await;
|
||||
let session_id = writer.create_session_with_timeout(workdir.path()).await;
|
||||
let shared_home = writer.take_home();
|
||||
@@ -66,7 +66,7 @@ async fn resume_reconciles_orphaned_running_subagent() {
|
||||
// Simulate a crash: inject a subagent meta left `running` on disk (no
|
||||
// terminal write, no SubagentFinished) — exactly what a dead process
|
||||
// leaves behind.
|
||||
// GrokStdioClient sets HOME=<temp>; the binary uses <HOME>/.kigi as KIGI_SHARE_DIR.
|
||||
// KigiStdioClient sets HOME=<temp>; the binary uses <HOME>/.kigi as KIGI_SHARE_DIR.
|
||||
let kigi_home = shared_home.path().join(".kigi");
|
||||
let session_dir = locate_session_dir(&kigi_home, session_id.0.as_ref());
|
||||
let sub_id = "sa-orphan";
|
||||
@@ -89,7 +89,7 @@ async fn resume_reconciles_orphaned_running_subagent() {
|
||||
.unwrap();
|
||||
|
||||
// Phase 2: resume in a fresh process. `load_session` runs the reconcile.
|
||||
let reader = GrokStdioClient::spawn_with_home(&server, workdir.path(), shared_home).await;
|
||||
let reader = KigiStdioClient::spawn_with_home(&server, workdir.path(), shared_home).await;
|
||||
reader.initialize_with_timeout().await;
|
||||
let _ = reader
|
||||
.load_session_with_timeout(&session_id, workdir.path())
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! creation — not only after an explicit model/effort switch (the old
|
||||
//! behavior, which left the field absent for sessions that never switched).
|
||||
//!
|
||||
//! Each test spawns a real `grok agent stdio` process against a mock
|
||||
//! Each test spawns a real `kigi agent stdio` process against a mock
|
||||
//! inference server and asserts on the persisted `summary.json`.
|
||||
//!
|
||||
//! Run locally:
|
||||
@@ -60,10 +60,10 @@ async fn test_fresh_session_persists_reasoning_effort() {
|
||||
// user config override (the same path a remote settings catalog entry or
|
||||
// `--effort` would populate).
|
||||
let home = tempfile::TempDir::new().expect("create temp home");
|
||||
let grok_dir = home.path().join(".kigi");
|
||||
std::fs::create_dir_all(&grok_dir).expect("create .kigi dir");
|
||||
let kigi_dir = home.path().join(".kigi");
|
||||
std::fs::create_dir_all(&kigi_dir).expect("create .kigi dir");
|
||||
std::fs::write(
|
||||
grok_dir.join("config.toml"),
|
||||
kigi_dir.join("config.toml"),
|
||||
r#"
|
||||
[model.test-model]
|
||||
supports_reasoning_effort = true
|
||||
@@ -72,7 +72,7 @@ reasoning_effort = "high"
|
||||
)
|
||||
.expect("write config.toml");
|
||||
|
||||
let client = GrokStdioClient::spawn_with_home(&server, workdir.path(), home).await;
|
||||
let client = KigiStdioClient::spawn_with_home(&server, workdir.path(), home).await;
|
||||
client.initialize_with_timeout().await;
|
||||
let session_id = client.create_session_with_timeout(workdir.path()).await;
|
||||
let result = client.prompt_with_timeout(&session_id, "say hello").await;
|
||||
@@ -98,7 +98,7 @@ async fn test_fresh_session_without_effort_omits_field() {
|
||||
.await
|
||||
.expect("start mock server");
|
||||
let workdir = git_workdir();
|
||||
let client = GrokStdioClient::spawn(&server, workdir.path()).await;
|
||||
let client = KigiStdioClient::spawn(&server, workdir.path()).await;
|
||||
|
||||
client.initialize_with_timeout().await;
|
||||
let session_id = client.create_session_with_timeout(workdir.path()).await;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! 3. Start a headless session — startup must re-copy trusted/user-home locals.
|
||||
//! 4. Smoke-validate session JSON under `$KIGI_SHARE_DIR/sessions/` after exit.
|
||||
//!
|
||||
//! Requires a built `grok` binary (`KIGI_BINARY` or cargo-built pager) for the
|
||||
//! Requires a built `kigi` binary (`KIGI_BINARY` or cargo-built pager) for the
|
||||
//! ignored headless test.
|
||||
//!
|
||||
//! ```bash
|
||||
@@ -114,7 +114,7 @@ fn trusted_local_refresh_surfaces_new_agent_via_discovery() {
|
||||
let home = dunce::canonicalize(home_tmp.path()).unwrap();
|
||||
let kigi_home = home.join(".kigi");
|
||||
let _home_guard = EnvVarGuard::set("HOME", &home);
|
||||
let _grok_guard = EnvVarGuard::set("KIGI_SHARE_DIR", &kigi_home);
|
||||
let _kigi_guard = EnvVarGuard::set("KIGI_SHARE_DIR", &kigi_home);
|
||||
|
||||
// Live source: a user-home local plugin (mirrors a `~/.claude` local tree).
|
||||
let source = home
|
||||
@@ -198,7 +198,7 @@ fn trusted_local_refresh_surfaces_new_agent_via_discovery() {
|
||||
|
||||
/// Full binary smoke: session start runs refresh then writes session JSON.
|
||||
#[tokio::test]
|
||||
#[ignore = "requires pre-built grok binary; run with --ignored"]
|
||||
#[ignore = "requires pre-built kigi binary; run with --ignored"]
|
||||
#[serial]
|
||||
async fn headless_session_refreshes_trusted_local_plugin_and_writes_session_json() {
|
||||
let server = MockInferenceServer::start()
|
||||
@@ -223,7 +223,7 @@ async fn headless_session_refreshes_trusted_local_plugin_and_writes_session_json
|
||||
// is only for the in-process post-run discovery assertion (which resolves the
|
||||
// registry via kigi_home()). `#[serial]` keeps it from racing other tests.
|
||||
let _home_guard = EnvVarGuard::set("HOME", &home);
|
||||
let _grok_guard = EnvVarGuard::set("KIGI_SHARE_DIR", &kigi_home);
|
||||
let _kigi_guard = EnvVarGuard::set("KIGI_SHARE_DIR", &kigi_home);
|
||||
|
||||
let mut registry = InstallRegistry::empty(kigi_home.join("installed-plugins"));
|
||||
let installed = register_local_install(&mut registry, &source);
|
||||
@@ -233,7 +233,7 @@ async fn headless_session_refreshes_trusted_local_plugin_and_writes_session_json
|
||||
assert!(!installed.path.join("agents/new.md").exists());
|
||||
|
||||
let workdir = git_workdir();
|
||||
let mut cmd = tokio::process::Command::new(grok_binary());
|
||||
let mut cmd = tokio::process::Command::new(kigi_binary());
|
||||
cmd.args([
|
||||
"-p",
|
||||
"say hello",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//! Vendor-compatibility end-to-end tests.
|
||||
//!
|
||||
//! Each test builds a fake `$HOME` containing skills/rules/AGENTS.md under the
|
||||
//! `.kigi`, `.cursor`, and `.claude` vendor dirs, spawns a real `grok agent
|
||||
//! `.kigi`, `.cursor`, and `.claude` vendor dirs, spawns a real `kigi agent
|
||||
//! stdio` process against the mock inference server (toggling the
|
||||
//! `GROK_<VENDOR>_<SURFACE>_ENABLED` env vars via `cmd.env`), sends one prompt,
|
||||
//! `KIGI_<VENDOR>_<SURFACE>_ENABLED` env vars via `cmd.env`), sends one prompt,
|
||||
//! and asserts on the full inference request bodies:
|
||||
//!
|
||||
//! - the Grok-native skill is always present regardless of toggles
|
||||
//! - the Kigi-native skill is always present regardless of toggles
|
||||
//! - each of the 6 (vendor x surface) cells toggles independently
|
||||
//! - a vendor-shipped default skill (`shell`) under `~/.cursor` is always
|
||||
//! dropped by the denylist
|
||||
@@ -33,7 +33,7 @@ where
|
||||
|
||||
/// Unique markers placed in skill descriptions / file contents so assertions
|
||||
/// can't be fooled by incidental occurrences of a bare word like "shell".
|
||||
const MARKER_GROK_SKILL: &str = "ZZ_GROK_SKILL_MARKER";
|
||||
const MARKER_KIGI_SKILL: &str = "ZZ_KIGI_SKILL_MARKER";
|
||||
const MARKER_CURSOR_SKILL: &str = "ZZ_CURSOR_SKILL_MARKER";
|
||||
const MARKER_CURSOR_SHELL: &str = "ZZ_CURSOR_SHELL_DENYLISTED_MARKER";
|
||||
const MARKER_CLAUDE_SKILL: &str = "ZZ_CLAUDE_SKILL_MARKER";
|
||||
@@ -63,7 +63,7 @@ fn write_skill(home: &Path, vendor_dir: &str, name: &str, marker: &str) {
|
||||
/// Populate a fake `$HOME` + repo cwd with the full vendor-compat fixture set.
|
||||
fn seed_fixtures(home: &Path, cwd: &Path) {
|
||||
// Skills (User scope, home-based).
|
||||
write_skill(home, ".kigi", "grok-skill", MARKER_GROK_SKILL);
|
||||
write_skill(home, ".kigi", "kigi-skill", MARKER_KIGI_SKILL);
|
||||
write_skill(home, ".cursor", "my-cursor-skill", MARKER_CURSOR_SKILL);
|
||||
// `shell` is a Cursor vendor-default → must be denylisted under ~/.cursor.
|
||||
write_skill(home, ".cursor", "shell", MARKER_CURSOR_SHELL);
|
||||
@@ -102,7 +102,7 @@ async fn run_scenario(env: &[(&str, &str)]) -> String {
|
||||
let home = tempfile::TempDir::new().expect("create temp home");
|
||||
seed_fixtures(home.path(), workdir.path());
|
||||
|
||||
let client = GrokStdioClient::spawn_with_home_and_env(&server, workdir.path(), home, env).await;
|
||||
let client = KigiStdioClient::spawn_with_home_and_env(&server, workdir.path(), home, env).await;
|
||||
client.initialize_with_timeout().await;
|
||||
let session_id = client.create_session_with_timeout(workdir.path()).await;
|
||||
let _ = client.prompt_with_timeout(&session_id, "hello").await;
|
||||
@@ -122,7 +122,7 @@ async fn run_scenario(env: &[(&str, &str)]) -> String {
|
||||
|
||||
// ── Skills ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Defaults (all vendors on): grok + cursor-vendor + claude-vendor skills present; the
|
||||
/// Defaults (all vendors on): kigi + cursor-vendor + claude-vendor skills present; the
|
||||
/// denylisted vendor builtin `shell` is dropped.
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary
|
||||
@@ -130,8 +130,8 @@ async fn vendor_compat_defaults_include_vendor_skills_but_drop_denylisted() {
|
||||
with_local_set(|| async {
|
||||
let body = run_scenario(&[]).await;
|
||||
assert!(
|
||||
body.contains(MARKER_GROK_SKILL),
|
||||
"grok-skill must always be present"
|
||||
body.contains(MARKER_KIGI_SKILL),
|
||||
"kigi-skill must always be present"
|
||||
);
|
||||
assert!(
|
||||
body.contains(MARKER_CURSOR_SKILL),
|
||||
@@ -149,15 +149,15 @@ async fn vendor_compat_defaults_include_vendor_skills_but_drop_denylisted() {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// `KIGI_CURSOR_SKILLS_ENABLED=false` drops the cursor-vendor skill; grok stays.
|
||||
/// `KIGI_CURSOR_SKILLS_ENABLED=false` drops the cursor-vendor skill; kigi stays.
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary
|
||||
async fn vendor_compat_cursor_skills_disabled() {
|
||||
with_local_set(|| async {
|
||||
let body = run_scenario(&[("KIGI_CURSOR_SKILLS_ENABLED", "false")]).await;
|
||||
assert!(
|
||||
body.contains(MARKER_GROK_SKILL),
|
||||
"grok-skill always present"
|
||||
body.contains(MARKER_KIGI_SKILL),
|
||||
"kigi-skill always present"
|
||||
);
|
||||
assert!(
|
||||
!body.contains(MARKER_CURSOR_SKILL),
|
||||
@@ -169,15 +169,15 @@ async fn vendor_compat_cursor_skills_disabled() {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// `KIGI_CLAUDE_SKILLS_ENABLED=false` drops the claude-vendor skill; grok stays.
|
||||
/// `KIGI_CLAUDE_SKILLS_ENABLED=false` drops the claude-vendor skill; kigi stays.
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary
|
||||
async fn vendor_compat_claude_skills_disabled() {
|
||||
with_local_set(|| async {
|
||||
let body = run_scenario(&[("KIGI_CLAUDE_SKILLS_ENABLED", "false")]).await;
|
||||
assert!(
|
||||
body.contains(MARKER_GROK_SKILL),
|
||||
"grok-skill always present"
|
||||
body.contains(MARKER_KIGI_SKILL),
|
||||
"kigi-skill always present"
|
||||
);
|
||||
assert!(
|
||||
!body.contains(MARKER_CLAUDE_SKILL),
|
||||
@@ -307,7 +307,7 @@ async fn vendor_compat_all_cursor_disabled() {
|
||||
assert!(!body.contains(MARKER_CURSOR_SHELL));
|
||||
assert!(!body.contains(MARKER_CURSOR_RULE));
|
||||
assert!(!body.contains(MARKER_CURSOR_AGENTS));
|
||||
assert!(body.contains(MARKER_GROK_SKILL), "grok always present");
|
||||
assert!(body.contains(MARKER_KIGI_SKILL), "kigi always present");
|
||||
assert!(body.contains(MARKER_CLAUDE_SKILL), "claude unaffected");
|
||||
assert!(body.contains(MARKER_CLAUDE_RULE), "claude unaffected");
|
||||
assert!(body.contains(MARKER_CLAUDE_AGENTS), "claude unaffected");
|
||||
@@ -330,7 +330,7 @@ async fn vendor_compat_all_claude_disabled() {
|
||||
assert!(!body.contains(MARKER_CLAUDE_SKILL));
|
||||
assert!(!body.contains(MARKER_CLAUDE_RULE));
|
||||
assert!(!body.contains(MARKER_CLAUDE_AGENTS));
|
||||
assert!(body.contains(MARKER_GROK_SKILL), "grok always present");
|
||||
assert!(body.contains(MARKER_KIGI_SKILL), "kigi always present");
|
||||
assert!(body.contains(MARKER_CURSOR_SKILL), "cursor unaffected");
|
||||
assert!(body.contains(MARKER_CURSOR_RULE), "cursor unaffected");
|
||||
assert!(body.contains(MARKER_CURSOR_AGENTS), "cursor unaffected");
|
||||
@@ -339,7 +339,7 @@ async fn vendor_compat_all_claude_disabled() {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// All vendor compat OFF: only grok-native skill survives.
|
||||
/// All vendor compat OFF: only kigi-native skill survives.
|
||||
#[tokio::test]
|
||||
#[ignore] // requires pre-built binary
|
||||
async fn vendor_compat_all_vendors_disabled() {
|
||||
@@ -353,7 +353,7 @@ async fn vendor_compat_all_vendors_disabled() {
|
||||
("KIGI_CLAUDE_AGENTS_ENABLED", "false"),
|
||||
])
|
||||
.await;
|
||||
assert!(body.contains(MARKER_GROK_SKILL), "grok always present");
|
||||
assert!(body.contains(MARKER_KIGI_SKILL), "kigi always present");
|
||||
assert!(!body.contains(MARKER_CURSOR_SKILL));
|
||||
assert!(!body.contains(MARKER_CURSOR_SHELL));
|
||||
assert!(!body.contains(MARKER_CURSOR_RULE));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Integration test for `_x.ai/session/update` notifications.
|
||||
//! Integration test for `_kigi/session/update` notifications.
|
||||
//!
|
||||
//! This test verifies that:
|
||||
//! 1. xAI session notifications (e.g., diff_review) can be sent via ext_notification
|
||||
@@ -123,7 +123,7 @@ async fn test_xai_session_notification_storage_roundtrip() {
|
||||
|
||||
/// Test that a `TurnCompleted` terminal round-trips through storage — the
|
||||
/// persistence half of the "stuck on Waiting…" fix, where the durable terminal
|
||||
/// must survive `updates.jsonl` and reload as a replayable `_x.ai/session/update`.
|
||||
/// must survive `updates.jsonl` and reload as a replayable `_kigi/session/update`.
|
||||
#[tokio::test]
|
||||
async fn test_turn_completed_round_trips_through_storage() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user