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:
1 parent
fa75eb139a
commit
5e4e24db99
120 files changed
+301
-11327
No files matched your search
@@ -900,17 +900,9 @@ impl SessionActor {
|
||||
// the order-sensitive `update_configs` would cause (merge order is
|
||||
// non-deterministic). Mirrors the `UpdateMcpServers` command handler.
|
||||
let t_mcp = std::time::Instant::now();
|
||||
let managed_configs = {
|
||||
let mcp_handle = self.managed_mcp_handle.lock().await;
|
||||
match &mcp_handle.cache {
|
||||
crate::session::managed_mcp::ManagedMcpCache::Ready(configs) => configs.clone(),
|
||||
_ => vec![],
|
||||
}
|
||||
};
|
||||
let new_mcp_servers = crate::session::managed_mcp::merge_managed_mcp_servers(
|
||||
self.initial_client_mcp_servers.clone(),
|
||||
session_cwd,
|
||||
&managed_configs,
|
||||
new_registry_snapshot.as_deref(),
|
||||
&self.rebuild_spec.compat,
|
||||
);
|
||||
|
||||
@@ -17,230 +17,6 @@ impl SessionActor {
|
||||
}
|
||||
self.ensure_mcp_tools_initialized().await;
|
||||
}
|
||||
/// If managed tokens are near expiry, swap clients using the agent-level cache.
|
||||
pub(super) async fn refresh_managed_mcp_if_stale(&self) {
|
||||
use crate::session::managed_mcp::ManagedMcpCache;
|
||||
let is_stale = crate::session::managed_mcp::managed_token_is_stale(
|
||||
*self.managed_mcp_expires_at.lock().unwrap(),
|
||||
chrono::Utc::now(),
|
||||
);
|
||||
if !is_stale {
|
||||
return;
|
||||
}
|
||||
let fresh_configs = match &self.managed_mcp_handle.lock().await.cache {
|
||||
ManagedMcpCache::Ready(c) if !c.is_empty() => c.clone(),
|
||||
_ => return,
|
||||
};
|
||||
self.mcp_state.lock().await.refresh_managed_clients(
|
||||
fresh_configs
|
||||
.iter()
|
||||
.map(|c| (c.endpoint.as_str(), &c.headers)),
|
||||
);
|
||||
*self.managed_mcp_expires_at.lock().unwrap() = fresh_configs
|
||||
.iter()
|
||||
.filter_map(|c| c.token_expires_at)
|
||||
.min();
|
||||
}
|
||||
/// Reactive managed re-auth: cache-bypassing config re-fetch, swap in fresh
|
||||
/// headers, re-handshake once. `Ok(())` if the server is `Ready` afterward.
|
||||
///
|
||||
/// Owner-scoped — only the session owning the client in `owned_clients` swaps
|
||||
/// (`refresh_managed_clients` ignores a subagent's shared Arc, which recovers
|
||||
/// via the leader on its next pool snapshot). The shared `ManagedMcpState`
|
||||
/// cooldown coalesces concurrent failures and bounds a revoked connector.
|
||||
/// Shared by both reactive entry points; locks are taken sequentially, never
|
||||
/// nested (acquire `managed_mcp_handle` before `mcp_state` if that changes).
|
||||
///
|
||||
/// Emits `metrics.mcp.managed.reauth.{triggered,outcome,cooldown_terminal}`
|
||||
/// tracing targets, plus an `mcp.server_connection` span (`connected` with
|
||||
/// `tool_count` on recovery, `failed`/`error_type=auth` on terminal exhaustion).
|
||||
pub(super) async fn reactive_managed_reauth(&self, server_name: &str) -> Result<(), String> {
|
||||
if !self
|
||||
.mcp_state
|
||||
.lock()
|
||||
.await
|
||||
.owned_clients
|
||||
.contains_key(server_name)
|
||||
{
|
||||
return Err(format!(
|
||||
"session does not own managed client '{server_name}'"
|
||||
));
|
||||
}
|
||||
let now = chrono::Utc::now();
|
||||
if !self
|
||||
.managed_mcp_handle
|
||||
.lock()
|
||||
.await
|
||||
.reauth_allowed(server_name, now)
|
||||
{
|
||||
return Err(format!(
|
||||
"managed reactive re-auth for '{server_name}' is in cooldown"
|
||||
));
|
||||
}
|
||||
tracing::info!(
|
||||
target : "metrics.mcp.managed.reauth.triggered", server = % server_name
|
||||
);
|
||||
tracing::info!(
|
||||
server = % server_name,
|
||||
"managed MCP auth rejection detected, attempting reactive re-fetch"
|
||||
);
|
||||
let scope = || {
|
||||
crate::util::config::mcp_server_scope(
|
||||
server_name,
|
||||
std::path::Path::new(self.session_info.cwd.as_str()),
|
||||
)
|
||||
};
|
||||
let started = std::time::Instant::now();
|
||||
match self.reactive_managed_reauth_inner(server_name).await {
|
||||
Ok(tool_count) => {
|
||||
let elapsed_ms = started.elapsed().as_millis() as i64;
|
||||
self.managed_mcp_handle
|
||||
.lock()
|
||||
.await
|
||||
.record_reauth_success(server_name);
|
||||
tracing::info!(
|
||||
target : "metrics.mcp.managed.reauth.outcome", server = %
|
||||
server_name, result = "recovered",
|
||||
);
|
||||
tracing::info!(
|
||||
server = % server_name, "managed MCP reactive re-auth recovered"
|
||||
);
|
||||
crate::session::telemetry::emit_mcp_connection_span(
|
||||
"connected",
|
||||
server_name,
|
||||
"http",
|
||||
scope(),
|
||||
Some(elapsed_ms),
|
||||
Some(tool_count as i64),
|
||||
None,
|
||||
);
|
||||
let payload = crate::session::mcp_dispatcher::McpServerStatusPayload {
|
||||
session_id: self.session_id_string(),
|
||||
name: server_name.to_string(),
|
||||
source: crate::session::mcp_dispatcher::classify_source(server_name),
|
||||
status: crate::session::mcp_dispatcher::McpServerStatus::Ready,
|
||||
reason:
|
||||
crate::session::mcp_dispatcher::McpServerStatusReason::ManagedTokenRefreshed,
|
||||
detail: None,
|
||||
tools: None,
|
||||
};
|
||||
crate::session::mcp_restart::forward_status(&self.notifications.gateway, &payload);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
let elapsed_ms = started.elapsed().as_millis() as i64;
|
||||
let failed_at = chrono::Utc::now();
|
||||
let terminal = {
|
||||
let mut st = self.managed_mcp_handle.lock().await;
|
||||
st.record_reauth_failure(server_name, failed_at);
|
||||
st.reauth_is_terminal(server_name)
|
||||
};
|
||||
if terminal {
|
||||
self.mcp_state
|
||||
.lock()
|
||||
.await
|
||||
.record_init_failure(server_name, true, None);
|
||||
let payload = crate::session::mcp_dispatcher::McpServerStatusPayload {
|
||||
session_id: self.session_id_string(),
|
||||
name: server_name.to_string(),
|
||||
source: crate::session::mcp_dispatcher::classify_source(server_name),
|
||||
status: crate::session::mcp_dispatcher::McpServerStatus::NeedsAuth,
|
||||
reason: crate::session::mcp_dispatcher::McpServerStatusReason::AuthExpired,
|
||||
detail: None,
|
||||
tools: None,
|
||||
};
|
||||
crate::session::mcp_restart::forward_status(
|
||||
&self.notifications.gateway,
|
||||
&payload,
|
||||
);
|
||||
tracing::warn!(
|
||||
target : "metrics.mcp.managed.reauth.cooldown_terminal", server =
|
||||
% server_name,
|
||||
);
|
||||
tracing::warn!(
|
||||
server = % server_name,
|
||||
"managed MCP reactive re-auth exhausted; surfacing NeedsAuth"
|
||||
);
|
||||
crate::session::telemetry::emit_mcp_connection_span(
|
||||
"failed",
|
||||
server_name,
|
||||
"http",
|
||||
scope(),
|
||||
Some(elapsed_ms),
|
||||
None,
|
||||
Some("auth"),
|
||||
);
|
||||
self.unregister_server_tools(server_name);
|
||||
self.refresh_mcp_snapshot_and_schedule_reminder().await;
|
||||
}
|
||||
tracing::info!(
|
||||
target : "metrics.mcp.managed.reauth.outcome", server = %
|
||||
server_name, result = if terminal { "failed" } else { "cooldown" },
|
||||
);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Inner half of [`Self::reactive_managed_reauth`]: snapshot inputs, force
|
||||
/// a fresh proxy fetch, swap clients, re-handshake, and register tools.
|
||||
/// Split out so the caller owns the cooldown gate and the status push.
|
||||
/// Returns the number of tools registered after recovery, so the caller can
|
||||
/// stamp the recovered `mcp.server_connection` span with a `tool_count`.
|
||||
async fn reactive_managed_reauth_inner(&self, server_name: &str) -> Result<usize, String> {
|
||||
let auth_manager = self.auth_manager.clone();
|
||||
let proxy_url = self.models_manager.endpoints().proxy_url();
|
||||
let Some(am) = auth_manager else {
|
||||
return Err("no auth manager; cannot re-fetch managed configs".into());
|
||||
};
|
||||
crate::session::managed_mcp::invalidate_cache(&self.managed_mcp_handle).await;
|
||||
let fresh_configs = crate::session::managed_mcp::fetch_managed_mcp_configs(
|
||||
&self.managed_mcp_handle,
|
||||
&proxy_url,
|
||||
&am,
|
||||
)
|
||||
.await;
|
||||
if fresh_configs.is_empty() {
|
||||
return Err("managed re-fetch returned no configs".into());
|
||||
}
|
||||
{
|
||||
let mut st = self.mcp_state.lock().await;
|
||||
st.refresh_managed_clients(
|
||||
fresh_configs
|
||||
.iter()
|
||||
.map(|c| (c.endpoint.as_str(), &c.headers)),
|
||||
);
|
||||
}
|
||||
*self.managed_mcp_expires_at.lock().unwrap() = fresh_configs
|
||||
.iter()
|
||||
.filter_map(|c| c.token_expires_at)
|
||||
.min();
|
||||
let client = {
|
||||
let st = self.mcp_state.lock().await;
|
||||
st.get_client(server_name)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("client '{server_name}' missing after refresh"))?
|
||||
};
|
||||
let registrations = client
|
||||
.get_tool_registrations(self.mcp_state.clone())
|
||||
.await
|
||||
.map_err(|e| format!("re-handshake failed: {e}"))?;
|
||||
let mut mcp_state = self.mcp_state.lock().await;
|
||||
mcp_state.auth_required.remove(server_name);
|
||||
mcp_state.clear_init_failed(server_name);
|
||||
let tool_count = registrations.len();
|
||||
let mut ui_tools: std::collections::HashMap<
|
||||
String,
|
||||
Vec<crate::extensions::mcp::McpToolEntry>,
|
||||
> = std::collections::HashMap::new();
|
||||
for reg in registrations {
|
||||
self.register_mcp_tool(server_name, reg, &mut mcp_state, &mut ui_tools)
|
||||
.await;
|
||||
}
|
||||
drop(mcp_state);
|
||||
self.refresh_mcp_snapshot_and_schedule_reminder().await;
|
||||
self.emit_mcp_tools_changed_notifications(ui_tools);
|
||||
Ok(tool_count)
|
||||
}
|
||||
/// Register tools from shared (inherited) MCP clients on this session's ToolBridge.
|
||||
///
|
||||
/// Shared clients are already connected (Arc-shared from parent), so
|
||||
@@ -411,9 +187,6 @@ impl SessionActor {
|
||||
/// Runs force_reauth (browser flow), then re-initializes the server
|
||||
/// and registers its tools.
|
||||
pub(super) async fn handle_mcp_auth_trigger(&self, server_name: &str) -> Result<(), String> {
|
||||
if server_name.starts_with(crate::session::managed_mcp::MANAGED_MCP_PREFIX) {
|
||||
return Err("To authenticate, visit grok.com".to_string());
|
||||
}
|
||||
let client = {
|
||||
let state = self.mcp_state.lock().await;
|
||||
state
|
||||
@@ -527,28 +300,13 @@ impl SessionActor {
|
||||
/// `<system-reminder>` at a turn boundary. The `search_tool` description
|
||||
/// itself stays static (cacheable).
|
||||
pub(super) async fn refresh_mcp_snapshot_and_schedule_reminder(&self) {
|
||||
let disabled_gateway_tools = crate::util::config::get_all_mcp_disabled_tools(
|
||||
std::path::Path::new(&self.session_info.cwd),
|
||||
);
|
||||
self.refresh_mcp_snapshot_and_schedule_reminder_with_disabled(&disabled_gateway_tools)
|
||||
.await;
|
||||
}
|
||||
pub(super) async fn refresh_mcp_snapshot_and_schedule_reminder_with_disabled(
|
||||
&self,
|
||||
disabled_gateway_tools: &std::collections::HashMap<
|
||||
String,
|
||||
std::collections::HashSet<String>,
|
||||
>,
|
||||
) {
|
||||
let mcp_initialized = self.mcp_state.lock().await.is_initialized();
|
||||
refresh_mcp_snapshot_and_schedule_reminder_with(
|
||||
self.agent.borrow().tool_bridge().clone(),
|
||||
Arc::clone(&self.mcp_state),
|
||||
self.managed_mcp_handle.clone(),
|
||||
self.tool_metadata_snapshot.clone(),
|
||||
Arc::clone(&self.mcp_reminder_dirty),
|
||||
mcp_initialized,
|
||||
disabled_gateway_tools,
|
||||
self.cursor_mcps_root(),
|
||||
)
|
||||
.await;
|
||||
@@ -638,8 +396,7 @@ impl SessionActor {
|
||||
"connection failed".to_string()
|
||||
};
|
||||
let retries_on_use = !mcp_state.auth_required.contains(name)
|
||||
&& matches!(cfg, acp::McpServer::Http(_) | acp::McpServer::Sse(_))
|
||||
&& !name.starts_with(crate::session::managed_mcp::MANAGED_MCP_PREFIX);
|
||||
&& matches!(cfg, acp::McpServer::Http(_) | acp::McpServer::Sse(_));
|
||||
let reason = if retries_on_use {
|
||||
format!("{base} — retries automatically on next tool call")
|
||||
} else {
|
||||
@@ -727,14 +484,9 @@ impl SessionActor {
|
||||
!disabled.contains(server)
|
||||
}
|
||||
/// HTTP analog of [`Self::is_stdio_server_configured`]: `true` iff
|
||||
/// `server` has an enabled, **non-managed** `Http` / `Sse` config entry.
|
||||
/// `server` has an enabled `Http` / `Sse` config entry.
|
||||
/// Gates [`crate::session::mcp_restart::maybe_schedule_http_recovery`].
|
||||
/// Managed connectors (`MANAGED_MCP_PREFIX`) are excluded — out of scope
|
||||
/// for in-place recovery; this mirrors the dispatcher's filter.
|
||||
pub(crate) async fn is_http_server_configured(&self, server: &str) -> bool {
|
||||
if server.starts_with(crate::session::managed_mcp::MANAGED_MCP_PREFIX) {
|
||||
return false;
|
||||
}
|
||||
let mcp_state = self.mcp_state.lock().await;
|
||||
let is_http_in_configs = mcp_state.configs.iter().any(|c| {
|
||||
matches!(
|
||||
@@ -1076,17 +828,6 @@ impl SessionActor {
|
||||
&mcp_server_configs,
|
||||
cwd,
|
||||
));
|
||||
let managed_count = mcp_server_configs
|
||||
.iter()
|
||||
.filter(|c| {
|
||||
mcp_server_name(c).starts_with(crate::session::managed_mcp::MANAGED_MCP_PREFIX)
|
||||
})
|
||||
.count() as u32;
|
||||
self.events
|
||||
.emit(kigi_file_utils::events::Event::McpManagedConfigResult {
|
||||
server_count: managed_count,
|
||||
error: None,
|
||||
});
|
||||
}
|
||||
let configs_to_start: Vec<_> = mcp_server_configs
|
||||
.iter()
|
||||
@@ -1264,14 +1005,10 @@ impl SessionActor {
|
||||
let tool_bridge = self.agent.borrow().tool_bridge().clone();
|
||||
let gateway = self.notifications.gateway.clone();
|
||||
let tool_snapshot = self.tool_metadata_snapshot.clone();
|
||||
let managed_mcp_handle = self.managed_mcp_handle.clone();
|
||||
let mcp_reminder_dirty = Arc::clone(&self.mcp_reminder_dirty);
|
||||
let mcp_handshakes_done = Arc::clone(&self.mcp_handshakes_done);
|
||||
let session_id_owned = self.session_info.id.0.clone();
|
||||
let mcps_root_bg = self.cursor_mcps_root();
|
||||
let disabled_gateway_tools_bg = crate::util::config::get_all_mcp_disabled_tools(
|
||||
std::path::Path::new(&self.session_info.cwd),
|
||||
);
|
||||
let server_transport_map: std::collections::HashMap<String, &'static str> =
|
||||
mcp_server_configs
|
||||
.iter()
|
||||
@@ -1348,13 +1085,7 @@ impl SessionActor {
|
||||
Ok((server_name, handles, server_start.elapsed(), timeout_sec))
|
||||
}
|
||||
Err(e) => {
|
||||
let needs_auth = if server_name
|
||||
.starts_with(crate::session::managed_mcp::MANAGED_MCP_PREFIX)
|
||||
{
|
||||
e.is_auth_rejection()
|
||||
} else {
|
||||
client.has_auth()
|
||||
};
|
||||
let needs_auth = client.has_auth();
|
||||
tracing::warn!(
|
||||
server = server_name.as_str(), elapsed_ms = server_start
|
||||
.elapsed().as_millis() as u64, timeout_sec, error = % e,
|
||||
@@ -1664,11 +1395,9 @@ impl SessionActor {
|
||||
refresh_mcp_snapshot_and_schedule_reminder_with(
|
||||
tool_bridge.clone(),
|
||||
Arc::clone(&mcp_state_bg),
|
||||
managed_mcp_handle.clone(),
|
||||
tool_snapshot,
|
||||
mcp_reminder_dirty,
|
||||
true,
|
||||
&disabled_gateway_tools_bg,
|
||||
mcps_root_bg,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -19,27 +19,12 @@ impl McpReminderMode {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn gateway_tool_is_disabled(
|
||||
tool: &crate::session::managed_mcp::GatewayTool,
|
||||
disabled_gateway_tools: &std::collections::HashMap<String, std::collections::HashSet<String>>,
|
||||
) -> bool {
|
||||
let qualified_name = tool.qualified_name();
|
||||
disabled_gateway_tools
|
||||
.get(crate::util::config::MANAGED_GATEWAY_DISABLED_CONNECTORS_KEY)
|
||||
.is_some_and(|set| set.contains(&tool.connector_id))
|
||||
|| disabled_gateway_tools
|
||||
.get(&tool.connector_id)
|
||||
.is_some_and(|set| set.contains(&qualified_name))
|
||||
}
|
||||
|
||||
pub(super) async fn refresh_mcp_snapshot_and_schedule_reminder_with(
|
||||
tool_bridge: Arc<crate::tools::bridge::ToolBridge>,
|
||||
mcp_state: Arc<TokioMutex<McpState>>,
|
||||
managed_mcp_handle: crate::session::managed_mcp::ManagedMcpStateHandle,
|
||||
tool_metadata_snapshot: Arc<std::sync::Mutex<crate::session::tool_index::ToolMetadataSnapshot>>,
|
||||
mcp_reminder_dirty: Arc<std::sync::atomic::AtomicBool>,
|
||||
mcp_initialized: bool,
|
||||
disabled_gateway_tools: &std::collections::HashMap<String, std::collections::HashSet<String>>,
|
||||
// External harness only: per-workspace `mcps/` descriptor root. `Some` makes
|
||||
// this refresh also update the on-disk descriptor mirror so late-connecting
|
||||
// servers become discoverable; `None` for other agent types (no-op).
|
||||
@@ -51,7 +36,7 @@ pub(super) async fn refresh_mcp_snapshot_and_schedule_reminder_with(
|
||||
|
||||
let all_defs = tool_bridge.tool_definitions().await;
|
||||
let mut seen_tools = std::collections::HashSet::new();
|
||||
let mut mcp_tools: Vec<ToolMetadata> = all_defs
|
||||
let mcp_tools: Vec<ToolMetadata> = all_defs
|
||||
.iter()
|
||||
.filter(|d| d.function.name.contains("__"))
|
||||
.filter(|d| seen_tools.insert(d.function.name.clone()))
|
||||
@@ -68,61 +53,6 @@ pub(super) async fn refresh_mcp_snapshot_and_schedule_reminder_with(
|
||||
})
|
||||
.collect();
|
||||
|
||||
let (gateway_catalog, mut gateway_connectors) = {
|
||||
let state = managed_mcp_handle.lock().await;
|
||||
let catalog = if state.gateway_tools_active {
|
||||
match &state.gateway_tool_cache {
|
||||
crate::session::managed_mcp::GatewayToolCatalogCache::Ready(catalog) => {
|
||||
Some(catalog.clone())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let connectors: Vec<String> = state.gateway_tool_connectors_seen.iter().cloned().collect();
|
||||
(catalog, connectors)
|
||||
};
|
||||
|
||||
if let Some(catalog) = gateway_catalog.as_ref() {
|
||||
gateway_connectors.extend(catalog.tools.iter().map(|tool| tool.connector_id.clone()));
|
||||
}
|
||||
gateway_connectors.sort_unstable();
|
||||
gateway_connectors.dedup();
|
||||
let mut gateway_resource_entries = Vec::new();
|
||||
if let Some(catalog) = gateway_catalog.as_ref() {
|
||||
for tool in &catalog.tools {
|
||||
let qualified_name = tool.qualified_name();
|
||||
if gateway_tool_is_disabled(tool, disabled_gateway_tools) {
|
||||
continue;
|
||||
}
|
||||
if !seen_tools.insert(qualified_name.clone()) {
|
||||
continue;
|
||||
}
|
||||
gateway_resource_entries.push((
|
||||
qualified_name.clone(),
|
||||
kigi_tools::types::resources::ManagedGatewayToolSource {
|
||||
connector_id: tool.connector_id.clone(),
|
||||
connector_name: tool.connector_name.clone(),
|
||||
tool_id: tool.tool_id.clone(),
|
||||
tool_name: tool.tool_name.clone(),
|
||||
call_id: tool.call_id.clone(),
|
||||
},
|
||||
));
|
||||
// Gateway ids are the model/search contract. Display labels stay
|
||||
// out of ToolMetadata so search_tool and permissions use stable ids:
|
||||
// connector_id/tool_id here, connector_name/tool_name in UI only.
|
||||
mcp_tools.push(ToolMetadata {
|
||||
qualified_name,
|
||||
server_name: tool.connector_id.clone(),
|
||||
tool_name: tool.tool_id.clone(),
|
||||
description: tool.description.clone(),
|
||||
parameters: extract_parameter_names(&tool.json_schema),
|
||||
input_schema: tool.json_schema.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let servers_with_tools: std::collections::HashSet<&str> =
|
||||
mcp_tools.iter().map(|t| t.server_name.as_str()).collect();
|
||||
|
||||
@@ -150,12 +80,6 @@ pub(super) async fn refresh_mcp_snapshot_and_schedule_reminder_with(
|
||||
snapshot.mcp_initialized = mcp_initialized;
|
||||
}
|
||||
|
||||
tool_bridge
|
||||
.update_resource(kigi_tools::types::resources::ManagedGatewayToolCatalog(
|
||||
gateway_resource_entries.into_iter().collect(),
|
||||
))
|
||||
.await;
|
||||
|
||||
mcp_reminder_dirty.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
tracing::debug!("MCP snapshot updated, reminder marked dirty");
|
||||
|
||||
@@ -170,30 +94,8 @@ pub(super) async fn refresh_mcp_snapshot_and_schedule_reminder_with(
|
||||
.map(|(n, c)| (n.clone(), Arc::clone(c)))
|
||||
.collect()
|
||||
};
|
||||
let protected_connectors = clients.iter().map(|(name, _)| name.clone()).collect();
|
||||
let mut gateway_descriptors = Vec::new();
|
||||
if let Some(catalog) = gateway_catalog.as_ref() {
|
||||
for tool in &catalog.tools {
|
||||
if gateway_tool_is_disabled(tool, disabled_gateway_tools) {
|
||||
continue;
|
||||
}
|
||||
gateway_descriptors.push(crate::session::mcp_descriptors::GatewayToolDescriptor {
|
||||
connector_id: tool.connector_id.clone(),
|
||||
tool_id: tool.tool_id.clone(),
|
||||
description: tool.description.clone(),
|
||||
json_schema: tool.json_schema.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
crate::session::mcp_descriptors::materialize_descriptors_for_clients(&mcps_root, clients)
|
||||
.await;
|
||||
crate::session::mcp_descriptors::materialize_descriptors_for_gateway_tools(
|
||||
&mcps_root,
|
||||
gateway_descriptors,
|
||||
gateway_connectors,
|
||||
protected_connectors,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,35 +103,14 @@ pub(super) async fn refresh_mcp_snapshot_and_schedule_reminder_with(
|
||||
pub(crate) async fn refresh_mcp_snapshot_for_test(
|
||||
tool_bridge: Arc<crate::tools::bridge::ToolBridge>,
|
||||
mcp_state: Arc<TokioMutex<McpState>>,
|
||||
managed_mcp_handle: crate::session::managed_mcp::ManagedMcpStateHandle,
|
||||
tool_metadata_snapshot: Arc<std::sync::Mutex<crate::session::tool_index::ToolMetadataSnapshot>>,
|
||||
) {
|
||||
refresh_mcp_snapshot_for_test_with_disabled(
|
||||
tool_bridge,
|
||||
mcp_state,
|
||||
managed_mcp_handle,
|
||||
tool_metadata_snapshot,
|
||||
&Default::default(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn refresh_mcp_snapshot_for_test_with_disabled(
|
||||
tool_bridge: Arc<crate::tools::bridge::ToolBridge>,
|
||||
mcp_state: Arc<TokioMutex<McpState>>,
|
||||
managed_mcp_handle: crate::session::managed_mcp::ManagedMcpStateHandle,
|
||||
tool_metadata_snapshot: Arc<std::sync::Mutex<crate::session::tool_index::ToolMetadataSnapshot>>,
|
||||
disabled_gateway_tools: &std::collections::HashMap<String, std::collections::HashSet<String>>,
|
||||
) {
|
||||
refresh_mcp_snapshot_and_schedule_reminder_with(
|
||||
tool_bridge,
|
||||
mcp_state,
|
||||
managed_mcp_handle,
|
||||
tool_metadata_snapshot,
|
||||
Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||||
false,
|
||||
disabled_gateway_tools,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -196,9 +196,6 @@ impl SessionActor {
|
||||
std::sync::Arc::new(tool_index),
|
||||
))
|
||||
.await;
|
||||
if let Some(client) = self.rebuild_spec.managed_gateway_tool_client.clone() {
|
||||
bridge.update_resource(client).await;
|
||||
}
|
||||
let plan_path = self.plan_mode.lock().plan_file_path().to_path_buf();
|
||||
bridge
|
||||
.update_resource(kigi_tools::types::resources::PlanFilePath(plan_path))
|
||||
|
||||
@@ -509,72 +509,9 @@ impl SessionActor {
|
||||
folder_path: server_dir.map(|d| d.to_string_lossy().to_string()),
|
||||
});
|
||||
}
|
||||
let gateway_entries = self.gather_gateway_mcp_servers(mcps_root.as_deref()).await;
|
||||
entries.extend(gateway_entries);
|
||||
entries.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
entries
|
||||
}
|
||||
async fn gather_gateway_mcp_servers(
|
||||
&self,
|
||||
mcps_root: Option<&std::path::Path>,
|
||||
) -> Vec<kigi_agent::prompt::user_message::McpServerEntry> {
|
||||
use kigi_agent::prompt::user_message::McpServerEntry;
|
||||
let disabled_gateway_tools = crate::util::config::get_all_mcp_disabled_tools(
|
||||
std::path::Path::new(&self.session_info.cwd),
|
||||
);
|
||||
let catalog = {
|
||||
let state = self.managed_mcp_handle.lock().await;
|
||||
if state.gateway_tools_active {
|
||||
match &state.gateway_tool_cache {
|
||||
crate::session::managed_mcp::GatewayToolCatalogCache::Ready(catalog) => {
|
||||
Some(catalog.clone())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let Some(catalog) = catalog else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut connectors = std::collections::BTreeMap::<String, String>::new();
|
||||
let mut gateway_connectors: Vec<String> = catalog
|
||||
.tools
|
||||
.iter()
|
||||
.map(|tool| tool.connector_id.clone())
|
||||
.collect();
|
||||
gateway_connectors.sort_unstable();
|
||||
gateway_connectors.dedup();
|
||||
let mut descriptors = Vec::new();
|
||||
for tool in &catalog.tools {
|
||||
if gateway_tool_is_disabled(tool, &disabled_gateway_tools) {
|
||||
continue;
|
||||
}
|
||||
connectors
|
||||
.entry(tool.connector_id.clone())
|
||||
.or_insert_with(|| tool.connector_name.clone());
|
||||
descriptors.push(crate::session::mcp_descriptors::GatewayToolDescriptor {
|
||||
connector_id: tool.connector_id.clone(),
|
||||
tool_id: tool.tool_id.clone(),
|
||||
description: tool.description.clone(),
|
||||
json_schema: tool.json_schema.clone(),
|
||||
});
|
||||
}
|
||||
connectors
|
||||
.into_iter()
|
||||
.map(|(connector_id, connector_name)| McpServerEntry {
|
||||
folder_path: mcps_root.map(|root| {
|
||||
crate::session::mcp_descriptors::server_descriptor_dir(root, &connector_id)
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}),
|
||||
name: connector_id,
|
||||
server_use_instructions: (!connector_name.trim().is_empty())
|
||||
.then_some(connector_name),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
/// Build a `PathRewriter` for sanitizing overlay paths in model-facing text.
|
||||
///
|
||||
/// Returns `None` when `display_cwd` is unset (no rewriting needed). Used
|
||||
|
||||
@@ -559,38 +559,7 @@ pub(super) async fn run_session(
|
||||
tracing::warn!(server = sname.as_str(), error = % e,
|
||||
"Failed to persist server enabled state to config"); } let _ = respond_to
|
||||
.send(Ok(())); }); } SessionCommand::ToggleMcpTool { server_name, tool_name,
|
||||
enabled, is_managed_gateway, respond_to } => { if is_managed_gateway { let
|
||||
mut disabled_tools = crate
|
||||
::util::config::get_all_mcp_disabled_tools(std::path::Path::new(& session
|
||||
.session_info.cwd)); if tool_name.is_empty() { let set = disabled_tools
|
||||
.entry(crate ::util::config::MANAGED_GATEWAY_DISABLED_CONNECTORS_KEY
|
||||
.to_string()).or_default(); if enabled { set.remove(& server_name); } else {
|
||||
set.insert(server_name.clone()); }
|
||||
if set.is_empty() { disabled_tools
|
||||
.remove(crate ::util::config::MANAGED_GATEWAY_DISABLED_CONNECTORS_KEY); } }
|
||||
else if enabled { if let Some(set) = disabled_tools.get_mut(& server_name) {
|
||||
set.remove(& tool_name); if set.is_empty() { disabled_tools.remove(&
|
||||
server_name); } } } else { disabled_tools.entry(server_name.clone())
|
||||
.or_default().insert(tool_name.clone()); } session
|
||||
.refresh_mcp_snapshot_and_schedule_reminder_with_disabled(& disabled_tools,).
|
||||
await; session.refresh_goal_harness_enabled(). await; let disabled_vec : Vec
|
||||
< String > = if tool_name.is_empty() { disabled_tools.get(crate
|
||||
::util::config::MANAGED_GATEWAY_DISABLED_CONNECTORS_KEY).map(| s | s.iter()
|
||||
.cloned().collect()).unwrap_or_default() } else { disabled_tools.get(&
|
||||
server_name).map(| s | s.iter().cloned().collect()).unwrap_or_default() };
|
||||
let notifications = session.notifications.gateway.clone(); let session_id =
|
||||
session.session_info.id.0.clone(); let server_for_persist = if tool_name
|
||||
.is_empty() { crate ::util::config::MANAGED_GATEWAY_DISABLED_CONNECTORS_KEY
|
||||
.to_string() } else { server_name.clone() }; tokio::task::spawn_local(async
|
||||
move { if let Err(e) = crate ::util::config::save_mcp_disabled_tools(&
|
||||
server_for_persist, & disabled_vec,). await { tracing::warn!(server =
|
||||
server_for_persist.as_str(), error = % e,
|
||||
"Failed to persist disabled_tools to config"); } let payload = crate
|
||||
::extensions::mcp::McpToolsChanged { session_id : session_id.to_string(),
|
||||
server_name : String::new(), tools : Vec::new(), }; if let Ok(params) =
|
||||
serde_json::value::to_raw_value(& payload) { notifications
|
||||
.forward_fire_and_forget(acp::ExtNotification::new("x.ai/mcp/tools_changed",
|
||||
params.into())); } let _ = respond_to.send(Ok(())); }); continue; } let
|
||||
enabled, respond_to } => { let
|
||||
qualified = format!("{}{}{}", server_name, crate
|
||||
::session::mcp_servers::MCP_TOOL_NAME_DELIMITER, tool_name,); let mut
|
||||
mcp_state = session.mcp_state.lock(). await; if enabled { if let Some(set) =
|
||||
@@ -663,11 +632,7 @@ pub(super) async fn run_session(
|
||||
SessionCommand::McpAuthTrigger { server_name, respond_to } => { let s =
|
||||
session.clone(); tokio::task::spawn_local(async move { let result = s
|
||||
.handle_mcp_auth_trigger(& server_name). await; let _ = respond_to
|
||||
.send(result); }); } SessionCommand::GetManagedGatewayDisabledTools {
|
||||
respond_to } => { let disabled_tools = crate
|
||||
::util::config::get_all_mcp_disabled_tools(std::path::Path::new(& session
|
||||
.session_info.cwd),); let _ = respond_to.send(disabled_tools); }
|
||||
SessionCommand::RetryAuthRequiredServers { respond_to } => { let s = session
|
||||
.send(result); }); } SessionCommand::RetryAuthRequiredServers { respond_to } => { let s = session
|
||||
.clone(); tokio::task::spawn_local(async move { s
|
||||
.retry_auth_required_servers(). await; let _ = respond_to.send(()); }); }
|
||||
SessionCommand::RefreshMcpSearchIndex => { session
|
||||
|
||||
@@ -139,9 +139,6 @@ pub(crate) async fn spawn_session_actor(
|
||||
persisted_announcement_state: Option<crate::session::announcement_state::AnnouncementState>,
|
||||
memory_config: Option<crate::config::MemoryConfig>,
|
||||
feedback_flags: crate::session::feedback_manager::FeedbackFlags,
|
||||
managed_mcp_handle: crate::session::managed_mcp::ManagedMcpStateHandle,
|
||||
managed_mcp_expires_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
managed_mcp_proxy_base_url: String,
|
||||
session_model_id: acp::ModelId,
|
||||
session_yolo_mode: bool,
|
||||
session_auto_mode: bool,
|
||||
@@ -150,8 +147,6 @@ pub(crate) async fn spawn_session_actor(
|
||||
max_retries: Option<u32>,
|
||||
web_search_config: kigi_tools::implementations::WebSearchConfig,
|
||||
web_fetch_config: kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig,
|
||||
image_gen_config: kigi_tools::implementations::grok_build::image_gen::ImageGenConfig,
|
||||
video_gen_config: kigi_tools::implementations::grok_build::video_gen::VideoGenConfig,
|
||||
app_builder_deployer_config: kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig,
|
||||
write_file_enabled: bool,
|
||||
goal_enabled: bool,
|
||||
@@ -660,14 +655,6 @@ pub(crate) async fn spawn_session_actor(
|
||||
let context_window_tokens = context_window_override
|
||||
.map(|c| c.get())
|
||||
.unwrap_or(sampling_config.context_window);
|
||||
let managed_gateway_tool_client = auth_manager.as_ref().map(|am| {
|
||||
kigi_tools::types::resources::ManagedGatewayToolClient(Arc::new(
|
||||
ShellManagedGatewayToolClient {
|
||||
proxy_base_url: managed_mcp_proxy_base_url.clone(),
|
||||
auth_manager: am.clone(),
|
||||
},
|
||||
))
|
||||
});
|
||||
let mcp_state = {
|
||||
let mut state = McpState::new_with_meta(mcp_servers.clone(), mcp_meta_config_map);
|
||||
if let Some(ref pool) = parent_mcp_pool {
|
||||
@@ -711,8 +698,6 @@ pub(crate) async fn spawn_session_actor(
|
||||
web_search_config: web_search_config.clone(),
|
||||
backend_search: backend_tools_enabled,
|
||||
web_fetch_config: web_fetch_config.clone(),
|
||||
image_gen_config: image_gen_config.clone(),
|
||||
video_gen_config: video_gen_config.clone(),
|
||||
app_builder_deployer_config: app_builder_deployer_config.clone(),
|
||||
write_file_enabled,
|
||||
subagents_enabled,
|
||||
@@ -739,7 +724,6 @@ pub(crate) async fn spawn_session_actor(
|
||||
respect_gitignore,
|
||||
path_not_found_hints,
|
||||
mcp_state: mcp_state.clone(),
|
||||
managed_gateway_tool_client: managed_gateway_tool_client.clone(),
|
||||
is_non_interactive: startup_hints.non_interactive,
|
||||
system_prompt_label,
|
||||
owner_session_id: Some(session_info.id.0.to_string()),
|
||||
@@ -1125,8 +1109,6 @@ pub(crate) async fn spawn_session_actor(
|
||||
goal_plan_reconciled: std::sync::atomic::AtomicBool::new(false),
|
||||
pending_classifier_completions: parking_lot::Mutex::new(VecDeque::new()),
|
||||
goal_classifier_in_flight: std::sync::atomic::AtomicBool::new(false),
|
||||
managed_mcp_handle,
|
||||
managed_mcp_expires_at: std::sync::Mutex::new(managed_mcp_expires_at),
|
||||
tool_metadata_snapshot: Arc::new(std::sync::Mutex::new(Default::default())),
|
||||
mcp_announced_servers: Mutex::new(
|
||||
persisted_announcement_state
|
||||
@@ -1224,14 +1206,6 @@ pub(crate) async fn spawn_session_actor(
|
||||
))
|
||||
.await;
|
||||
}
|
||||
if let Some(client) = managed_gateway_tool_client.clone() {
|
||||
session
|
||||
.agent
|
||||
.borrow()
|
||||
.tool_bridge()
|
||||
.update_resource(client)
|
||||
.await;
|
||||
}
|
||||
{
|
||||
let plan_path = session.plan_mode.lock().plan_file_path().to_path_buf();
|
||||
session
|
||||
@@ -1448,7 +1422,6 @@ pub(crate) async fn spawn_session_actor(
|
||||
permission_handle: permissions_for_handle,
|
||||
attribution_callback: attribution_callback_for_handle,
|
||||
agent_name: agent_name_for_handle,
|
||||
managed_mcp_proxy_base_url,
|
||||
session_default_agent_profile,
|
||||
allowed_subagent_types: allowed_subagent_types_for_handle,
|
||||
hook_registry: hook_registry_for_handle,
|
||||
@@ -1541,9 +1514,6 @@ pub(crate) async fn spawn_session_on_thread(
|
||||
persisted_announcement_state: Option<crate::session::announcement_state::AnnouncementState>,
|
||||
memory_config: Option<crate::config::MemoryConfig>,
|
||||
feedback_flags: crate::session::feedback_manager::FeedbackFlags,
|
||||
managed_mcp_handle: crate::session::managed_mcp::ManagedMcpStateHandle,
|
||||
managed_mcp_expires_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
managed_mcp_proxy_base_url: String,
|
||||
session_model_id: acp::ModelId,
|
||||
session_yolo_mode: bool,
|
||||
session_auto_mode: bool,
|
||||
@@ -1552,8 +1522,6 @@ pub(crate) async fn spawn_session_on_thread(
|
||||
max_retries: Option<u32>,
|
||||
web_search_config: kigi_tools::implementations::WebSearchConfig,
|
||||
web_fetch_config: kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig,
|
||||
image_gen_config: kigi_tools::implementations::grok_build::image_gen::ImageGenConfig,
|
||||
video_gen_config: kigi_tools::implementations::grok_build::video_gen::VideoGenConfig,
|
||||
app_builder_deployer_config: kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig,
|
||||
write_file_enabled: bool,
|
||||
goal_enabled: bool,
|
||||
@@ -1688,9 +1656,6 @@ pub(crate) async fn spawn_session_on_thread(
|
||||
persisted_announcement_state,
|
||||
memory_config,
|
||||
feedback_flags,
|
||||
managed_mcp_handle,
|
||||
managed_mcp_expires_at,
|
||||
managed_mcp_proxy_base_url,
|
||||
session_model_id,
|
||||
session_yolo_mode,
|
||||
session_auto_mode,
|
||||
@@ -1699,8 +1664,6 @@ pub(crate) async fn spawn_session_on_thread(
|
||||
max_retries,
|
||||
web_search_config,
|
||||
web_fetch_config,
|
||||
image_gen_config,
|
||||
video_gen_config,
|
||||
app_builder_deployer_config,
|
||||
write_file_enabled,
|
||||
goal_enabled,
|
||||
|
||||
@@ -480,31 +480,13 @@ impl SessionActor {
|
||||
}
|
||||
});
|
||||
let _drainer_guard = crate::util::AbortOnDrop(drainer);
|
||||
while let Some((idx, mut result)) = dispatch_rx.recv().await {
|
||||
while let Some((idx, result)) = dispatch_rx.recv().await {
|
||||
let prepared = approved_slots[idx]
|
||||
.take()
|
||||
.expect("dispatch index should match an approved slot exactly once");
|
||||
self.signals_handle().record_tool_call(&prepared.tool_name);
|
||||
let tool_start = self.events.tool_started(prepared.tool_name.clone());
|
||||
let mut post_tool_use_result: Option<serde_json::Value> = None;
|
||||
if let Some((server, _)) =
|
||||
crate::session::mcp_servers::parse_mcp_tool_name(&prepared.tool_name)
|
||||
&& server.starts_with(crate::session::managed_mcp::MANAGED_MCP_PREFIX)
|
||||
{
|
||||
let auth_rejected = match &result {
|
||||
Err(err) => kigi_mcp::servers::is_auth_rejection_message(&err.to_string()),
|
||||
Ok(tool_result) => {
|
||||
tool_result.output.is_error()
|
||||
&& kigi_mcp::servers::is_auth_rejection_message(
|
||||
&tool_result.prompt_text,
|
||||
)
|
||||
}
|
||||
};
|
||||
if auth_rejected && self.reactive_managed_reauth(&server).await.is_ok() {
|
||||
result = dispatch_tool(&self.workspace_ops, &prepared, &self.session_info.id.0)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
let tool_result_size_bytes = match &result {
|
||||
Ok(tool_result) => tool_result.prompt_text.len() as i64,
|
||||
Err(_) => 0,
|
||||
@@ -745,12 +727,6 @@ impl SessionActor {
|
||||
}
|
||||
let mcp_parts = parse_mcp_tool_name(&call.function.name);
|
||||
let is_mcp_tool = mcp_parts.is_some();
|
||||
if let Some((ref server, _)) = mcp_parts
|
||||
&& server.starts_with(crate::session::managed_mcp::MANAGED_MCP_PREFIX)
|
||||
{
|
||||
let _span = tracing::info_span!("tool.refresh_managed_mcp").entered();
|
||||
self.refresh_managed_mcp_if_stale().await;
|
||||
}
|
||||
if is_mcp_tool && !self.mcp_state.lock().await.is_initialized() {
|
||||
match self.mcp_strategy {
|
||||
McpInitStrategy::Blocking => {
|
||||
@@ -1483,33 +1459,6 @@ impl SessionActor {
|
||||
vec![],
|
||||
vec![],
|
||||
),
|
||||
ToolInput::ImageGen(ig) => (
|
||||
format!("imagine: {}", ig.prompt),
|
||||
acp::ToolKind::Other,
|
||||
vec![],
|
||||
vec![],
|
||||
),
|
||||
ToolInput::ImageEdit(ie) => (
|
||||
format!("imagine-edit: {}", ie.prompt),
|
||||
acp::ToolKind::Other,
|
||||
vec![],
|
||||
vec![],
|
||||
),
|
||||
ToolInput::ImageToVideo(i2v) => (
|
||||
format!(
|
||||
"image-to-video: {}",
|
||||
i2v.prompt.as_deref().unwrap_or(&i2v.image)
|
||||
),
|
||||
acp::ToolKind::Other,
|
||||
vec![],
|
||||
vec![],
|
||||
),
|
||||
ToolInput::ReferenceToVideo(r2v) => (
|
||||
format!("reference-to-video: {}", r2v.prompt),
|
||||
acp::ToolKind::Other,
|
||||
vec![],
|
||||
vec![],
|
||||
),
|
||||
ToolInput::MCPTool(mcp_tool) => (
|
||||
mcp_tool.tool_name.to_owned(),
|
||||
acp::ToolKind::Other,
|
||||
|
||||
@@ -128,7 +128,7 @@ pub(super) fn resolve_session_shell() -> String {
|
||||
}
|
||||
|
||||
/// Key in `ToolError::details` that carries the HTTP status code.
|
||||
/// Used by both error producers (image_gen, video_gen, test helpers) and
|
||||
/// Used by both error producers (tool HTTP clients, test helpers) and
|
||||
/// the `is_auth_tool_error` classifier to avoid accidental key mismatch.
|
||||
pub(crate) const HTTP_STATUS_DETAILS_KEY: &str = "status";
|
||||
|
||||
|
||||
@@ -574,16 +574,6 @@ impl SessionActor {
|
||||
.data(format!("failed to save user images to assets dir: {e}"))
|
||||
})?
|
||||
};
|
||||
let attached_image_refs = if self.is_cursor_harness() {
|
||||
Vec::new()
|
||||
} else {
|
||||
crate::session::placeholder_images::attached_image_references(&user_images)
|
||||
};
|
||||
self.tool_bridge_handle()
|
||||
.update_resource(kigi_tools::types::resources::AttachedImages(
|
||||
attached_image_refs,
|
||||
))
|
||||
.await;
|
||||
let prompt_text_for_hook = user_message.clone();
|
||||
{
|
||||
let origin = super::super::PromptOrigin::from_prompt_id(prompt_id);
|
||||
|
||||
Reference in new issue
Block a user