nit: better collab tui (#9551)

<img width="478" height="304" alt="Screenshot 2026-01-21 at 11 53 50"
src="https://github.com/user-attachments/assets/e2ef70de-2fff-44e0-a574-059177966ed2"
/>
This commit is contained in:
jif-oai
2026-01-21 11:53:58 +00:00
committed by GitHub
parent 13358fa131
commit f1b6a43907

View File

@@ -7,123 +7,122 @@ use codex_core::protocol::CollabAgentSpawnEndEvent;
use codex_core::protocol::CollabCloseEndEvent;
use codex_core::protocol::CollabWaitingBeginEvent;
use codex_core::protocol::CollabWaitingEndEvent;
use codex_protocol::ThreadId;
use ratatui::style::Stylize;
use ratatui::text::Line;
use ratatui::text::Span;
use std::collections::HashMap;
const COLLAB_PROMPT_PREVIEW_GRAPHEMES: usize = 160;
const COLLAB_AGENT_ERROR_PREVIEW_GRAPHEMES: usize = 160;
const COLLAB_AGENT_RESPONSE_PREVIEW_GRAPHEMES: usize = 240;
pub(crate) fn spawn_end(ev: CollabAgentSpawnEndEvent) -> PlainHistoryCell {
let CollabAgentSpawnEndEvent {
call_id,
sender_thread_id,
sender_thread_id: _,
new_thread_id,
prompt,
status,
} = ev;
let new_agent = new_thread_id
.map(|id| id.to_string())
.unwrap_or_else(|| "none".to_string());
.map(|id| Span::from(id.to_string()))
.unwrap_or_else(|| Span::from("not created").dim());
let mut details = vec![
detail_line("call", call_id),
detail_line("sender", sender_thread_id),
detail_line("new_agent", new_agent),
detail_line("agent", new_agent),
status_line(&status),
];
if let Some(line) = prompt_line(&prompt) {
details.push(line);
}
collab_event("Collab spawn", details)
collab_event("Agent spawned", details)
}
pub(crate) fn interaction_end(ev: CollabAgentInteractionEndEvent) -> PlainHistoryCell {
let CollabAgentInteractionEndEvent {
call_id,
sender_thread_id,
sender_thread_id: _,
receiver_thread_id,
prompt,
status,
} = ev;
let mut details = vec![
detail_line("call", call_id),
detail_line("sender", sender_thread_id),
detail_line("receiver", receiver_thread_id),
detail_line("receiver", receiver_thread_id.to_string()),
status_line(&status),
];
if let Some(line) = prompt_line(&prompt) {
details.push(line);
}
collab_event("Collab send input", details)
collab_event("Input sent", details)
}
pub(crate) fn waiting_begin(ev: CollabWaitingBeginEvent) -> PlainHistoryCell {
let CollabWaitingBeginEvent {
call_id,
sender_thread_id,
sender_thread_id: _,
receiver_thread_ids,
} = ev;
let details = vec![
detail_line("call", call_id),
detail_line("sender", sender_thread_id),
detail_line("receiver", format!("{receiver_thread_ids:?}")),
detail_line("receivers", format_thread_ids(&receiver_thread_ids)),
];
collab_event("Collab wait begin", details)
collab_event("Waiting for agents", details)
}
pub(crate) fn waiting_end(ev: CollabWaitingEndEvent) -> PlainHistoryCell {
let CollabWaitingEndEvent {
call_id,
sender_thread_id,
sender_thread_id: _,
statuses,
} = ev;
let details = vec![
detail_line("call", call_id),
detail_line("sender", sender_thread_id),
detail_line("statuses", format!("{statuses:#?}")),
];
collab_event("Collab wait end", details)
let mut details = vec![detail_line("call", call_id)];
details.extend(wait_complete_lines(&statuses));
collab_event("Wait complete", details)
}
pub(crate) fn close_end(ev: CollabCloseEndEvent) -> PlainHistoryCell {
let CollabCloseEndEvent {
call_id,
sender_thread_id,
sender_thread_id: _,
receiver_thread_id,
status,
} = ev;
let details = vec![
detail_line("call", call_id),
detail_line("sender", sender_thread_id),
detail_line("receiver", receiver_thread_id),
detail_line("receiver", receiver_thread_id.to_string()),
status_line(&status),
];
collab_event("Collab close", details)
collab_event("Agent closed", details)
}
fn collab_event(title: impl Into<String>, details: Vec<Line<'static>>) -> PlainHistoryCell {
let title = title.into();
let mut lines: Vec<Line<'static>> = vec![vec!["".dim(), title.bold()].into()];
let mut lines: Vec<Line<'static>> =
vec![vec![Span::from("").dim(), Span::from(title).bold()].into()];
if !details.is_empty() {
lines.extend(prefix_lines(details, "".dim(), " ".into()));
}
PlainHistoryCell::new(lines)
}
fn detail_line(label: &str, value: impl std::fmt::Display) -> Line<'static> {
Line::from(format!("{label}: {value}").dim())
fn detail_line(label: &str, value: impl Into<Span<'static>>) -> Line<'static> {
vec![Span::from(format!("{label}: ")).dim(), value.into()].into()
}
fn status_line(status: &AgentStatus) -> Line<'static> {
Line::from(format!("status: {}", status_text(status)).dim())
detail_line("status", status_span(status))
}
fn status_text(status: &AgentStatus) -> &'static str {
fn status_span(status: &AgentStatus) -> Span<'static> {
match status {
AgentStatus::PendingInit => "pending_init",
AgentStatus::Running => "running",
AgentStatus::Completed(_) => "completed",
AgentStatus::Errored(_) => "errored",
AgentStatus::Shutdown => "shutdown",
AgentStatus::NotFound => "not_found",
AgentStatus::PendingInit => Span::from("pending init").dim(),
AgentStatus::Running => Span::from("running").cyan().bold(),
AgentStatus::Completed(_) => Span::from("completed").green(),
AgentStatus::Errored(_) => Span::from("errored").red(),
AgentStatus::Shutdown => Span::from("shutdown").dim(),
AgentStatus::NotFound => Span::from("not found").red(),
}
}
@@ -134,7 +133,133 @@ fn prompt_line(prompt: &str) -> Option<Line<'static>> {
} else {
Some(detail_line(
"prompt",
truncate_text(trimmed, COLLAB_PROMPT_PREVIEW_GRAPHEMES),
Span::from(truncate_text(trimmed, COLLAB_PROMPT_PREVIEW_GRAPHEMES)).dim(),
))
}
}
fn format_thread_ids(ids: &[ThreadId]) -> Span<'static> {
if ids.is_empty() {
return Span::from("none").dim();
}
let joined = ids
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
Span::from(joined)
}
fn wait_complete_lines(statuses: &HashMap<ThreadId, AgentStatus>) -> Vec<Line<'static>> {
if statuses.is_empty() {
return vec![detail_line("agents", Span::from("none").dim())];
}
let mut pending_init = 0usize;
let mut running = 0usize;
let mut completed = 0usize;
let mut errored = 0usize;
let mut shutdown = 0usize;
let mut not_found = 0usize;
for status in statuses.values() {
match status {
AgentStatus::PendingInit => pending_init += 1,
AgentStatus::Running => running += 1,
AgentStatus::Completed(_) => completed += 1,
AgentStatus::Errored(_) => errored += 1,
AgentStatus::Shutdown => shutdown += 1,
AgentStatus::NotFound => not_found += 1,
}
}
let mut summary = vec![Span::from(format!("{} total", statuses.len())).dim()];
push_status_count(
&mut summary,
pending_init,
"pending init",
ratatui::prelude::Stylize::dim,
);
push_status_count(&mut summary, running, "running", |span| span.cyan().bold());
push_status_count(
&mut summary,
completed,
"completed",
ratatui::prelude::Stylize::green,
);
push_status_count(
&mut summary,
errored,
"errored",
ratatui::prelude::Stylize::red,
);
push_status_count(
&mut summary,
shutdown,
"shutdown",
ratatui::prelude::Stylize::dim,
);
push_status_count(
&mut summary,
not_found,
"not found",
ratatui::prelude::Stylize::red,
);
let mut entries: Vec<(String, &AgentStatus)> = statuses
.iter()
.map(|(thread_id, status)| (thread_id.to_string(), status))
.collect();
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
let mut lines = Vec::with_capacity(entries.len() + 1);
lines.push(detail_line_spans("agents", summary));
lines.extend(entries.into_iter().map(|(thread_id, status)| {
let mut spans = vec![
Span::from(thread_id).dim(),
Span::from(" ").dim(),
status_span(status),
];
match status {
AgentStatus::Completed(Some(message)) => {
let message_preview = truncate_text(
&message.split_whitespace().collect::<Vec<_>>().join(" "),
COLLAB_AGENT_RESPONSE_PREVIEW_GRAPHEMES,
);
spans.push(Span::from(": ").dim());
spans.push(Span::from(message_preview));
}
AgentStatus::Errored(error) => {
let error_preview = truncate_text(
&error.split_whitespace().collect::<Vec<_>>().join(" "),
COLLAB_AGENT_ERROR_PREVIEW_GRAPHEMES,
);
spans.push(Span::from(": ").dim());
spans.push(Span::from(error_preview).dim());
}
_ => {}
}
spans.into()
}));
lines
}
fn push_status_count(
spans: &mut Vec<Span<'static>>,
count: usize,
label: &'static str,
style: impl FnOnce(Span<'static>) -> Span<'static>,
) {
if count == 0 {
return;
}
spans.push(Span::from(" · ").dim());
spans.push(style(Span::from(format!("{count} {label}"))));
}
fn detail_line_spans(label: &str, mut value: Vec<Span<'static>>) -> Line<'static> {
let mut spans = Vec::with_capacity(value.len() + 1);
spans.push(Span::from(format!("{label}: ")).dim());
spans.append(&mut value);
spans.into()
}