F3: Kimi inference pipeline + full grok cloud-surface excision

Sampler / inference (PRD F3):
- kimi_compat.rs: single adaptation point for the Kimi chat/completions
  dialect (thinking-field mapping, model_id stripping, empty-content
  tool-call message fix, stream_options.include_usage), with kimi-cli
  source citations
- Rate-limit handling reworked for Kimi/Moonshot semantics; UA kigi/{version}
- /models replaces the xAI models-v2 endpoint everywhere; idle model
  refresh carries X-Msh-* device headers only (X-XAI-Token-Auth and
  x-grok-client-mode/CLIENT_MODE_HEADER machinery deleted)

Cloud-surface excision (PRD §5, zero-egress):
- remote/ conversations lane, cli-chat-proxy-types crate, prod/ dir,
  share command, credit bar: deleted (single local session lane;
  paginate() replaces merge_and_paginate)
- Subscription/tier gate stack deleted end-to-end: AppView
  gate/tier/team/ZDR fields, app/subscription.rs watch loop,
  dispatch/billing.rs paywall + SuperGrok upsell, free-usage-exhausted
  chain, tier-restricted commands, GateInfo, RemoteSettings gate fields,
  SettingsUpdateNotification gate fields
- /privacy + coding-data-sharing setting deleted (backed by a dead xAI
  RPC; Kigi is zero-egress — nothing to share or retain remotely)

Auth UX correctness (user-reported):
- Device-flow fixtures now mirror the live Kimi payload shape
  (https://www.kimi.com/code/authorize_device?user_code=..., verified
  against auth.kimi.com); the fabricated auth.kimi.com/device?code=...
  URLs are gone
- open_browser_detached is a no-op under cfg(test): unit tests drove
  wiremock fixture URLs into the real browser (root cause of the
  "garbage mock link" ABCD-1234 tabs)
- Welcome/pager-minimal rebrand: Grok Build -> Kigi, grok.com ->
  kimi.com, "Sign in to Grok" -> "Sign in to Kimi"
