fix(tui/agents): show built-ins as available and skip install prompts

- mark built-in agents that resolve to the current binary as "available" and remove install hints in Agents UI
- thread self-resolved status through overview rendering for consistent labeling in overlay and bottom pane
- add regression test for available status copy

Refs #429
This commit is contained in:
James Peter
2025-11-26 09:36:51 +10:00
parent 01519ee282
commit cc5877d933
3 changed files with 43 additions and 5 deletions
@@ -13,7 +13,7 @@ use super::BottomPane;
#[derive(Clone, Debug)]
pub(crate) struct AgentsOverviewView {
agents: Vec<(String, bool /*enabled*/ , bool /*installed*/ , String /*command*/ )>,
agents: Vec<(String, bool /*enabled*/ , bool /*installed*/ , bool /*self_resolved*/ , String /*command*/ )>,
commands: Vec<String>,
selected: usize,
is_complete: bool,
@@ -22,7 +22,7 @@ pub(crate) struct AgentsOverviewView {
impl AgentsOverviewView {
pub fn new(
agents: Vec<(String, bool, bool, String)>,
agents: Vec<(String, bool, bool, bool, String)>,
commands: Vec<String>,
selected_index: usize,
app_event_tx: AppEventSender,
@@ -50,10 +50,12 @@ impl AgentsOverviewView {
.map(|(name, _, _, _)| name.len())
.max()
.unwrap_or(0);
for (i, (name, enabled, installed, _cmd)) in self.agents.iter().enumerate() {
for (i, (name, enabled, installed, self_resolved, _cmd)) in self.agents.iter().enumerate() {
let sel = i == self.selected;
let (status_text, status_color) = if !*enabled {
("disabled", crate::colors::error())
} else if *self_resolved {
("available", crate::colors::success())
} else if !*installed {
("not installed", crate::colors::warning())
} else {
@@ -74,7 +76,9 @@ impl AgentsOverviewView {
];
if sel {
spans.push(Span::raw(" "));
let hint = if !*installed {
let hint = if *self_resolved {
"(press Enter to configure)"
} else if !*installed {
"(press Enter to install)"
} else {
"(press Enter to configure)"
@@ -201,3 +205,29 @@ impl<'a> BottomPaneView<'a> for AgentsOverviewView {
.render(Rect { x: inner.x.saturating_add(1), y: inner.y, width: inner.width.saturating_sub(2), height: inner.height }, buf);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtin_agent_shows_available_status() {
let app_event_tx = AppEventSender::new(|_| {});
let mut view = AgentsOverviewView::new(
vec![("code".to_string(), true, true, true, "coder".to_string())],
Vec::new(),
0,
app_event_tx,
);
let lines = view.build_lines();
let combined = lines
.iter()
.map(|l| l.to_string())
.collect::<Vec<_>>()
.join("\n");
assert!(combined.contains("available"), "expected status text to show 'available', got: {combined}");
assert!(!combined.contains("not installed"));
}
}
+3
View File
@@ -20027,6 +20027,7 @@ Have we met every part of this goal and is there no further work to do?"#
name: cfg.name.clone(),
enabled: cfg.enabled,
installed,
self_resolved: builtin,
description: Self::agent_description_for(
&cfg.name,
Some(&cfg.command),
@@ -20052,6 +20053,7 @@ Have we met every part of this goal and is there no further work to do?"#
name: cfg.name.clone(),
enabled: cfg.enabled,
installed,
self_resolved: builtin,
description: Self::agent_description_for(
&cfg.name,
Some(&cfg.command),
@@ -20073,6 +20075,7 @@ Have we met every part of this goal and is there no further work to do?"#
name: name.clone(),
enabled: true,
installed,
self_resolved: builtin,
description: Self::agent_description_for(name, Some(&cmd), None),
});
}
@@ -434,6 +434,7 @@ pub(crate) struct AgentOverviewRow {
pub(crate) name: String,
pub(crate) enabled: bool,
pub(crate) installed: bool,
pub(crate) self_resolved: bool,
pub(crate) description: Option<String>,
}
@@ -546,6 +547,8 @@ impl AgentsSettingsContent {
let selected = idx == state.selected;
let status = if !row.enabled {
("disabled", crate::colors::error())
} else if row.self_resolved {
("available", crate::colors::success())
} else if !row.installed {
("not installed", crate::colors::warning())
} else {
@@ -605,7 +608,9 @@ impl AgentsSettingsContent {
if selected && !showed_desc {
spans.push(Span::raw(" "));
let hint = if !row.installed {
let hint = if row.self_resolved {
"Enter to configure"
} else if !row.installed {
"Enter to install"
} else {
"Enter to configure"