mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat: better agent picker in TUI (#12332)
<img width="486" height="112" alt="Screenshot 2026-02-20 at 15 04 52" src="https://github.com/user-attachments/assets/0d744f58-d902-4638-aeaf-27e7389ccd73" />
This commit is contained in:
committed by
GitHub
Unverified
parent
4d60c803ba
commit
5a30cd3f92
+85
-9
@@ -808,38 +808,57 @@ impl App {
|
||||
|
||||
async fn open_agent_picker(&mut self) {
|
||||
let thread_ids: Vec<ThreadId> = self.thread_event_channels.keys().cloned().collect();
|
||||
let mut agent_threads = Vec::new();
|
||||
for thread_id in thread_ids {
|
||||
if self.server.get_thread(thread_id).await.is_err() {
|
||||
self.thread_event_channels.remove(&thread_id);
|
||||
match self.server.get_thread(thread_id).await {
|
||||
Ok(thread) => {
|
||||
let session_source = thread.config_snapshot().await.session_source;
|
||||
agent_threads.push((
|
||||
thread_id,
|
||||
session_source.get_nickname(),
|
||||
session_source.get_agent_role(),
|
||||
));
|
||||
}
|
||||
Err(_) => {
|
||||
self.thread_event_channels.remove(&thread_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.thread_event_channels.is_empty() {
|
||||
if agent_threads.is_empty() {
|
||||
self.chat_widget
|
||||
.add_info_message("No agents available yet.".to_string(), None);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut thread_ids: Vec<ThreadId> = self.thread_event_channels.keys().cloned().collect();
|
||||
thread_ids.sort_by_key(ToString::to_string);
|
||||
agent_threads.sort_by(|(left, ..), (right, ..)| left.to_string().cmp(&right.to_string()));
|
||||
|
||||
let mut initial_selected_idx = None;
|
||||
let items: Vec<SelectionItem> = thread_ids
|
||||
let items: Vec<SelectionItem> = agent_threads
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, thread_id)| {
|
||||
.map(|(idx, (thread_id, agent_nickname, agent_role))| {
|
||||
if self.active_thread_id == Some(*thread_id) {
|
||||
initial_selected_idx = Some(idx);
|
||||
}
|
||||
let id = *thread_id;
|
||||
let is_primary = self.primary_thread_id == Some(*thread_id);
|
||||
let name = format_agent_picker_item_name(
|
||||
*thread_id,
|
||||
agent_nickname.as_deref(),
|
||||
agent_role.as_deref(),
|
||||
is_primary,
|
||||
);
|
||||
let uuid = thread_id.to_string();
|
||||
SelectionItem {
|
||||
name: thread_id.to_string(),
|
||||
name: name.clone(),
|
||||
description: Some(uuid.clone()),
|
||||
is_current: self.active_thread_id == Some(*thread_id),
|
||||
actions: vec![Box::new(move |tx| {
|
||||
tx.send(AppEvent::SelectAgentThread(id));
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
search_value: Some(thread_id.to_string()),
|
||||
search_value: Some(format!("{name} {uuid}")),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
@@ -2892,6 +2911,28 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_agent_picker_item_name(
|
||||
_thread_id: ThreadId,
|
||||
agent_nickname: Option<&str>,
|
||||
agent_role: Option<&str>,
|
||||
is_primary: bool,
|
||||
) -> String {
|
||||
if is_primary {
|
||||
return "Main [default]".to_string();
|
||||
}
|
||||
|
||||
let agent_nickname = agent_nickname
|
||||
.map(str::trim)
|
||||
.filter(|nickname| !nickname.is_empty());
|
||||
let agent_role = agent_role.map(str::trim).filter(|role| !role.is_empty());
|
||||
match (agent_nickname, agent_role) {
|
||||
(Some(agent_nickname), Some(agent_role)) => format!("{agent_nickname} [{agent_role}]"),
|
||||
(Some(agent_nickname), None) => agent_nickname.to_string(),
|
||||
(None, Some(agent_role)) => format!("[{agent_role}]"),
|
||||
(None, None) => "Agent".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -3072,6 +3113,41 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_picker_item_name_snapshot() {
|
||||
let thread_id =
|
||||
ThreadId::from_string("00000000-0000-0000-0000-000000000123").expect("valid thread id");
|
||||
let snapshot = [
|
||||
format!(
|
||||
"{} | {}",
|
||||
format_agent_picker_item_name(thread_id, Some("Robie"), Some("explorer"), true),
|
||||
thread_id
|
||||
),
|
||||
format!(
|
||||
"{} | {}",
|
||||
format_agent_picker_item_name(thread_id, Some("Robie"), Some("explorer"), false),
|
||||
thread_id
|
||||
),
|
||||
format!(
|
||||
"{} | {}",
|
||||
format_agent_picker_item_name(thread_id, Some("Robie"), None, false),
|
||||
thread_id
|
||||
),
|
||||
format!(
|
||||
"{} | {}",
|
||||
format_agent_picker_item_name(thread_id, None, Some("explorer"), false),
|
||||
thread_id
|
||||
),
|
||||
format!(
|
||||
"{} | {}",
|
||||
format_agent_picker_item_name(thread_id, None, None, false),
|
||||
thread_id
|
||||
),
|
||||
]
|
||||
.join("\n");
|
||||
assert_snapshot!("agent_picker_item_name", snapshot);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn active_non_primary_shutdown_target_returns_none_for_non_shutdown_event() -> Result<()>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
source: tui/src/app.rs
|
||||
expression: snapshot
|
||||
---
|
||||
Main [default] | 00000000-0000-0000-0000-000000000123
|
||||
Robie [explorer] | 00000000-0000-0000-0000-000000000123
|
||||
Robie | 00000000-0000-0000-0000-000000000123
|
||||
[explorer] | 00000000-0000-0000-0000-000000000123
|
||||
Agent | 00000000-0000-0000-0000-000000000123
|
||||
Reference in New Issue
Block a user