This commit is contained in:
2026-07-17 16:05:51 -04:00
parent fe1f885bb3
commit ea0ce9d15f
231 changed files with 4730 additions and 26358 deletions
@@ -48,7 +48,6 @@ impl acp::Agent for MvpAgent {
);
});
kigi_workspace::trust::migrate_legacy_hook_trust();
self.maybe_sync_bundle_in_background(false);
let mut client_type = arguments
.meta
.as_ref()
@@ -278,11 +277,7 @@ impl acp::Agent for MvpAgent {
}
self.spawn_initialize_launch_mcp_setup(fetch_managed_mcps);
self.spawn_managed_gateway_tool_catalog_fetch();
let init_model_state = if crate::agent::chat_modes::process_chat_mode_enabled() {
self.chat_modes.model_state().await
} else {
self.model_state(None)
};
let init_model_state = self.model_state(None);
Ok(
acp::InitializeResponse::new(acp::ProtocolVersion::V1)
.agent_capabilities(
@@ -374,9 +369,6 @@ impl acp::Agent for MvpAgent {
}
}
self.set_auth_method(arguments.method_id.clone());
if crate::agent::chat_modes::process_chat_mode_enabled() {
self.chat_modes.warm_in_background();
}
emit_login_span(true, "api_key", None, None);
Ok(Default::default())
}
@@ -421,10 +413,8 @@ impl acp::Agent for MvpAgent {
.authenticate_after_cached_token_unavailable(arguments)
.await;
};
self.refresh_remote_settings(&auth).await;
self.emit_settings_update_notification();
self.maybe_sync_bundle_in_background(false);
{
{
let mut sampling_config = self.sampling_config.borrow_mut();
sampling_config.api_key = Some(auth.key);
tracing::debug!(
@@ -437,12 +427,8 @@ impl acp::Agent for MvpAgent {
);
}
self.set_auth_method(arguments.method_id.clone());
if crate::agent::chat_modes::process_chat_mode_enabled() {
self.chat_modes.warm_in_background();
}
let uid = self.auth_manager.current().map(|a| a.user_id);
emit_login_span(true, "cached_token", uid.as_deref(), None);
self.maybe_fetch_post_auth_settings().await;
Ok(self.auth_response_with_meta())
}
auth_method::KIGI_COM_METHOD_ID => {
@@ -517,21 +503,15 @@ impl acp::Agent for MvpAgent {
);
}
self.auth_manager.hot_swap(auth.clone());
self.refresh_remote_settings(&auth).await;
self.emit_settings_update_notification();
self.maybe_sync_bundle_in_background(false);
self.set_auth_method(arguments.method_id.clone());
self.set_auth_method(arguments.method_id.clone());
self.models_manager.on_auth_changed().await;
if crate::agent::chat_modes::process_chat_mode_enabled() {
self.chat_modes.warm_in_background();
}
emit_login_span(
true,
arguments.method_id.0.as_ref(),
Some(auth.user_id.as_str()),
None,
);
self.maybe_fetch_post_auth_settings().await;
Ok(self.auth_response_with_meta())
}
_ => {
@@ -559,9 +539,7 @@ impl acp::Agent for MvpAgent {
.data("initialize must be called before new_session")
})?;
self.seed_client_config_auth_if_available();
if let Ok(auth) = self.auth_manager.auth().await {
self.refresh_settings_and_reapply(&auth).await;
}
self.refresh_settings_and_reapply().await;
let cwd = AbsPathBuf::new(arguments.cwd.clone())
.map_err(|e| acp::Error::invalid_params().data(e.to_string()))?;
let remote_settings = self.cfg.borrow().remote_settings.clone();
@@ -858,8 +836,10 @@ impl acp::Agent for MvpAgent {
Some(serde_json::json!({ "cwd" : cwd.as_str() })),
);
let models = if is_chat_kind {
// The grok.com chat-mode model picker was removed with the xAI
// proxy; a chat-kind session has no managed catalog to offer.
chat_new_session_model_state(
self.chat_modes.model_state().await,
acp::SessionModelState::new(acp::ModelId::from(String::new()), Vec::new()),
session_initial_model
.filter(|_| matches!(bridge_attach, BridgeAttach::Spawned)),
)
@@ -982,14 +962,6 @@ impl acp::Agent for MvpAgent {
.build_summary_client(&load_session_sampling)?;
let mut persistence_timer = crate::instrumentation_timer!("session.load_light");
persistence_timer.with_field("session_id", session_id.0.as_ref());
let backend = if self.build_registry_config().is_some() {
Some(
crate::remote::BackendClient::new()
.with_auth_manager(self.auth_manager.clone()),
)
} else {
None
};
let registry_title_sync = self
.session_registry_client()
.map(|client| crate::session::persistence::RegistryGeneratedTitleSync {
@@ -999,9 +971,6 @@ impl acp::Agent for MvpAgent {
let (persistence_info, persistence) = crate::session::persistence::load_light(
&session_info,
summary_client,
self.storage_mode,
Some(self.auth_manager.clone()),
backend.as_ref(),
Some(self.gateway.clone()),
summary_model,
registry_title_sync,
@@ -2095,9 +2064,6 @@ impl acp::Agent for MvpAgent {
| "x.ai/sessions/list" => {
crate::agent::handlers::session::handle(self, &args).await
}
"x.ai/workspaces/list" => {
crate::agent::handlers::workspaces::handle(self, &args).await
}
"x.ai/session/updates" => {
crate::extensions::session_updates::handle(&args, &self.gateway).await
}
@@ -2122,6 +2088,7 @@ impl acp::Agent for MvpAgent {
crate::extensions::session_admin::handle(self, &args).await
}
"x.ai/session/repair" => crate::extensions::repair::handle(self, &args).await,
"x.ai/billing" => crate::extensions::billing::handle(self, &args).await,
"x.ai/memory/flush" | "x.ai/memory/rewrite" => {
crate::extensions::memory::handle(self, &args).await
}
@@ -2136,206 +2103,6 @@ impl acp::Agent for MvpAgent {
crate::extensions::feedback::handle(self, &args).await
}
"x.ai/recap" => crate::extensions::recap::handle(self, &args).await,
"x.ai/cloud/terminate" => {
crate::extensions::auth_gate::require_xai_auth(
&self.auth_manager,
"Authentication required",
"Run `grok login` to authenticate.",
)?;
let params: serde_json::Value = serde_json::from_str(args.params.get())
.map_err(|e| acp::Error::invalid_params().data(e.to_string()))?;
let sandbox_id = params
.get("sandbox_id")
.and_then(|v| v.as_str())
.ok_or_else(|| {
acp::Error::invalid_params().data("missing sandbox_id")
})?;
let sandbox_client = crate::remote::SandboxClient::new(
self.cli_chat_proxy_base_url(),
self.auth_manager.clone(),
);
sandbox_client
.terminate_session(
sandbox_id,
&crate::remote::SandboxTerminateRequest {
environment_id: None,
},
)
.await
.map_err(|e| {
acp::Error::internal_error()
.data(format!("Failed to terminate sandbox: {e}"))
})?;
crate::extensions::to_raw_response(&serde_json::json!({ "ok" : true }))
}
"x.ai/cloud/env/list" => {
crate::extensions::auth_gate::require_xai_auth(
&self.auth_manager,
"Authentication required",
"Run `grok login` to authenticate.",
)?;
let sandbox_client = crate::remote::SandboxClient::new(
self.cli_chat_proxy_base_url(),
self.auth_manager.clone(),
);
let resp = sandbox_client
.list_environments(
&crate::remote::SandboxListEnvironmentsRequest::default(),
)
.await
.map_err(|e| {
acp::Error::internal_error()
.data(format!("Failed to list environments: {e}"))
})?;
crate::extensions::to_raw_response(
&serde_json::json!({ "environments" : resp.environments, }),
)
}
"x.ai/cloud/env/create" => {
crate::extensions::auth_gate::require_xai_auth(
&self.auth_manager,
"Authentication required",
"Run `grok login` to authenticate.",
)?;
let params: serde_json::Value = serde_json::from_str(args.params.get())
.map_err(|e| acp::Error::invalid_params().data(e.to_string()))?;
let sandbox_client = crate::remote::SandboxClient::new(
self.cli_chat_proxy_base_url(),
self.auth_manager.clone(),
);
let resp = sandbox_client
.create_environment(
&crate::remote::SandboxCreateEnvironmentRequest {
name: params
.get("name")
.and_then(|v| v.as_str())
.map(String::from),
description: params
.get("description")
.and_then(|v| v.as_str())
.map(String::from),
repository: params
.get("repository")
.and_then(|v| v.as_str())
.map(String::from),
default_branch: params
.get("default_branch")
.and_then(|v| v.as_str())
.map(String::from),
container_image: params
.get("container_image")
.and_then(|v| v.as_str())
.map(String::from),
setup_script: params
.get("setup_script")
.and_then(|v| v.as_str())
.map(String::from),
workspace_directory: Some("/workspace".to_string()),
internet_enabled: Some(true),
domain_allowlist_preset: Some("common".to_string()),
allowed_http_methods: Some("all".to_string()),
..Default::default()
},
)
.await
.map_err(|e| {
acp::Error::internal_error()
.data(format!("Failed to create environment: {e}"))
})?;
crate::extensions::to_raw_response(
&serde_json::json!({ "environment" : resp.environment, }),
)
}
"x.ai/cloud/env/update" => {
crate::extensions::auth_gate::require_xai_auth(
&self.auth_manager,
"Authentication required",
"Run `grok login` to authenticate.",
)?;
let params: serde_json::Value = serde_json::from_str(args.params.get())
.map_err(|e| acp::Error::invalid_params().data(e.to_string()))?;
let environment_id = params
.get("environment_id")
.and_then(|v| v.as_str())
.ok_or_else(|| {
acp::Error::invalid_params().data("missing environment_id")
})?;
let sandbox_client = crate::remote::SandboxClient::new(
self.cli_chat_proxy_base_url(),
self.auth_manager.clone(),
);
let resp = sandbox_client
.update_environment(
environment_id,
&crate::remote::SandboxUpdateEnvironmentRequest {
name: params
.get("name")
.and_then(|v| v.as_str())
.map(String::from),
description: params
.get("description")
.and_then(|v| v.as_str())
.map(String::from),
repository: params
.get("repository")
.and_then(|v| v.as_str())
.map(String::from),
default_branch: params
.get("default_branch")
.and_then(|v| v.as_str())
.map(String::from),
container_image: params
.get("container_image")
.and_then(|v| v.as_str())
.map(String::from),
setup_script: params
.get("setup_script")
.and_then(|v| v.as_str())
.map(String::from),
..Default::default()
},
)
.await
.map_err(|e| {
acp::Error::internal_error()
.data(format!("Failed to update environment: {e}"))
})?;
crate::extensions::to_raw_response(
&serde_json::json!({ "environment" : resp.environment, }),
)
}
"x.ai/cloud/env/delete" => {
crate::extensions::auth_gate::require_xai_auth(
&self.auth_manager,
"Authentication required",
"Run `grok login` to authenticate.",
)?;
let params: serde_json::Value = serde_json::from_str(args.params.get())
.map_err(|e| acp::Error::invalid_params().data(e.to_string()))?;
let environment_id = params
.get("environment_id")
.and_then(|v| v.as_str())
.ok_or_else(|| {
acp::Error::invalid_params().data("missing environment_id")
})?;
let sandbox_client = crate::remote::SandboxClient::new(
self.cli_chat_proxy_base_url(),
self.auth_manager.clone(),
);
sandbox_client
.delete_environment(environment_id)
.await
.map_err(|e| {
acp::Error::internal_error()
.data(format!("Failed to delete environment: {e}"))
})?;
crate::extensions::to_raw_response(&serde_json::json!({ "ok" : true }))
}
"x.ai/billing" => crate::extensions::billing::handle(self, &args).await,
"x.ai/auto-topup-rule" => {
crate::extensions::billing::handle(self, &args).await
}
"x.ai/share_session" => crate::extensions::share::handle(self, &args).await,
"x.ai/rollout/survey" => {
crate::extensions::rollout::handle(self, &args).await
}
@@ -2395,9 +2162,6 @@ impl acp::Agent for MvpAgent {
s if s.starts_with("x.ai/search/") => {
crate::extensions::search::handle(self, &args).await
}
s if s.starts_with("x.ai/bundle/") => {
crate::extensions::bundle::handle(self, &args).await
}
s if s.starts_with("x.ai/code/") => {
let ops = self.resolve_workspace_ops()?;
crate::extensions::code_nav::handle(self, &ops, &args).await
@@ -28,23 +28,15 @@ impl MvpAgent {
let session_key = self.auth_manager.current_or_expired().map(|a| a.key.clone());
let models = self.models_manager.models();
let endpoints = self.models_manager.endpoints();
let (alpha_test_key, client_version) = {
let cfg = self.cfg.borrow();
(
cfg.endpoints.alpha_test_key.clone(),
cfg.client_version.clone(),
)
};
let alpha_test_key = self.cfg.borrow().endpoints.alpha_test_key.clone();
let config = match crate::agent::config::resolve_aux_model_sampling_config(
&slug,
&models,
&endpoints,
session_key.as_deref(),
alpha_test_key,
client_version,
) {
Some(mut cfg) => {
cfg.client_identifier = primary.client_identifier.clone();
cfg.attribution_callback = primary.attribution_callback.clone();
cfg.bearer_resolver = primary.bearer_resolver.clone();
cfg.max_retries = primary.max_retries;
@@ -60,10 +52,6 @@ impl MvpAgent {
let client = OaiCompatClient::new(config).map_err(map_sampling_err_to_acp)?;
Ok((client, model))
}
fn has_proxy_credentials(&self) -> bool {
self.cfg.borrow().endpoints.deployment_key.is_some()
|| self.auth_manager.current_or_expired().is_some_and(|a| a.is_session_auth())
}
/// `true` for session-based ACP auth methods.
fn is_session_based_auth(&self) -> bool {
self.auth_method_id
@@ -384,39 +372,23 @@ impl MvpAgent {
);
}
}
/// Extract feedback credentials when proxy credentials are available.
///
/// Returns `(base_url, user_token, optional_extra_access_key, deployment_key)`.
/// Used by both [`feedback_client`] and session spawning to avoid
/// duplicating the credential assembly logic.
#[allow(clippy::type_complexity)]
fn feedback_credentials(
&self,
) -> Option<(String, Option<String>, Option<String>, Option<String>)> {
if !self.has_proxy_credentials() {
return None;
}
let user_token = self
/// Feedback endpoint base when this is a subscription (OAuth) session —
/// the Kimi Code feedback endpoint only takes the OAuth Bearer, so
/// API-key-only setups get `None` (they are pointed at the issue
/// tracker instead; kimi-cli slash.py parity).
fn feedback_base_url(&self) -> Option<String> {
let has_session = self
.auth_manager
.current_or_expired()
.filter(|a| a.is_session_auth())
.map(|a| a.key.clone());
let cfg = self.cfg.borrow();
let base_url = cfg.endpoints.resolve_feedback_base_url();
let alpha_test_key = cfg.endpoints.alpha_test_key.clone();
let deployment_key = cfg.endpoints.deployment_key.clone();
Some((base_url, user_token, alpha_test_key, deployment_key))
.is_some_and(|a| a.is_session_auth());
has_session.then(|| self.cfg.borrow().endpoints.resolve_feedback_base_url())
}
/// Build a `FeedbackClient` with resolved feedback URL and credentials.
/// Build a `FeedbackClient` for subscription sessions.
pub(crate) fn feedback_client(&self) -> Option<FeedbackClient> {
let (base_url, user_token, alpha_test_key, deployment_key) = self
.feedback_credentials()?;
Some(
FeedbackClient::new(base_url, user_token)
.with_alpha_test_key(alpha_test_key)
.with_deployment_key(deployment_key)
.with_auth_manager(self.auth_manager.clone()),
)
Some(FeedbackClient::new(
self.feedback_base_url()?,
self.auth_manager.clone(),
))
}
/// Build a `RegistryConfig` if the feature is enabled (for passing to persistence actor).
pub(super) fn build_registry_config(
@@ -460,17 +432,6 @@ impl MvpAgent {
.with_auth(self.auth_manager.clone()),
)
}
pub(crate) fn conversations_client(
&self,
) -> Option<crate::remote::ConversationsClient> {
if !crate::session::unified_list::conversations_lane_active() {
return None;
}
Some(crate::remote::ConversationsClient::new(self.auth_manager.clone()))
}
pub(crate) fn workspaces_client(&self) -> crate::remote::WorkspacesClient {
crate::remote::WorkspacesClient::new(self.auth_manager.clone())
}
/// Pre-session command availability snapshot.
///
/// Used by the `x.ai/commands/list` ext method and the
@@ -515,13 +476,9 @@ impl MvpAgent {
) -> &kigi_agent::plugins::SharedPluginRegistryHandle {
&self.plugin_registry_handle
}
/// `true` when the agent runs in writeback storage mode.
pub(crate) fn is_writeback_storage(&self) -> bool {
matches!(self.storage_mode, StorageMode::Writeback)
}
/// Resolved cli-chat-proxy base for session features (via
/// `proxy_url`). Not for the deployment-config fetch.
pub(crate) fn cli_chat_proxy_base_url(&self) -> String {
pub(crate) fn coding_api_base_url(&self) -> String {
self.cfg.borrow().endpoints.proxy_url()
}
pub(crate) fn alpha_test_key(&self) -> Option<String> {
@@ -635,54 +592,14 @@ impl MvpAgent {
pub(crate) fn deployment_key(&self) -> Option<String> {
self.cfg.borrow().endpoints.deployment_key.clone()
}
/// Re-fetch remote settings and re-init the telemetry client.
///
/// Called unconditionally from both auth handlers so that:
/// - First install / expired OIDC token: settings are fetched for
/// the first time (the early prefetch had no auth to use).
/// - Reauth / account switch: settings are refreshed to reflect
/// the new user's remote settings targeting attributes.
///
/// This only refreshes `cfg.remote_settings` and re-inits the
/// telemetry client (the only global static). Other settings
/// derived from `remote_settings` (`web_fetch_enabled`, etc.) are
/// resolved lazily per-turn from `cfg` and pick up the new values
/// automatically.
/// Agent-level fields materialised at startup (`worktree_type`,
/// `restore_code`) are NOT re-resolved here; that requires a
/// broader refactor of the init path.
pub(super) async fn refresh_remote_settings(&self, auth: &crate::auth::KimiAuth) {
if !crate::util::config::resolve_remote_fetch_enabled() {
tracing::debug!("post-auth settings refresh skipped: remote_fetch disabled");
return;
}
let Some(settings) = self.fetch_remote_settings(auth.clone()).await else {
tracing::warn!("post-auth settings refresh failed (HTTP or parse error)");
return;
};
tracing::info!("post-auth settings refreshed");
{
let mut cfg = self.cfg.borrow_mut();
cfg.remote_settings = Some(settings);
crate::util::config::sync_campaign_fields(&mut cfg);
crate::agent::config::apply_remote_settings_side_effects(
cfg.remote_settings.as_ref(),
);
}
}
/// Refresh remote settings settings and re-resolve eagerly-resolved config fields.
/// Re-resolve eagerly-resolved config fields from the local config.
///
/// Called on `/new` session creation so feature flags reflect the latest
/// remote settings state without requiring a TUI restart. Extends
/// [`refresh_remote_settings`] by also re-running [`resolve_runtime_fields`]
/// with the fresh settings.
/// on-disk config without requiring a TUI restart. (Formerly this also
/// re-fetched the xAI proxy's remote settings; that endpoint is gone.)
///
/// In-flight sessions are unaffected — they snapshot config at creation.
pub(super) async fn refresh_settings_and_reapply(
&self,
auth: &crate::auth::KimiAuth,
) {
self.refresh_remote_settings(auth).await;
pub(super) async fn refresh_settings_and_reapply(&self) {
let cwd = std::env::current_dir().ok();
{
let mut cfg = self.cfg.borrow_mut();
@@ -698,36 +615,6 @@ impl MvpAgent {
}
self.emit_settings_update_notification();
}
/// Shared fetch half of every settings refresh: endpoint fields from a
/// scoped `cfg` borrow, `fetch_settings_blocking` off-executor (it already
/// retries transient errors internally), failures normalized to `None`.
/// Callers own their miss logging.
pub(super) async fn fetch_remote_settings(
&self,
auth: crate::auth::KimiAuth,
) -> Option<crate::util::config::RemoteSettings> {
if !crate::util::config::resolve_remote_fetch_enabled() {
tracing::debug!("settings fetch skipped: remote_fetch disabled");
return None;
}
let (base_url, alpha_test_key) = {
let cfg = self.cfg.borrow();
(cfg.endpoints.proxy_url(), cfg.endpoints.alpha_test_key.clone())
};
match tokio::task::spawn_blocking(move || crate::remote::fetch_settings_blocking(
&base_url,
&auth,
alpha_test_key.as_deref(),
))
.await
{
Ok(settings) => settings,
Err(e) => {
tracing::warn!(error = % e, "settings fetch task panicked");
None
}
}
}
pub(super) async fn send_model_auto_switched(
&self,
session_id: &acp::SessionId,
@@ -828,26 +715,9 @@ impl MvpAgent {
),
);
}
let cfg = self.cfg.borrow();
let alpha_test_key = cfg.endpoints.alpha_test_key.clone();
let client_version = cfg.client_version.clone();
let deployment_id = crate::managed_config::resolve_deployment_id(
cfg.endpoints.deployment_key.as_deref(),
);
drop(cfg);
let user_id = self
.auth_manager
.current_or_expired()
.filter(|a| a.is_session_auth())
.map(|a| a.user_id);
let mut config = crate::agent::config::sampling_config_for_model(
model,
credentials,
alpha_test_key,
client_version,
deployment_id,
user_id,
);
let alpha_test_key = self.cfg.borrow().endpoints.alpha_test_key.clone();
let mut config =
crate::agent::config::sampling_config_for_model(model, credentials, alpha_test_key);
config.origin_client = origin_client;
config
}
@@ -912,13 +782,7 @@ impl MvpAgent {
.unwrap_or_else(|| kigi_version::VERSION.to_string());
let alpha_test_key = cfg.endpoints.alpha_test_key.clone();
let mut headers = indexmap::IndexMap::new();
headers.insert("user-agent".to_string(), format!("xai-grok-build/{version}"));
inject_proxy_headers(
&mut headers,
cfg.client_version.as_deref(),
alpha_test_key.as_deref(),
&base_url,
);
headers.insert("user-agent".to_string(), format!("kigi/{version}"));
ImageGenConfig::Enabled {
api_key: api_key.clone(),
base_url,
@@ -961,13 +825,7 @@ impl MvpAgent {
.unwrap_or_else(|| kigi_version::VERSION.to_string());
let alpha_test_key = cfg.endpoints.alpha_test_key.clone();
let mut headers = indexmap::IndexMap::new();
headers.insert("user-agent".to_string(), format!("xai-grok-build/{version}"));
inject_proxy_headers(
&mut headers,
cfg.client_version.as_deref(),
alpha_test_key.as_deref(),
&base_url,
);
headers.insert("user-agent".to_string(), format!("kigi/{version}"));
VideoGenConfig::Enabled {
api_key,
base_url,
@@ -987,15 +845,8 @@ impl MvpAgent {
&models,
session.as_ref().map(|a| a.key.as_str()),
alpha_test_key.clone(),
client_version,
&self.cfg.borrow().endpoints,
)?;
inject_proxy_headers(
&mut cfg.extra_headers,
cfg.client_version.as_deref(),
alpha_test_key.as_deref(),
&cfg.base_url,
);
Some(cfg)
}
/// Returns `Err` with a user-facing message on invalid config; the caller at
@@ -1112,15 +963,6 @@ impl MvpAgent {
.map(|(name, p)| p.render_io_summary(name))
.collect(),
models_manager,
chat_modes: {
let chat_modes = crate::agent::chat_modes::ChatModesManager::new(
auth_manager.clone(),
);
if crate::agent::chat_modes::process_chat_mode_enabled() {
chat_modes.warm_in_background();
}
chat_modes
},
cfg: RefCell::new(cfg.clone()),
auth_method_id: crate::agent::auth_method::new_shared_auth_method_id(None),
sampling_config: RefCell::new(sampling_config),
@@ -1159,7 +1001,6 @@ impl MvpAgent {
subagent_event_rx: RefCell::new(Some(subagent_event_rx)),
subagent_coordinator: RefCell::new(subagent_coordinator),
monitor_event_buffer: kigi_tools::implementations::grok_build::task::types::MonitorEventBuffer::default(),
bundle_sync_in_flight: Arc::new(std::sync::atomic::AtomicBool::new(false)),
workspace_ops: RefCell::new(None),
require_gateway_sessions: Rc::new(
RefCell::new(std::collections::HashSet::new()),
@@ -2221,19 +2062,9 @@ impl MvpAgent {
let auto_update = self.cfg.borrow().cli.auto_update;
let client_type = *self.client_type.borrow();
let buffering_settings = self.buffering_settings.borrow().clone();
let (
feedback_proxy_url,
feedback_user_token,
feedback_alpha_test_key,
deployment_key,
) = if let Some((url, token, alpha, deploy)) = self.feedback_credentials() {
(Some(url), token, alpha, deploy)
} else {
(None, None, None, None)
};
let feedback_base_url = self.feedback_base_url();
tracing::info!(
session_id = % session_info.id.0, feedback_url = ? feedback_proxy_url,
authenticated = feedback_user_token.is_some(),
session_id = % session_info.id.0, feedback_url = ? feedback_base_url,
"Initializing feedback manager for session"
);
let skills = self.cfg.borrow().skills.clone();
@@ -2489,7 +2320,6 @@ impl MvpAgent {
self.auth_type(),
),
alpha_test_key: self.alpha_test_key(),
client_version: sampling_config.client_version.clone(),
};
let attribution_callback: Option<
kigi_sampler::SharedAttributionCallback,
@@ -2599,10 +2429,7 @@ impl MvpAgent {
self.codebase_indexes.clone(),
client_code_nav_enabled,
fs_watch_caps,
feedback_proxy_url,
feedback_user_token,
feedback_alpha_test_key,
deployment_key,
feedback_base_url,
client_terminal,
client_fs_read && client_fs_write,
gateway_enabled,
@@ -2617,7 +2444,6 @@ impl MvpAgent {
persisted_goal_mode,
persisted_announcement_state,
self.memory_config.clone(),
loc_tracking_enabled,
feedback_flags,
self.managed_mcp_cache.clone(),
managed_mcp_expires_at,
@@ -406,17 +406,11 @@ struct SettingsUpdateNotification {
sharing_enabled: Option<bool>,
session_picker_grouped: Option<bool>,
tips: Option<Vec<String>>,
gate_message: Option<String>,
gate_url: Option<String>,
gate_label: Option<String>,
allow_access: Option<bool>,
subscription_tier_display: Option<String>,
auto_permission_mode_enabled: Option<bool>,
/// Soft-default permission mode for the pager (post-auth / `/new` refresh).
permission_mode: Option<String>,
group_tool_verbs: Option<bool>,
collapsed_edit_blocks: Option<bool>,
subscription_watch_interval_secs: Option<u64>,
}
/// Reason why a client is not eligible to use codebase indexing.
///
@@ -509,9 +503,6 @@ pub struct MvpAgent {
pub(crate) sampling_config: RefCell<SamplingConfig>,
pub(crate) auth_manager: Arc<AuthManager>,
pub(crate) models_manager: crate::agent::models::ModelsManager,
/// grok.com chat-product catalog (`/rest/modes`) for chat sessions; distinct
/// from `models_manager` (the build `/v1/models` catalog).
pub(crate) chat_modes: crate::agent::chat_modes::ChatModesManager,
/// Forwards pasted codes from `handle_auth_submit_code` to the auth flow.
pub(crate) auth_code_tx: RefCell<Option<tokio::sync::mpsc::Sender<String>>>,
/// Receives the auth URL from the auth flow; read by `handle_auth_get_url`.
@@ -672,20 +663,6 @@ pub struct MvpAgent {
/// this flag keeps that to a single discovery walk.
plugin_registry_initialized: std::cell::Cell<bool>,
persona_io_summaries: Vec<String>,
/// Single-flight guard for the proactive bundle sync background task.
///
/// `maybe_sync_bundle_in_background` is invoked from each post-auth path
/// (initialize, cached-token reauth, oidc) and a rapid reconnect can fire
/// all three within the TTL window, giving us multiple concurrent
/// `tokio::task::spawn_local` tasks racing to extract the tar archive,
/// rewrite `manifest.json`, and prune stale files. The non-atomic
/// per-file write/prune semantics in `bundle::extract_bundle_archive`
/// make that race observable as a partially-written cache.
///
/// We use an `Arc<AtomicBool>` so the spawned task can clear the flag
/// on completion without re-borrowing `&self`. `Send` is required
/// because the inner `sync_bundle_to_root` now uses `spawn_blocking`.
bundle_sync_in_flight: Arc<std::sync::atomic::AtomicBool>,
/// Local workspace ops, built lazily via [`Self::ensure_local_workspace_ops`].
/// The agent never opens Computer Hub as a harness/client; remote cloud
/// sandboxes are gateway-owned (`gateway_bridge` / `computer_sessions`).
@@ -944,50 +921,6 @@ impl AuthRequestMeta {
.unwrap_or_default()
}
}
/// Inject standard proxy headers into an `extra_headers` map.
///
/// Every authenticated request to cli-chat-proxy (web search, image gen, and
/// any future tools that go through the proxy) must carry these headers.
/// Centralising them here means new tool code paths only need one call instead
/// of remembering which headers the proxy expects.
///
/// Headers injected:
/// - `x-grok-client-version` -- required by the proxy's version-gate check.
/// Uses `client_version` when provided, otherwise falls back to cli-chat-proxy
/// compile-time `CARGO_PKG_VERSION`.
/// - `X-XAI-Token-Auth` / `x-authenticateresponse` -- required by the
/// cli-chat-proxy auth middleware when the `base_url` is a known proxy URL.
/// - optional extra access header -- only set when the corresponding key is
/// `Some` *and* the `base_url` points at a matching non-production host
/// (requires the optional non-production feature).
///
/// Existing entries are never overwritten so callers can pre-set a value.
fn inject_proxy_headers(
headers: &mut indexmap::IndexMap<String, String>,
client_version: Option<&str>,
alpha_test_key: Option<&str>,
base_url: &str,
) {
headers
.entry("x-grok-client-version".to_string())
.or_insert_with(|| {
client_version
.map(String::from)
.unwrap_or_else(|| kigi_version::VERSION.to_string())
});
if crate::util::is_cli_chat_proxy_url(base_url) {
headers
.entry("X-XAI-Token-Auth".to_string())
.or_insert_with(|| "xai-grok-cli".to_string());
headers
.entry("x-authenticateresponse".to_string())
.or_insert_with(|| "authenticate-response".to_string());
headers
.entry(crate::http::CLIENT_MODE_HEADER.to_string())
.or_insert_with(|| crate::http::process_client_mode().to_string());
}
let _ = (alpha_test_key, base_url);
}
fn resolve_inference_idle_timeout_secs(
models: &indexmap::IndexMap<String, crate::agent::config::ModelEntry>,
model: &str,
@@ -1580,47 +1513,6 @@ impl MvpAgent {
});
AuthenticateResponse::new().meta(meta)
}
/// Fetch remote settings after authentication when early prefetch had none.
/// Notifies the pager so soft-default permission_mode applies post-login.
pub(super) async fn maybe_fetch_post_auth_settings(&self) {
if self.cfg.borrow().remote_settings.is_some() {
return;
}
let Some(auth) = self.auth_manager.current() else {
return;
};
let is_session_auth = auth.is_session_auth();
let Some(settings) = self.fetch_remote_settings(auth).await else {
return;
};
tracing::info!("post-auth remote_settings fetch succeeded");
{
let mut cfg = self.cfg.borrow_mut();
cfg.remote_settings = Some(settings);
crate::agent::config::apply_remote_settings_side_effects(
cfg.remote_settings.as_ref(),
);
if cfg.storage_mode == StorageMode::Local
&& cfg.mode != crate::agent::config::AgentMode::Generic
{
cfg.storage_mode = StorageMode::resolve(
None,
cfg.remote_settings.as_ref(),
);
if cfg.storage_mode == StorageMode::Writeback && !is_session_auth {
cfg.storage_mode = StorageMode::Local;
}
}
if let Some(v) = cfg
.remote_settings
.as_ref()
.and_then(|s| s.path_not_found_hints)
{
cfg.path_not_found_hints = v;
}
}
self.emit_settings_update_notification();
}
/// Fire-and-forget `x.ai/settings/update` from the current remote snapshot.
pub(super) fn emit_settings_update_notification(&self) {
let payload = {
@@ -1631,20 +1523,12 @@ impl MvpAgent {
sharing_enabled: rs.and_then(|s| s.sharing_enabled),
session_picker_grouped: rs.and_then(|s| s.session_picker_grouped),
tips: rs.and_then(|s| s.tips.clone()),
gate_message: rs.and_then(|s| s.gate_message.clone()),
gate_url: rs.and_then(|s| s.gate_url.clone()),
gate_label: rs.and_then(|s| s.gate_label.clone()),
allow_access: rs.and_then(|s| s.allow_access),
subscription_tier_display: rs
.and_then(|s| s.subscription_tier_display.clone()),
auto_permission_mode_enabled: crate::util::config::remote_auto_mode_enabled(
rs,
),
permission_mode: rs.and_then(|s| s.permission_mode.clone()),
group_tool_verbs: rs.and_then(|s| s.group_tool_verbs),
collapsed_edit_blocks: rs.and_then(|s| s.collapsed_edit_blocks),
subscription_watch_interval_secs: rs
.and_then(|s| s.subscription_watch_interval_secs),
}
};
if let Ok(params) = serde_json::value::to_raw_value(&payload) {
@@ -1719,78 +1603,6 @@ impl MvpAgent {
});
}
}
/// Spawn a best-effort bundle sync. Re-fires on every call site (init,
/// cached_token, grok.com/oidc); the cheap pre-checks below absorb repeats
/// so reconnects are cheap.
///
/// Pre-spawn gating order (cheapest first, all synchronous):
/// 1. Auth gate — avoid spawning a no-op task on every init.
/// 2. Freshness check — skip the sender snapshot + spawn entirely on
/// cache hits, which is the steady-state on every reconnect.
/// 3. Single-flight guard — if a previous sync is still in flight (e.g.,
/// initialize + cached_token + oidc fired in quick succession before
/// the first sync's tar extract finished), drop this call to avoid
/// racing concurrent extracts that would interleave per-file writes
/// against `~/.kigi/bundled/` and the manifest.
pub(crate) fn maybe_sync_bundle_in_background(&self, force: bool) {
use crate::extensions::bundle::{
BUNDLE_SYNC_TTL, bundle_cache_is_fresh, has_bundle_credentials,
maybe_sync_bundle_to_root,
};
use std::sync::atomic::Ordering;
let am = self.auth_manager.clone();
let deployment_key = self.deployment_key();
if !has_bundle_credentials(Some(&am), deployment_key.as_deref()) {
return;
}
let root = crate::bundle::bundled_root();
if !force && bundle_cache_is_fresh(&root, BUNDLE_SYNC_TTL) {
tracing::debug!("proactive bundle sync skipped pre-spawn: cache is fresh");
return;
}
let in_flight = self.bundle_sync_in_flight.clone();
if in_flight
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
tracing::debug!(
"proactive bundle sync skipped: another sync is already in flight"
);
return;
}
let proxy_base_url = self.cli_chat_proxy_base_url();
let alpha_test_key = self.alpha_test_key();
let senders: Vec<
tokio::sync::mpsc::UnboundedSender<crate::session::SessionCommand>,
> = self.sessions.borrow().values().map(|h| h.cmd_tx.clone()).collect();
tokio::task::spawn_local(async move {
let result = maybe_sync_bundle_to_root(
&root,
&proxy_base_url,
Some(&am),
deployment_key.as_deref(),
alpha_test_key.as_deref(),
force,
BUNDLE_SYNC_TTL,
)
.await;
in_flight.store(false, Ordering::Release);
match result {
Ok(Some(res)) => {
tracing::info!(
version = % res.version, personas = res.personas_count, roles =
res.roles_count, agents = res.agents_count, skills = res
.skills_count, "proactive bundle sync complete"
);
Self::broadcast_refresh_skill_baseline(senders);
}
Ok(None) => {}
Err(err) => {
tracing::warn!(error = % err, "proactive bundle sync failed");
}
}
});
}
}
/// Parse `_meta.agentProfile` as a JSON object or string name.
/// Returns `None` if absent or invalid.
@@ -402,7 +402,7 @@ impl MvpAgent {
client_hooks: Default::default(),
sampling_config: self.sampling_config.borrow().clone(),
managed_mcp_proxy_base_url: parent_managed_mcp_proxy_base_url
.unwrap_or_else(|| self.cli_chat_proxy_base_url()),
.unwrap_or_else(|| self.coding_api_base_url()),
alpha_test_key: self.alpha_test_key(),
auth_method_id: self
.auth_method_id
@@ -1822,18 +1822,6 @@ fn orphaned_tasks_filters_rewind_dead_branches() {
);
}
#[test]
fn allow_access_from_remote_settings() {
let json = serde_json::json!({ "allow_access" : true });
let rs: crate::util::config::RemoteSettings = serde_json::from_value(json).unwrap();
assert_eq!(rs.allow_access, Some(true));
let json = serde_json::json!({ "allow_access" : false });
let rs: crate::util::config::RemoteSettings = serde_json::from_value(json).unwrap();
assert_eq!(rs.allow_access, Some(false));
let json = serde_json::json!({});
let rs: crate::util::config::RemoteSettings = serde_json::from_value(json).unwrap();
assert_eq!(rs.allow_access, None);
}
#[test]
fn on_demand_enabled_from_remote_settings() {
let json = serde_json::json!({ "on_demand_enabled" : false });
let rs: crate::util::config::RemoteSettings = serde_json::from_value(json).unwrap();