M2 audit: excise managed connectors and xAI media-gen tools

Managed connectors (grok.com MCP admin) removed root-and-branch:
- The managed-MCP fetch/injection pipeline is gone, including the whole
  kigi-shell-session-support crate (managed-config fetch client, gateway
  tool catalog + dispatch, header injection, refresh task), reactive
  managed re-auth, mcp_doctor's grok.com-source discovery, and the
  [managed_mcps] config surface.
- TUI: the 'Managed by grok.com' section, connectors URL/deep-link,
  Action::OpenManagedConnectors, and session_team_id are gone. Local MCP
  management (list/toggle/add/remove/auth/tools) is fully intact.
- Kept as LOCAL policy: managed-settings.json MCP allow/deny enforcement,
  the multi-source local MCP merge, folder-trust gating. PluginOrigin
  Project/User labels kept (they tag locally discovered plugin dirs).

imagine/media-gen tools (xAI image/video generation) removed:
- image_gen, image_edit, video_gen, image_to_video, reference_to_video
  implementations, registrations, ToolKind/ToolInput/Output variants
  (serde-safe), config plumbing end to end, ZDR video machinery,
  /imagine + /imagine-video commands and guidance text, the bundled
  imagine skill (added to legacy cleanup so user installs delete it),
  and the media-gen render path.
