#[cfg(feature = "mcp")]
use crate::extras::mcp::McpClientManager;
use crate::session::Session;
use crate::sync_util::LockExt;
use crate::ui::panel_data::{ContextGauge, GitSnapshot, LeftPanelInfo};
use crate::ui::renderer::PanelData;
use crate::ui::sysload::SharedSysLoad;
const FOLD_WARN_PCT: u16 = 75;
pub(crate) fn build_left_panel_info(
session: &Session,
activity: &[String],
git: Option<GitSnapshot>,
) -> LeftPanelInfo {
let used = session.total_estimated_tokens;
let window = session.context_window;
let pct = ((used.saturating_mul(100)).checked_div(window).unwrap_or(0)).min(100) as u16;
LeftPanelInfo {
context: ContextGauge {
used,
window,
pct,
compactions: session.compactions.len(),
fold_soon: pct >= FOLD_WARN_PCT,
},
activity: activity.to_vec(),
git,
}
}
static PANEL_MODIFIED_CACHE: std::sync::Mutex<Option<(u64, std::path::PathBuf, Vec<String>)>> =
std::sync::Mutex::new(None);
pub(crate) fn panel_modified_cached(cwd: &std::path::Path) -> Vec<String> {
let v = crate::agent::tools::modified::version();
{
let guard = PANEL_MODIFIED_CACHE.lock_ignore_poison();
if let Some((cached_v, cached_cwd, cached_data)) = guard.as_ref()
&& *cached_v == v
&& cached_cwd.as_path() == cwd
{
return cached_data.clone();
}
}
let cwd_buf = cwd.to_path_buf();
let rendered: Vec<String> = crate::agent::tools::modified::recent(256)
.into_iter()
.map(|p| {
p.strip_prefix(&cwd_buf)
.map(|r| r.display().to_string())
.unwrap_or_else(|_| {
p.file_name()
.and_then(|n| n.to_str())
.map(String::from)
.unwrap_or_else(|| p.display().to_string())
})
})
.collect();
let mut guard = PANEL_MODIFIED_CACHE.lock_ignore_poison();
*guard = Some((v, cwd_buf, rendered.clone()));
rendered
}
pub(crate) fn build_panel_data(
session: &Session,
sysload: Option<&SharedSysLoad>,
#[cfg(feature = "mcp")] mcp_manager: Option<&McpClientManager>,
#[cfg(feature = "lsp")] lsp_manager: Option<&std::sync::Arc<crate::lsp::manager::LspManager>>,
) -> PanelData {
use std::path::Path;
#[cfg(feature = "mcp")]
let mcp: Vec<(String, bool)> = mcp_manager
.map(|m| {
let mut rows: Vec<(String, bool)> = m
.connections_snapshot()
.into_iter()
.map(|(name, _conn)| (name, true))
.collect();
for name in m.failed_servers() {
rows.push((name, false));
}
rows
})
.unwrap_or_default();
#[cfg(not(feature = "mcp"))]
let mcp: Vec<(String, bool)> = Vec::new();
#[cfg(feature = "lsp")]
let lsp: Vec<(String, String, bool)> = lsp_manager
.map(|m| {
let cwd_path = Path::new(session.working_dir.as_str());
let shorten = |p: &Path| -> String {
p.strip_prefix(cwd_path)
.map(|r| r.display().to_string())
.unwrap_or_else(|_| {
p.file_name()
.and_then(|n| n.to_str())
.map(String::from)
.unwrap_or_else(|| p.display().to_string())
})
};
let mut all = Vec::new();
for (id, root) in m.active_servers() {
all.push((id, shorten(&root), true));
}
for (id, root) in m.broken_servers() {
all.push((id, shorten(&root), false));
}
all
})
.unwrap_or_default();
#[cfg(not(feature = "lsp"))]
let lsp: Vec<(String, String, bool)> = Vec::new();
let todos: Vec<(String, String)> = {
let list = crate::agent::tools::todo::TODO_LIST.lock_ignore_poison();
list.iter()
.take(8)
.map(|t| {
let status = match t.status.as_str() {
"in_progress" => "[~]",
"blocked" => "[!]",
_ => "[ ]",
};
(status.to_string(), t.content.to_string())
})
.collect()
};
let cwd_path = Path::new(session.working_dir.as_str()).to_path_buf();
let modified = panel_modified_cached(&cwd_path);
PanelData {
mcp,
lsp,
todos,
modified,
sysload: sysload.map(|s| s.snapshot()),
}
}
#[cfg(all(test, feature = "mcp"))]
mod tests {
use super::*;
use crate::extras::mcp::McpClientManager;
use crate::extras::mcp::config::McpServerConfig;
use std::collections::HashMap;
fn bogus_server() -> McpServerConfig {
McpServerConfig::Command {
command: "dirge-nonexistent-mcp-binary".to_string(),
args: vec![],
env: HashMap::new(),
allow_external_paths: false,
}
}
#[tokio::test]
async fn build_panel_data_surfaces_failed_mcp_servers_as_broken() {
let mut configs: HashMap<String, McpServerConfig> = HashMap::new();
configs.insert("ghost".to_string(), bogus_server());
let mgr = McpClientManager::connect_all(&configs).await;
let session = crate::session::Session::new("p", "m", 100_000);
let data = build_panel_data(
&session,
None,
Some(&mgr),
#[cfg(feature = "lsp")]
None,
);
assert_eq!(data.mcp.len(), 1, "failed server must still be listed");
assert_eq!(data.mcp[0].0, "ghost");
assert!(
!data.mcp[0].1,
"failed server must render as broken (ok=false)"
);
}
}