feat: basic collab rendering

This commit is contained in:
jif-oai
2026-01-12 15:27:59 +00:00
parent e3cf74885a
commit 651d6852a2
3 changed files with 149 additions and 3 deletions

View File

@@ -24,6 +24,7 @@ use codex_core::protocol::AgentReasoningRawContentDeltaEvent;
use codex_core::protocol::AgentReasoningRawContentEvent;
use codex_core::protocol::ApplyPatchApprovalRequestEvent;
use codex_core::protocol::BackgroundEventEvent;
use codex_core::protocol::CollabInteractionEvent;
use codex_core::protocol::CreditsSnapshot;
use codex_core::protocol::DeprecationNoticeEvent;
use codex_core::protocol::ErrorEvent;
@@ -102,6 +103,7 @@ use crate::bottom_pane::SelectionViewParams;
use crate::bottom_pane::custom_prompt_view::CustomPromptView;
use crate::bottom_pane::popup_consts::standard_popup_hint_line;
use crate::clipboard_paste::paste_image_to_temp_png;
use crate::collab_event_cell;
use crate::diff_render::display_path_for;
use crate::exec_cell::CommandOutput;
use crate::exec_cell::ExecCell;
@@ -1073,6 +1075,11 @@ impl ChatWidget {
self.set_status_header(message);
}
fn on_collab_interaction(&mut self, event: CollabInteractionEvent) {
self.add_to_history(collab_event_cell::new_collab_interaction(event));
self.request_redraw();
}
fn on_undo_started(&mut self, event: UndoStartedEvent) {
self.bottom_pane.ensure_status_indicator();
self.bottom_pane.set_interrupt_hint_visible(false);
@@ -2182,9 +2189,7 @@ impl ChatWidget {
}
EventMsg::ExitedReviewMode(review) => self.on_exited_review_mode(review),
EventMsg::ContextCompacted(_) => self.on_agent_message("Context compacted".to_owned()),
EventMsg::CollabInteraction(_) => {
// TODO(jif) handle collab tools.
}
EventMsg::CollabInteraction(event) => self.on_collab_interaction(event),
EventMsg::ThreadRolledBack(_) => {}
EventMsg::RawResponseItem(_)
| EventMsg::ItemStarted(_)

View File

@@ -0,0 +1,140 @@
use codex_core::protocol::AgentStatus;
use codex_core::protocol::CollabInteractionEvent;
use ratatui::style::Stylize;
use ratatui::text::Line;
use crate::history_cell::HistoryCell;
use crate::text_formatting::truncate_text;
use crate::wrapping::RtOptions;
use crate::wrapping::word_wrap_lines;
const COLLAB_PROMPT_MAX_GRAPHEMES: usize = 120;
#[derive(Debug)]
pub(crate) struct CollabInteractionCell {
summary: Line<'static>,
detail: Option<Line<'static>>,
}
impl CollabInteractionCell {
fn new(summary: Line<'static>, detail: Option<Line<'static>>) -> Self {
Self { summary, detail }
}
}
impl HistoryCell for CollabInteractionCell {
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
let wrap_width = width.max(1) as usize;
let mut lines = word_wrap_lines(
std::iter::once(self.summary.clone()),
RtOptions::new(wrap_width)
.initial_indent("".dim().into())
.subsequent_indent(" ".into()),
);
if let Some(detail) = &self.detail {
let detail_lines = word_wrap_lines(
std::iter::once(detail.clone()),
RtOptions::new(wrap_width)
.initial_indent("".dim().into())
.subsequent_indent(" ".into()),
);
lines.extend(detail_lines);
}
lines
}
}
fn collab_status_label(status: &AgentStatus) -> String {
match status {
AgentStatus::PendingInit => "pending init".to_string(),
AgentStatus::Running => "running".to_string(),
AgentStatus::Completed(message) => format!("completed: {message:?}"),
AgentStatus::Errored(_) => "errored".to_string(),
AgentStatus::Shutdown => "shutdown".to_string(),
AgentStatus::NotFound => "not found".to_string(),
}
}
fn collab_detail_line(label: &str, message: &str) -> Option<Line<'static>> {
let trimmed = message.trim();
if trimmed.is_empty() {
return None;
}
let collapsed = trimmed
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.collect::<Vec<_>>()
.join(" ");
let truncated = truncate_text(&collapsed, COLLAB_PROMPT_MAX_GRAPHEMES);
let label = format!("{label}: ");
Some(Line::from(vec![label.dim(), truncated.into()]))
}
pub(crate) fn new_collab_interaction(event: CollabInteractionEvent) -> CollabInteractionCell {
let (summary, detail) = match event {
CollabInteractionEvent::AgentSpawned { new_id, prompt, .. } => {
let summary = Line::from(vec![
"Spawned agent".bold(),
" ".into(),
new_id.to_string().dim(),
]);
let detail = collab_detail_line("Prompt", &prompt);
(summary, detail)
}
CollabInteractionEvent::AgentInteraction {
receiver_id,
prompt,
..
} => {
let summary = Line::from(vec![
"Sent to agent".bold(),
" ".into(),
receiver_id.to_string().dim(),
]);
let detail = collab_detail_line("Message", &prompt);
(summary, detail)
}
CollabInteractionEvent::WaitingBegin { receiver_id, .. } => {
let summary = Line::from(vec![
"Waiting on agent".bold(),
" ".into(),
receiver_id.to_string().dim(),
]);
(summary, None)
}
CollabInteractionEvent::WaitingEnd {
receiver_id,
status,
..
} => {
let summary = Line::from(vec![
"Wait ended for agent".bold(),
" ".into(),
receiver_id.to_string().dim(),
" · ".dim(),
collab_status_label(&status).dim(),
]);
(summary, None)
}
CollabInteractionEvent::Close {
receiver_id,
status,
..
} => {
let summary = Line::from(vec![
"Closed agent".bold(),
" ".into(),
receiver_id.to_string().dim(),
" · ".dim(),
collab_status_label(&status).dim(),
]);
(summary, None)
}
};
CollabInteractionCell::new(summary, detail)
}

View File

@@ -43,6 +43,7 @@ mod bottom_pane;
mod chatwidget;
mod cli;
mod clipboard_paste;
mod collab_event_cell;
mod color;
pub mod custom_terminal;
mod diff_render;