- Kept: image INPUT (paste/attach, [Image #N] meta, pdf/image fetch,
  clipboard wrap), generic media-ref rendering, and the generic tool
  401-retry machinery (tests renamed, assertions unweakened).
- deploy_app stays: it is a permanently-disabled local stub deploying
  nowhere.

121 files changed, 8 deleted. Gates: workspace check/clippy 0/0, fmt,
deny ok; suites green (tools 2554, shell 4862, tui 6608, workspace
1042). Remaining grok.com strings live only in the auth-method ids and
changelog archives (§9/M3 sweep).
This commit is contained in:
2026-07-17 23:45:05 -04:00
parent fa75eb139a
commit 5e4e24db99
120 changed files with 301 additions and 11327 deletions
@@ -917,15 +917,6 @@ pub async fn run_leader(
}
ConfigUpdate::AuthCleared => {
auth_manager_for_config.clear_in_memory();
let line = internal_reload_request_line(
"config-auth-cleared",
"x.ai/internal/auth_cleared",
serde_json::json!({}),
);
let mut tx = acp_tx_for_config.lock().await;
if let Err(e) = tx.write_all(line.as_bytes()).await {
warn!(error = %e, "failed to inject auth-cleared cleanup into ACP stream");
}
models_manager_for_config.on_auth_changed().await;
kigi_log::unified_log::warn(
"auth cleared from disk",
@@ -1144,14 +1135,6 @@ mod tests {
);
let msg: serde_json::Value = serde_json::from_str(line.trim_end()).unwrap();
assert_eq!(msg["params"]["cwd"], "/repo/x");
let line = internal_reload_request_line(
"config-auth-cleared",
"x.ai/internal/auth_cleared",
serde_json::json!({}),
);
let msg: serde_json::Value = serde_json::from_str(line.trim_end()).unwrap();
assert_eq!(msg["method"], "_x.ai/internal/auth_cleared");
}
#[tokio::test]
@@ -301,9 +301,6 @@ pub struct Requirements {
pub tool_search: Constrained<bool>,
pub web_fetch: Constrained<bool>,
pub ask_user_question: Constrained<bool>,
pub image_gen: Constrained<bool>,
pub image_edit: Constrained<bool>,
pub video_gen: Constrained<bool>,
pub write_file: Constrained<bool>,
/// Voice dictation (STT). Pin via requirements/managed `[features] voice_mode`.
pub voice_mode: Constrained<bool>,
@@ -1075,8 +1072,6 @@ pub struct Config {
pub memory: crate::config::MemoryConfig,
#[serde(default, skip_serializing)]
pub compaction: CompactionConfig,
#[serde(default, skip_serializing)]
pub managed_mcps: crate::config::ManagedMcpsConfig,
/// `[auth]` alias — consumed by `expand_auth_alias` before serde.
/// Typed as `KimiCodeConfig` (same schema) so sub-field typos are caught.
#[serde(default, skip_serializing)]
@@ -1206,18 +1201,6 @@ pub struct Config {
/// Resolved by [`crate::config::ToolsConfig::resolve`].
#[serde(skip)]
pub respect_gitignore: bool,
/// When `true`, `MvpAgent::prepare_video_gen_config` returns
/// `VideoGenConfig::Disabled`, dropping `video_gen` (and any
/// future ZDR-incompatible tools) from the model's tool set.
/// Resolved by [`crate::config::ToolsConfig::resolve`].
#[serde(skip)]
pub disable_zdr_incompatible_tools: bool,
/// S3 config for ZDR video output (presigned upload to team bucket).
/// Only used when `disable_zdr_incompatible_tools` is `true` and the
/// config is valid. Resolved by [`crate::config::ToolsConfig::resolve`].
#[serde(skip)]
pub zdr_video_output_s3:
Option<kigi_tools::implementations::grok_build::video_gen::ZdrVideoOutputS3Config>,
/// Whether to enrich path-not-found errors with CWD reminders,
/// "dropped repo folder" correction, and similar-name suggestions.
/// Default `false`. Enabled via remote settings.
@@ -1225,13 +1208,6 @@ pub struct Config {
/// which sessions had path-not-found hints active.
#[serde(default)]
pub path_not_found_hints: bool,
/// Whether to fetch managed MCP configs from the managed connectors service at startup.
/// Resolved by [`crate::config::ManagedMcpsConfig::resolve`]: env var >
/// config.toml > remote settings > default (off in headless, on in interactive).
#[serde(skip)]
pub managed_mcps_enabled: bool,
#[serde(skip)]
pub managed_mcp_gateway_tools_enabled: bool,
/// Whether auto-wake is enabled: when a background task or subagent
/// completes, immediately inject a synthetic prompt instead of waiting
/// for the idle-gated notification drain.
@@ -1446,7 +1422,6 @@ impl Default for Config {
subagents: crate::config::SubagentsConfig::default(),
memory: crate::config::MemoryConfig::default(),
compaction: CompactionConfig::default(),
managed_mcps: crate::config::ManagedMcpsConfig::default(),
auth: None,
desktop: None,
tips: None,
@@ -1476,15 +1451,11 @@ impl Default for Config {
todo_gate: false,
laziness_debug_log: None,
respect_gitignore: false,
disable_zdr_incompatible_tools: false,
zdr_video_output_s3: None,
path_not_found_hints: false,
cli_experimental_memory: false,
cli_no_memory: false,
cli_subagents: None,
memory_config: None,
managed_mcps_enabled: true,
managed_mcp_gateway_tools_enabled: false,
auto_wake_enabled: true,
compat_resolved: CompatConfig::default(),
requirements: Requirements::default(),
@@ -1603,8 +1574,6 @@ impl Config {
/// Call immediately after `new_from_toml_cfg()`. Fields resolved:
/// - subagents (6 fields) via `SubagentsConfig::resolve`
/// - respect_gitignore via `ToolsConfig::resolve`
/// - disable_zdr_incompatible_tools via `ToolsConfig::resolve`
/// - managed_mcps_enabled via `ManagedMcpsConfig::resolve`
/// - session_summary_model / image_description_model /
/// prompt_suggest_model_pin via `ModelOverrideConfig::resolve`
/// - memory_config via `MemoryConfig::resolve`
@@ -1624,15 +1593,6 @@ impl Config {
Some(pinned) => pinned,
None => tools.respect_gitignore,
};
self.disable_zdr_incompatible_tools = tools.disable_zdr_incompatible_tools;
self.zdr_video_output_s3 = tools.zdr_video_output_s3;
let mcps = crate::config::ManagedMcpsConfig::resolve(
ctx.raw_config,
ctx.remote_settings,
ctx.is_headless,
);
self.managed_mcps_enabled = mcps.enabled;
self.managed_mcp_gateway_tools_enabled = mcps.gateway_tools_enabled;
let models = crate::config::ModelOverrideConfig::resolve(
ctx.cli_session_summary_model,
ctx.raw_config,
@@ -1867,51 +1827,6 @@ impl Config {
.default(true)
.resolve()
}
/// `image_gen` tool gate. Default on; gated only by the `KIGI_IMAGE_GEN`
/// env var and managed-config requirement pin.
pub(crate) fn resolve_image_gen(&self) -> Resolved<bool> {
BoolFlag::env("KIGI_IMAGE_GEN")
.requirement(self.requirements.image_gen.pinned())
.default(true)
.resolve()
}
/// `image_edit` tool gate.
///
/// The remote settings `imagine_tools_disabled` denylist is authoritative:
/// when it lists `image_edit`, the tool is force-removed and local
/// env/config can't re-enable it. A managed requirement pin still outranks
/// it; otherwise the tool defaults on and is overridable via
/// `KIGI_IMAGE_EDIT`.
pub(crate) fn resolve_image_edit(&self) -> Resolved<bool> {
use kigi_tools::implementations::grok_build::IMAGE_EDIT_TOOL_NAME;
if let Some(pinned) = self.requirements.image_edit.pinned() {
return Resolved::new(pinned, ConfigSource::Requirement);
}
if self
.remote_settings
.as_ref()
.is_some_and(|s| s.imagine_tool_disabled(IMAGE_EDIT_TOOL_NAME))
{
return Resolved::new(false, ConfigSource::Remote);
}
BoolFlag::env("KIGI_IMAGE_EDIT").default(true).resolve()
}
/// Optional Imagine model override for `image_gen`. When set (non-empty),
/// `image_gen` calls this model slug instead of the default quality model.
/// Precedence: env `KIGI_IMAGE_GEN_MODEL_OVERRIDE` > `[features]
/// image_gen_model_override` config > remote settings `image_gen_model_override`.
/// `None` → default model (`grok-imagine-image-quality`).
pub(crate) fn resolve_image_gen_model_override(&self) -> Option<String> {
resolve_string_flag(
None,
"KIGI_IMAGE_GEN_MODEL_OVERRIDE",
self.features.image_gen_model_override.as_deref(),
self.remote_settings
.as_ref()
.and_then(|s| s.image_gen_model_override.as_deref()),
)
.map(|r| r.value)
}
/// Goal mode (`/goal`) master switch. Default ON: deployments that can't
/// reach cli-chat-proxy `/v1/settings` (custom `models_base_url`, external
/// `auth_provider_command`, air-gapped proxies) never receive the
@@ -3646,13 +3561,6 @@ pub struct Features {
/// compaction. `None` = defer to remote settings / env / default (`false`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub two_pass_compaction: Option<bool>,
/// Video generation tool. `None` = defer to remote settings / env / default (false).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub video_gen: Option<bool>,
/// `image_gen` Imagine model override. `None`/empty = defer to remote settings
/// (`image_gen_model_override`) / env / default (`grok-imagine-image-quality`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub image_gen_model_override: Option<String>,
/// Write file tool. `None` = defer to remote settings / env / default (true).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub write_file: Option<bool>,
@@ -4472,34 +4380,6 @@ reasoning_effort = "low"
assert_eq!(cfg.toolset.bash.timeout_secs, Some(30.5));
}
#[test]
fn resolve_runtime_fields_propagates_disable_zdr_incompatible_tools() {
fn ctx(raw: &toml::Value) -> RuntimeResolutionContext<'_> {
RuntimeResolutionContext {
raw_config: raw,
remote_settings: None,
cwd: None,
is_headless: false,
cli_subagents: None,
cli_session_summary_model: None,
cli_experimental_memory: false,
cli_no_memory: false,
disable_web_search: false,
todo_gate: false,
laziness_debug_log: None,
storage_mode: None,
}
}
let empty: toml::Value = toml::Value::Table(toml::map::Map::new());
let mut cfg = Config::new_from_toml_cfg(&empty).unwrap();
cfg.resolve_runtime_fields(&ctx(&empty));
assert!(!cfg.disable_zdr_incompatible_tools);
let zdr: toml::Value =
toml::from_str("[tools]\ndisable_zdr_incompatible_tools = true").unwrap();
let mut cfg = Config::new_from_toml_cfg(&zdr).unwrap();
cfg.resolve_runtime_fields(&ctx(&zdr));
assert!(cfg.disable_zdr_incompatible_tools);
}
#[test]
fn resolve_runtime_fields_propagates_disable_web_search() {
fn ctx(raw: &toml::Value, disable_web_search: bool) -> RuntimeResolutionContext<'_> {
RuntimeResolutionContext {
@@ -7215,51 +7095,6 @@ reasoning_effort = "low"
assert_eq!(r.source, ConfigSource::Remote);
assert!(!r.value);
}
#[test]
#[serial]
fn resolve_image_gen_model_override_remote_settings_or_config() {
unsafe { std::env::remove_var("KIGI_IMAGE_GEN_MODEL_OVERRIDE") };
let with = |config: Option<&str>, gb: Option<&str>| Config {
features: Features {
image_gen_model_override: config.map(String::from),
..Default::default()
},
remote_settings: Some(crate::util::config::RemoteSettings {
image_gen_model_override: gb.map(String::from),
..Default::default()
}),
..Default::default()
};
assert_eq!(Config::default().resolve_image_gen_model_override(), None);
assert_eq!(
with(None, Some("grok-imagine-image")).resolve_image_gen_model_override(),
Some("grok-imagine-image".to_owned())
);
assert_eq!(
with(Some("grok-imagine-image-pro"), Some("grok-imagine-image"))
.resolve_image_gen_model_override(),
Some("grok-imagine-image-pro".to_owned())
);
}
#[test]
#[serial]
fn imagine_tools_disabled_gates_image_edit() {
unsafe { std::env::remove_var("KIGI_IMAGE_EDIT") };
let with_list = |tools: Vec<&str>| Config {
remote_settings: Some(crate::util::config::RemoteSettings {
imagine_tools_disabled: Some(tools.into_iter().map(String::from).collect()),
..Default::default()
}),
..Default::default()
};
unsafe { std::env::set_var("KIGI_IMAGE_EDIT", "1") };
let off = with_list(vec!["image_edit"]).resolve_image_edit();
assert!(!off.value);
assert_eq!(off.source, ConfigSource::Remote);
unsafe { std::env::remove_var("KIGI_IMAGE_EDIT") };
assert!(with_list(vec!["image_to_video"]).resolve_image_edit().value);
assert!(Config::default().resolve_image_edit().value);
}
/// Clear every env var the goal/companion resolvers read so tests
/// start from a known baseline regardless of run order.
fn clear_goal_envs() {
@@ -8103,8 +7938,6 @@ agent_type = "cursor"
secret = "value"
[worktree_pool]
pool_size = 4
[managed_mcps]
enabled = true
[mcp_servers.test]
url = "https://mcp.test.com"
[toolset.bash]
@@ -8239,12 +8072,6 @@ agent_type = "cursor"
std::env::remove_var("KIGI_CLAUDE_AGENTS_ENABLED");
}
}
fn clear_managed_mcp_env_vars() {
unsafe {
std::env::remove_var("KIGI_MANAGED_MCPS_ENABLED");
std::env::remove_var("KIGI_MANAGED_MCP_GATEWAY_TOOLS_ENABLED");
}
}
fn isolate_compat_env() -> Vec<EnvGuard> {
COMPAT_CELLS
.into_iter()
@@ -8531,7 +8358,6 @@ hooks = true
#[serial]
fn resolve_runtime_fields_interactive_defaults() {
clear_runtime_env_vars();
clear_managed_mcp_env_vars();
let raw = empty_config();
let mut cfg = Config::new_from_toml_cfg(&raw).unwrap();
cfg.resolve_runtime_fields(&RuntimeResolutionContext {
@@ -8550,8 +8376,6 @@ hooks = true
});
assert!(cfg.subagents_enabled);
assert!(!cfg.respect_gitignore);
assert!(cfg.managed_mcps_enabled);
assert!(!cfg.managed_mcp_gateway_tools_enabled);
assert_eq!(
cfg.session_summary_model,
Some(crate::models::default_session_summary_model().to_owned())
@@ -8560,60 +8384,6 @@ hooks = true
}
#[test]
#[serial]
fn resolve_runtime_fields_headless_defaults() {
clear_runtime_env_vars();
clear_managed_mcp_env_vars();
let raw = empty_config();
let mut cfg = Config::new_from_toml_cfg(&raw).unwrap();
cfg.resolve_runtime_fields(&RuntimeResolutionContext {
raw_config: &raw,
remote_settings: None,
cwd: None,
is_headless: true,
cli_subagents: None,
cli_session_summary_model: None,
cli_experimental_memory: false,
cli_no_memory: false,
disable_web_search: false,
todo_gate: false,
laziness_debug_log: None,
storage_mode: None,
});
assert!(
!cfg.managed_mcps_enabled,
"headless should default managed_mcps to false"
);
assert!(!cfg.managed_mcp_gateway_tools_enabled);
}
#[test]
#[serial]
fn resolve_runtime_fields_managed_gateway_tools_from_remote() {
clear_runtime_env_vars();
clear_managed_mcp_env_vars();
let raw = empty_config();
let remote = crate::util::config::RemoteSettings {
managed_mcp_gateway_tools_enabled: Some(true),
..Default::default()
};
let mut cfg = Config::new_from_toml_cfg(&raw).unwrap();
cfg.resolve_runtime_fields(&RuntimeResolutionContext {
raw_config: &raw,
remote_settings: Some(&remote),
cwd: None,
is_headless: false,
cli_subagents: None,
cli_session_summary_model: None,
cli_experimental_memory: false,
cli_no_memory: false,
disable_web_search: false,
todo_gate: false,
laziness_debug_log: None,
storage_mode: None,
});
assert!(cfg.managed_mcp_gateway_tools_enabled);
}
#[test]
#[serial]
fn resolve_runtime_fields_subagents_from_config() {
clear_runtime_env_vars();
let raw: toml::Value = toml::from_str("[subagents]\nenabled = true").unwrap();
@@ -8751,12 +8521,10 @@ hooks = true
cfg.resolve_runtime_fields(&ctx);
let first_subagents = cfg.subagents_enabled;
let first_gitignore = cfg.respect_gitignore;
let first_mcps = cfg.managed_mcps_enabled;
let first_ss = cfg.session_summary_model.clone();
cfg.resolve_runtime_fields(&ctx);
assert_eq!(cfg.subagents_enabled, first_subagents);
assert_eq!(cfg.respect_gitignore, first_gitignore);
assert_eq!(cfg.managed_mcps_enabled, first_mcps);
assert_eq!(cfg.session_summary_model, first_ss);
}
#[test]
@@ -270,13 +270,7 @@ impl acp::Agent for MvpAgent {
let current_working_directory = self.launch_cwd.clone();
let hostname = gethostname::gethostname();
let mcp_servers: Vec<crate::extensions::mcp::McpServerEntry> = Vec::new();
let fetch_managed_mcps = self.cfg.borrow().managed_mcps_enabled
&& self.can_fetch_managed_mcps();
if self.cfg.borrow().managed_mcps_enabled && !fetch_managed_mcps {
tracing::info!("Managed MCP fetch: DISABLED");
}
self.spawn_initialize_launch_mcp_setup(fetch_managed_mcps);
self.spawn_managed_gateway_tool_catalog_fetch();
self.spawn_initialize_launch_mcp_setup();
let init_model_state = self.model_state(None);
Ok(
acp::InitializeResponse::new(acp::ProtocolVersion::V1)
@@ -545,9 +539,7 @@ impl acp::Agent for MvpAgent {
let remote_settings = self.cfg.borrow().remote_settings.clone();
folder_trust::resolve_and_record(cwd.as_path(), remote_settings.as_ref(), false);
let initial_client_mcp_servers = arguments.mcp_servers.clone();
let (mcp_servers, managed_mcp_expires_at) = self
.resolve_mcp_servers(arguments.mcp_servers, cwd.as_path())
.await;
let mcp_servers = self.resolve_mcp_servers(arguments.mcp_servers, cwd.as_path());
let mcp_meta_config_map = parse_mcp_meta_config(arguments.meta.as_ref());
let client_session_id = arguments
.meta
@@ -756,7 +748,6 @@ impl acp::Agent for MvpAgent {
persisted_goal_mode: None,
persisted_announcement_state: None,
session_meta: arguments.meta.as_ref(),
managed_mcp_expires_at,
model_agent_type: model_agent_type.as_deref(),
session_model_id,
session_yolo_mode,
@@ -902,9 +893,7 @@ impl acp::Agent for MvpAgent {
let remote_settings = self.cfg.borrow().remote_settings.clone();
folder_trust::resolve_and_record(cwd.as_path(), remote_settings.as_ref(), false);
let initial_client_mcp_servers = client_mcp_servers.clone();
let (mcp_servers, managed_mcp_expires_at) = self
.resolve_mcp_servers(client_mcp_servers, cwd.as_path())
.await;
let mcp_servers = self.resolve_mcp_servers(client_mcp_servers, cwd.as_path());
let mcp_meta_config_map = parse_mcp_meta_config(request_meta.as_ref());
let mut load_timer = crate::instrumentation_timer!("session.load_session");
load_timer.with_field("session_id", session_id.0.as_ref());
@@ -1212,7 +1201,6 @@ impl acp::Agent for MvpAgent {
persisted_goal_mode: _persisted_goal_mode,
persisted_announcement_state,
session_meta: request_meta.as_ref(),
managed_mcp_expires_at,
model_agent_type: persisted_agent_name.as_deref(),
session_model_id: summary.current_model_id.clone(),
session_yolo_mode,
@@ -2083,7 +2071,7 @@ impl acp::Agent for MvpAgent {
| "x.ai/internal/reload_all_mcp_servers"
| "x.ai/internal/reload_project_mcp_servers" | "x.ai/internal/reload_skills"
| "x.ai/internal/reload_models" | "x.ai/internal/reload_models_cache"
| "x.ai/internal/auth_cleared" | "x.ai/plugins/reload"
| "x.ai/plugins/reload"
| "x.ai/commands/list" => {
crate::extensions::session_admin::handle(self, &args).await
}
@@ -84,123 +84,6 @@ impl MvpAgent {
}
})
}
fn has_managed_mcp_auth(&self) -> bool {
self.auth_manager
.current_or_expired()
.is_some_and(|a| a.is_session_auth())
}
/// Requires feature flag AND xAI authentication (OIDC or legacy WebLogin).
pub(super) fn can_fetch_managed_mcps(&self) -> bool {
let cfg = self.cfg.borrow();
cfg.managed_mcps_enabled && !cfg.managed_mcp_gateway_tools_enabled
&& self.has_managed_mcp_auth()
}
fn can_fetch_managed_mcp_gateway_tools(&self) -> bool {
self.cfg.borrow().managed_mcp_gateway_tools_enabled
&& self.has_managed_mcp_auth()
}
pub async fn get_managed_mcp_configs(
&self,
) -> Vec<crate::session::managed_mcp::ManagedMcpConfig> {
if !self.can_fetch_managed_mcps() {
return vec![];
}
let proxy_url = self.cfg.borrow().endpoints.proxy_url();
crate::session::managed_mcp::fetch_managed_mcp_configs(
&self.managed_mcp_cache,
&proxy_url,
&self.auth_manager,
)
.await
}
pub async fn get_managed_mcp_gateway_tool_catalog(
&self,
) -> Option<crate::session::managed_mcp::GatewayToolCatalog> {
if !self.can_fetch_managed_mcp_gateway_tools() {
self.managed_mcp_cache.lock().await.disable_gateway_tools();
return None;
}
self.managed_mcp_cache.lock().await.enable_gateway_tools();
let proxy_url = self.cfg.borrow().endpoints.proxy_url();
let auth_key = self
.auth_manager
.get_valid_token()
.await
.ok()
.or_else(|| self.auth_manager.current_or_expired().map(|a| a.key));
crate::session::managed_mcp::get_or_fetch_gateway_tool_catalog(
&self.managed_mcp_cache,
&proxy_url,
auth_key.as_deref(),
)
.await
}
pub fn managed_mcp_cache(
&self,
) -> &crate::session::managed_mcp::ManagedMcpStateHandle {
&self.managed_mcp_cache
}
pub(crate) fn disable_managed_gateway_tools_and_refresh_sessions(&self) {
self.disable_managed_gateway_tools_and_refresh_sessions_with_txs(
self.sessions.borrow().values().map(|handle| handle.cmd_tx.clone()).collect(),
);
}
fn disable_managed_gateway_tools_and_refresh_sessions_with_txs(
&self,
session_txs: Vec<tokio::sync::mpsc::UnboundedSender<SessionCommand>>,
) {
let cache = self.managed_mcp_cache.clone();
tokio::task::spawn_local(async move {
cache.lock().await.disable_gateway_tools();
for tx in session_txs {
let _ = tx.send(SessionCommand::RefreshMcpSearchIndex);
}
});
}
pub(crate) fn spawn_managed_gateway_tool_catalog_fetch(&self) {
let session_txs: Vec<_> = self
.sessions
.borrow()
.values()
.map(|handle| handle.cmd_tx.clone())
.collect();
if !self.can_fetch_managed_mcp_gateway_tools() {
self.disable_managed_gateway_tools_and_refresh_sessions_with_txs(
session_txs,
);
return;
}
let cache = self.managed_mcp_cache.clone();
let proxy_url = self.cfg.borrow().endpoints.proxy_url();
let auth_manager = self.auth_manager.clone();
tokio::task::spawn_local(async move {
let auth_key = auth_manager
.get_valid_token()
.await
.ok()
.or_else(|| auth_manager.current_or_expired().map(|a| a.key));
if !auth_manager
.current_or_expired()
.is_some_and(|a| a.is_session_auth())
{
cache.lock().await.disable_gateway_tools();
for tx in session_txs {
let _ = tx.send(SessionCommand::RefreshMcpSearchIndex);
}
return;
}
cache.lock().await.enable_gateway_tools();
crate::session::managed_mcp::get_or_fetch_gateway_tool_catalog(
&cache,
&proxy_url,
auth_key.as_deref(),
)
.await;
for tx in session_txs {
let _ = tx.send(SessionCommand::RefreshMcpSearchIndex);
}
});
}
/// Resolve the launch dir's project-scope trust verdict ONCE and return it
/// with its path.
///
@@ -226,15 +109,12 @@ impl MvpAgent {
/// Resolve folder trust and load launch-dir MCP configs after `initialize`
/// returns. The walks are synchronous and expensive in large monorepos; they
/// must not block the ACP response (grok-desktop sends `initialize` immediately).
pub(super) fn spawn_initialize_launch_mcp_setup(&self, fetch_managed_mcps: bool) {
pub(super) fn spawn_initialize_launch_mcp_setup(&self) {
let cwd = self.launch_cwd.clone();
let compat = self.cfg.borrow().compat_resolved;
let remote_settings = self.cfg.borrow().remote_settings.clone();
let gateway = self.gateway.clone();
let agent_mcp_state = self.agent_mcp_state.clone();
let managed_mcp_cache = self.managed_mcp_cache.clone();
let proxy_url = self.cfg.borrow().endpoints.proxy_url();
let auth_manager = self.auth_manager.clone();
tokio::task::spawn_local(async move {
let local_mcp_servers = match tokio::task::spawn_blocking(move || {
let local = crate::util::config::load_mcp_servers(&cwd, &compat);
@@ -258,27 +138,9 @@ impl MvpAgent {
}
crate::extensions::mcp::notify_servers_updated(
&gateway,
&[],
&local_mcp_servers,
)
.await;
if !fetch_managed_mcps {
return;
}
let managed = crate::session::managed_mcp::fetch_managed_mcp_configs(
&managed_mcp_cache,
&proxy_url,
&auth_manager,
)
.await;
if !managed.is_empty() {
crate::extensions::mcp::notify_servers_updated(
&gateway,
&managed,
&local_mcp_servers,
)
.await;
}
});
}
pub fn agent_mcp_state(
@@ -311,23 +173,19 @@ impl MvpAgent {
plugin_count = count, "lazily populated plugin registry snapshot"
);
}
/// Fetch managed configs, merge with client servers, return merged list + earliest expiry.
pub(super) async fn resolve_mcp_servers(
/// Merge on-disk/plugin MCP servers with client servers.
pub(super) fn resolve_mcp_servers(
&self,
client_servers: Vec<acp::McpServer>,
cwd: &std::path::Path,
) -> (Vec<acp::McpServer>, Option<chrono::DateTime<chrono::Utc>>) {
) -> Vec<acp::McpServer> {
self.ensure_plugin_registry();
let managed = self.get_managed_mcp_configs().await;
let expires_at = managed.iter().filter_map(|c| c.token_expires_at).min();
let merged = crate::session::managed_mcp::merge_managed_mcp_servers(
crate::session::managed_mcp::merge_managed_mcp_servers(
client_servers,
cwd,
&managed,
self.plugin_registry_handle.snapshot().as_deref(),
&self.cfg.borrow().compat_resolved,
);
(merged, expires_at)
)
}
/// Set the memory configuration (called from TUI after config resolution).
pub fn set_memory_config(&mut self, config: crate::config::MemoryConfig) {
@@ -760,39 +618,6 @@ impl MvpAgent {
);
(id.clone(), new_config)
}
/// Build image generation config.
///
/// Both BYOK and session (OAuth) users go direct to `api_base_url`.
/// `sampling_config.api_key` carries the OAuth bearer for session users (the
/// `api_key_provider` refreshes it per request), so IC authenticates and
/// meters Imagine usage per-user.
pub(super) fn prepare_image_gen_config(
&self,
) -> kigi_tools::implementations::grok_build::image_gen::ImageGenConfig {
use kigi_tools::implementations::grok_build::image_gen::ImageGenConfig;
let sampling_config = self.sampling_config.borrow();
let Some(ref api_key) = sampling_config.api_key else {
return ImageGenConfig::Disabled;
};
let cfg = self.cfg.borrow();
let base_url = cfg.endpoints.api_base_url.clone();
let version = cfg
.client_version
.clone()
.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!("kigi/{version}"));
ImageGenConfig::Enabled {
api_key: api_key.clone(),
base_url,
extra_headers: headers,
image_gen_enabled: cfg.resolve_image_gen().value,
image_edit_enabled: cfg.resolve_image_edit().value,
model_override: cfg.resolve_image_gen_model_override(),
tier_restricted: false,
}
}
/// Build deploy-service config. The tool talks directly to the deployer service.
pub(super) fn prepare_app_builder_deployer_config(
&self,
@@ -800,40 +625,6 @@ impl MvpAgent {
use kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig;
AppBuilderDeployerConfig::Disabled
}
/// Build video generation config. Video tools call the xAI API directly.
pub(super) fn prepare_video_gen_config(
&self,
) -> kigi_tools::implementations::grok_build::video_gen::VideoGenConfig {
use kigi_tools::implementations::grok_build::video_gen::VideoGenConfig;
let Some(api_key) = self.sampling_config.borrow().api_key.clone() else {
return VideoGenConfig::Disabled;
};
let cfg = self.cfg.borrow();
let zdr_video_output_s3 = cfg
.disable_zdr_incompatible_tools
.then(|| cfg.zdr_video_output_s3.clone())
.flatten()
.filter(|s3| s3.is_valid());
if cfg.disable_zdr_incompatible_tools && zdr_video_output_s3.is_none() {
tracing::info!("video_gen disabled by tools.disable_zdr_incompatible_tools");
return VideoGenConfig::Disabled;
}
let base_url = cfg.endpoints.api_base_url.clone();
let version = cfg
.client_version
.clone()
.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!("kigi/{version}"));
VideoGenConfig::Enabled {
api_key,
base_url,
extra_headers: headers,
zdr_video_output_s3: zdr_video_output_s3.map(Box::new),
tier_restricted: false,
}
}
/// Web search config (PRD F5). The Kimi search service exists only on
/// the Kimi Code subscription channel (`POST {coding_base}/search`,
/// kimi-cli `auth/platforms.py`), so this is `Enabled` only for OAuth
@@ -1016,7 +807,6 @@ impl MvpAgent {
worktree_type,
restore_code,
session_registry_local,
managed_mcp_cache: Default::default(),
agent_mcp_state: std::sync::Arc::new(
tokio::sync::Mutex::new(
crate::session::mcp_servers::McpState::new(vec![]),
@@ -1829,7 +1619,6 @@ impl MvpAgent {
persisted_goal_mode,
persisted_announcement_state,
session_meta,
managed_mcp_expires_at,
model_agent_type,
session_model_id,
session_yolo_mode,
@@ -2258,8 +2047,6 @@ impl MvpAgent {
.and_then(|entry| entry.info.max_retries);
let origin_client = self.origin_client_info_from_meta(init.meta.as_ref());
let web_search_config = self.prepare_web_search_config();
let image_gen_config = self.prepare_image_gen_config();
let video_gen_config = self.prepare_video_gen_config();
let app_builder_deployer_config = self.prepare_app_builder_deployer_config();
let web_fetch_config = self.prepare_web_fetch_config();
let write_file_enabled = self.cfg.borrow().resolve_write_file().value;
@@ -2309,7 +2096,6 @@ impl MvpAgent {
let cfg = self.cfg.borrow();
cfg.resolve_backend_tools().value
};
let managed_mcp_proxy_url = self.cfg.borrow().endpoints.proxy_url();
let init_meta = self
.initialize_request
.get()
@@ -2471,9 +2257,6 @@ impl MvpAgent {
persisted_announcement_state,
self.memory_config.clone(),
feedback_flags,
self.managed_mcp_cache.clone(),
managed_mcp_expires_at,
managed_mcp_proxy_url,
session_model_id,
session_yolo_mode,
session_auto_mode,
@@ -2482,8 +2265,6 @@ impl MvpAgent {
model_max_retries,
web_search_config,
web_fetch_config,
image_gen_config,
video_gen_config,
app_builder_deployer_config,
write_file_enabled,
goal_enabled,
@@ -2617,7 +2398,6 @@ impl MvpAgent {
self.notify_session_cwd_for_watch(std::path::Path::new(&session_info.cwd));
self.activity.register_session(&session_info.id.0, &handle);
self.sessions.borrow_mut().insert(session_info.id.clone(), handle);
self.spawn_managed_gateway_tool_catalog_fetch();
let cwd_for_maintenance = session_info.cwd.clone();
tokio::spawn(async move {
crate::session::prompt_history::truncate_if_needed_async(cwd_for_maintenance)
@@ -150,10 +150,6 @@ impl MvpAgent {
let gateway = self.gateway.clone();
let plugin_handle = self.plugin_registry_handle.clone();
let managed_mcp_cache = self.managed_mcp_cache.clone();
let auth_manager = self.auth_manager.clone();
let can_fetch_managed = self.can_fetch_managed_mcps();
let proxy_url = self.cfg.borrow().endpoints.proxy_url();
let compat = self.cfg.borrow().compat_resolved;
let remote = remote.cloned();
let cwd = cwd.to_path_buf();
@@ -258,10 +254,6 @@ impl MvpAgent {
gateway: &gateway,
targets,
plugin_handle: &plugin_handle,
managed_mcp_cache: &managed_mcp_cache,
auth_manager: &auth_manager,
can_fetch_managed,
proxy_url: &proxy_url,
compat: &compat,
prompt_cwd: &cwd,
})
@@ -291,10 +283,6 @@ struct ReloadAfterGrant<'a> {
/// Every session sharing the granted workspace, each with its own cwd.
targets: Vec<ReloadTarget>,
plugin_handle: &'a kigi_agent::plugins::SharedPluginRegistryHandle,
managed_mcp_cache: &'a crate::session::managed_mcp::ManagedMcpStateHandle,
auth_manager: &'a std::sync::Arc<AuthManager>,
can_fetch_managed: bool,
proxy_url: &'a str,
compat: &'a kigi_tools::types::CompatConfig,
/// The prompting session's cwd — used only for the client catalog push.
prompt_cwd: &'a std::path::Path,
@@ -303,27 +291,15 @@ struct ReloadAfterGrant<'a> {
/// Reload each granted-workspace session's now-trusted project servers in place
/// (no restart), driving the canonical primitives the normal spawn/reload paths
/// use — PER SESSION CWD, like `handle_reload_project_mcp_servers` /
/// `broadcast_plugin_registry_to_sessions`: `fetch_managed_mcp_configs` +
/// `broadcast_plugin_registry_to_sessions`:
/// `merge_managed_mcp_servers` (`SessionCommand::UpdateMcpServers`), `build_for_cwd`
/// (`SessionCommand::ReloadPlugins`), and `reload_hooks_impl`
/// (`SessionCommand::ReloadHooks`), then push the refreshed MCP catalog. LSP is
/// spawn-baked and applies on the next session open (see module docs). Caller
/// must have granted + recorded trust first.
async fn reload_project_servers_after_grant(ctx: ReloadAfterGrant<'_>) {
// Managed (gateway/Toolbox) servers must survive the re-merge; fetch them once
// (cwd-independent) via the shared helper (single-sources the auth-key dance
// with `MvpAgent::get_managed_mcp_configs`). The plugin MCP snapshot is also
// global, so it is fine to reuse across cwds for the merge.
let managed = if ctx.can_fetch_managed {
crate::session::managed_mcp::fetch_managed_mcp_configs(
ctx.managed_mcp_cache,
ctx.proxy_url,
ctx.auth_manager,
)
.await
} else {
vec![]
};
// The plugin MCP snapshot is global, so it is fine to reuse across cwds
// for the merge.
let plugin_snapshot = ctx.plugin_handle.snapshot();
for target in ctx.targets {
@@ -336,7 +312,6 @@ async fn reload_project_servers_after_grant(ctx: ReloadAfterGrant<'_>) {
let merged = crate::session::managed_mcp::merge_managed_mcp_servers(
target.initial_client_mcp_servers,
session_cwd,
&managed,
plugin_snapshot.as_deref(),
ctx.compat,
);
@@ -375,7 +350,7 @@ async fn reload_project_servers_after_grant(ctx: ReloadAfterGrant<'_>) {
ctx.prompt_cwd,
crate::util::config::load_mcp_servers(ctx.prompt_cwd, ctx.compat),
);
crate::extensions::mcp::notify_servers_updated(ctx.gateway, &managed, &local).await;
crate::extensions::mcp::notify_servers_updated(ctx.gateway, &local).await;
}
#[cfg(test)]
@@ -124,7 +124,6 @@ pub(crate) struct SessionSpawnOptions<'a> {
crate::session::announcement_state::AnnouncementState,
>,
pub session_meta: Option<&'a acp::Meta>,
pub managed_mcp_expires_at: Option<chrono::DateTime<chrono::Utc>>,
pub model_agent_type: Option<&'a str>,
pub session_model_id: acp::ModelId,
pub session_yolo_mode: bool,
@@ -260,7 +259,6 @@ pub(crate) fn chat_session_spawn_options<'a>(
persisted_goal_mode: None,
persisted_announcement_state: None,
session_meta,
managed_mcp_expires_at: None,
model_agent_type,
session_model_id,
session_yolo_mode,
@@ -591,8 +589,6 @@ pub struct MvpAgent {
/// Local config.toml override for session registry (`[cli] session_registry`).
/// `Some(true)` enables, `Some(false)` disables, `None` defers to remote settings.
session_registry_local: Option<bool>,
/// Managed MCP configs and gateway tool catalog; lazily fetched.
managed_mcp_cache: crate::session::managed_mcp::ManagedMcpStateHandle,
/// Agent-level MCP server state. LEADER-SAFE(shared): MCP servers are
/// agent-scoped, not per-client.
agent_mcp_state: std::sync::Arc<
@@ -304,7 +304,6 @@ impl MvpAgent {
session_env,
parent_attribution_callback,
parent_agent_name,
parent_managed_mcp_proxy_base_url,
) = {
let sessions = self.sessions.borrow();
let ps = sessions.get(&parent_sid);
@@ -339,7 +338,6 @@ impl MvpAgent {
.unwrap_or_else(|| std::sync::Arc::new(std::collections::HashMap::new())),
ps.and_then(|h| h.attribution_callback.clone()),
ps.map(|h| h.agent_name.clone()),
ps.map(|h| h.managed_mcp_proxy_base_url.clone()),
)
};
let (
@@ -401,8 +399,6 @@ impl MvpAgent {
gateway: self.gateway.clone(),
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.coding_api_base_url()),
alpha_test_key: self.alpha_test_key(),
auth_method_id: self
.auth_method_id
@@ -429,8 +425,6 @@ impl MvpAgent {
memory_config: self.memory_config.clone(),
web_search_config: self.prepare_web_search_config(),
web_fetch_config: self.prepare_web_fetch_config(),
image_gen_config: self.prepare_image_gen_config(),
video_gen_config: self.prepare_video_gen_config(),
app_builder_deployer_config: self.prepare_app_builder_deployer_config(),
write_file_enabled: self.cfg.borrow().resolve_write_file().value,
goal_enabled: self.cfg.borrow().resolve_goal().value,
@@ -505,7 +499,6 @@ impl MvpAgent {
.map(|h| h.mcp_servers.clone())
.unwrap_or_default()
},
managed_mcp_state: self.managed_mcp_cache.clone(),
parent_mcp_pool: None,
parent_tool_snapshot: None,
parent_skills: None,
@@ -648,8 +648,6 @@ async fn file_toolset_override_e2e_to_finalized_toolset() {
web_search_config: kigi_tools::implementations::web_search::WebSearchConfig::default(),
web_fetch_config: Default::default(),
lsp: None,
image_gen_config: kigi_tools::implementations::grok_build::image_gen::ImageGenConfig::default(),
video_gen_config: kigi_tools::implementations::grok_build::video_gen::VideoGenConfig::default(),
app_builder_deployer_config: kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig::default(),
api_key_provider: None,
attribution_callback: None,
@@ -744,7 +742,6 @@ fn make_test_handle(
permission_handle: kigi_workspace::permission::PermissionHandle::allow_all(),
attribution_callback: None,
agent_name: "grok-build".to_string(),
managed_mcp_proxy_base_url: String::new(),
session_default_agent_profile: None,
allowed_subagent_types: None,
hook_registry: None,
@@ -1136,38 +1133,6 @@ fn test_sessionless_request_requires_session_id() {
"cwd-only requests with no sessionId must return SessionRequired"
);
}
#[tokio::test(flavor = "current_thread")]
async fn ext_method_routes_auth_cleared_and_refreshes_resident_sessions() {
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let agent = build_agent_with_auth(crate::auth::KimiAuth {
key: "eligible".into(),
auth_mode: crate::auth::AuthMode::OAuth,
..crate::auth::KimiAuth::test_default()
});
use acp::Agent as _;
agent.managed_mcp_cache.lock().await.enable_gateway_tools();
let sid = acp::SessionId::new("sess-auth-cleared");
let (handle, _tx, mut cmd_rx) = make_live_session_handle(&sid, None);
agent.sessions.borrow_mut().insert(sid, handle);
let params = serde_json::json!({});
agent
.ext_method(acp::ExtRequest::new(
"x.ai/internal/auth_cleared",
std::sync::Arc::from(serde_json::value::to_raw_value(&params).unwrap()),
))
.await
.expect("auth_cleared must route through session-admin");
let cmd = tokio::time::timeout(std::time::Duration::from_secs(1), cmd_rx.recv())
.await
.expect("refresh command should be sent")
.expect("channel should stay open until command is received");
assert!(matches!(cmd, SessionCommand::RefreshMcpSearchIndex));
assert!(!agent.managed_mcp_cache.lock().await.gateway_tools_active);
})
.await;
}
/// Build a minimal MvpAgent suitable for testing extension methods.
fn build_minimal_agent_for_tests() -> MvpAgent {
use crate::agent::config::Config as AgentConfig;
@@ -1860,7 +1825,7 @@ async fn auth_type_session_based_no_current_returns_session_token() {
/// BYOK guard. Users with `xai.api_key` must continue to report `ApiKey`
/// regardless of live-token state -- BYOK sessions have nothing to refresh,
/// and reporting `SessionToken` would route through cli-chat-proxy paths
/// (image_gen / video_gen base_url) that don't apply to BYOK keys.
/// that don't apply to BYOK keys.
#[tokio::test(flavor = "current_thread")]
async fn auth_type_xai_api_key_no_current_returns_api_key() {
let agent = build_minimal_agent_for_tests();
@@ -1952,26 +1917,6 @@ async fn cached_token_fallthrough_falls_to_grok_com_without_credentials() {
"no API-key creds and no kill switch -> interactive grok.com login",
);
}
/// The imagine tier gate fails **open**: with no resolved auth we can't confirm
/// a restricted personal tier, so the tools stay advertised and un-flagged (the
/// server 429 remains the authoritative backstop). Guards against accidentally
/// disabling a paid feature when tier info hasn't loaded.
#[tokio::test(flavor = "current_thread")]
async fn prepare_image_gen_config_fails_open_without_auth() {
use kigi_tools::implementations::grok_build::image_gen::ImageGenConfig;
let agent = build_minimal_agent_for_tests();
agent.sampling_config.borrow_mut().api_key = Some("test-key".to_string());
let ImageGenConfig::Enabled {
tier_restricted, ..
} = agent.prepare_image_gen_config()
else {
panic!("expected Enabled");
};
assert!(
!tier_restricted,
"no resolved auth ⇒ fail open (tools not tier-restricted)"
);
}
/// `parse_session_kind` routes `session/load` to the gateway Chat path vs. the
/// disk-backed Build path. Anything but an explicit `kind: "chat"` is Build.
#[test]
@@ -2107,7 +2052,6 @@ fn chat_session_spawn_options_matches_thin_profile() {
assert!(!opts.client_fs_read);
assert!(!opts.client_fs_write);
assert!(opts.chat_history.is_empty());
assert!(opts.managed_mcp_expires_at.is_none());
assert!(!opts.session_auto_mode);
assert!(
opts.persistence.is_noop(),
@@ -1054,9 +1054,6 @@ pub(crate) async fn handle_subagent_request(
ctx.memory_config.clone()
},
Default::default(),
ctx.managed_mcp_state.clone(),
None,
ctx.managed_mcp_proxy_base_url.clone(),
effective_model_id,
ctx.yolo_mode
|| matches!(
@@ -1069,8 +1066,6 @@ pub(crate) async fn handle_subagent_request(
None,
ctx.web_search_config.clone(),
ctx.web_fetch_config.clone(),
ctx.image_gen_config.clone(),
ctx.video_gen_config.clone(),
ctx.app_builder_deployer_config.clone(),
ctx.write_file_enabled,
ctx.goal_enabled,
@@ -144,7 +144,6 @@ pub(crate) struct SubagentSpawnContext {
/// context is built (an async snapshot from the parent session actor).
pub client_hooks: crate::extensions::hooks::ClientHooks,
pub sampling_config: kigi_sampler::SamplerConfig,
pub managed_mcp_proxy_base_url: String,
/// The staging auth header value propagated from the parent. Used
/// when materialising subagent `SamplerConfig`s for auth-flow tracking
/// and for `inject_url_derived_headers` in the construction helpers.
@@ -209,10 +208,6 @@ pub(crate) struct SubagentSpawnContext {
pub web_search_config: kigi_tools::implementations::WebSearchConfig,
/// Resolved config for web fetch.
pub web_fetch_config: kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig,
/// Image generation config (parent-inherited).
pub image_gen_config: kigi_tools::implementations::grok_build::image_gen::ImageGenConfig,
/// Resolved config for video generation.
pub video_gen_config: kigi_tools::implementations::grok_build::video_gen::VideoGenConfig,
/// Resolved config for the deploy service.
pub app_builder_deployer_config:
kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig,
@@ -320,8 +315,6 @@ pub(crate) struct SubagentSpawnContext {
/// time). Servers added later via `UpdateMcpServers` (managed MCPs, plugin reload)
/// will not appear here. Named references only resolve against the initial config.
pub parent_mcp_configs: Vec<agent_client_protocol::McpServer>,
/// Parent's managed MCP state handle (Arc-shared, no re-fetch).
pub managed_mcp_state: crate::session::managed_mcp::ManagedMcpStateHandle,
/// Snapshot of the parent session's MCP client pool at spawn time.
pub parent_mcp_pool: Option<crate::session::mcp_servers::SharedMcpPool>,
/// Snapshot of the parent session's resolved tool schema at spawn time.
@@ -143,16 +143,6 @@ fn subagent_inherits_parent_lsp_via_context() {
);
}
#[test]
fn subagent_inherits_managed_mcp_state_via_context() {
let handle = crate::session::managed_mcp::ManagedMcpStateHandle::default();
let mut ctx = ctx_with_toggle(HashMap::new());
ctx.managed_mcp_state = handle.clone();
assert!(
Arc::ptr_eq(& handle, & ctx.managed_mcp_state),
"child should share parent's managed MCP state (Arc identity)"
);
}
#[test]
fn no_parent_lsp_means_child_gets_none() {
let ctx = ctx_with_toggle(HashMap::new());
assert!(ctx.lsp.is_none());
@@ -1110,7 +1100,6 @@ fn dummy_tracker(
permission_handle: kigi_workspace::permission::PermissionHandle::allow_all(),
attribution_callback: None,
agent_name: "grok-build".to_string(),
managed_mcp_proxy_base_url: String::new(),
session_default_agent_profile: None,
allowed_subagent_types: None,
hook_registry: None,