Display pending child-thread approvals in TUI (#12767)

Summary
- propagate approval policy from parent to spawned agents and drop the
Never override so sub-agents respect the caller’s request
- refresh the pending-approval list whenever events arrive or the active
thread changes and surface the list above the composer for inactive
threads
- add widgets, helpers, and tests covering the new pending-thread
approval UI state

![Uploading Screenshot 2026-02-25 at 11.02.18.png…]()
This commit is contained in:
jif-oai
2026-02-25 11:40:11 +00:00
committed by GitHub
parent 93efcfd50d
commit bcd6e68054
7 changed files with 419 additions and 12 deletions

View File

@@ -17,6 +17,7 @@ use std::path::PathBuf;
use crate::app_event::ConnectorsSnapshot;
use crate::app_event_sender::AppEventSender;
use crate::bottom_pane::pending_thread_approvals::PendingThreadApprovals;
use crate::bottom_pane::queued_user_messages::QueuedUserMessages;
use crate::bottom_pane::unified_exec_footer::UnifiedExecFooter;
use crate::key_hint;
@@ -92,6 +93,7 @@ pub(crate) use skills_toggle_view::SkillsToggleView;
pub(crate) use status_line_setup::StatusLineItem;
pub(crate) use status_line_setup::StatusLineSetupView;
mod paste_burst;
mod pending_thread_approvals;
pub mod popup_consts;
mod queued_user_messages;
mod scroll_state;
@@ -171,6 +173,8 @@ pub(crate) struct BottomPane {
unified_exec_footer: UnifiedExecFooter,
/// Queued user messages to show above the composer while a turn is running.
queued_user_messages: QueuedUserMessages,
/// Inactive threads with pending approval requests.
pending_thread_approvals: PendingThreadApprovals,
context_window_percent: Option<i64>,
context_window_used_tokens: Option<i64>,
}
@@ -219,6 +223,7 @@ impl BottomPane {
status: None,
unified_exec_footer: UnifiedExecFooter::new(),
queued_user_messages: QueuedUserMessages::new(),
pending_thread_approvals: PendingThreadApprovals::new(),
esc_backtrack_hint: false,
animations_enabled,
context_window_percent: None,
@@ -759,6 +764,18 @@ impl BottomPane {
self.request_redraw();
}
/// Update the inactive-thread approval list shown above the composer.
pub(crate) fn set_pending_thread_approvals(&mut self, threads: Vec<String>) {
if self.pending_thread_approvals.set_threads(threads) {
self.request_redraw();
}
}
#[cfg(test)]
pub(crate) fn pending_thread_approvals(&self) -> &[String] {
self.pending_thread_approvals.threads()
}
/// Update the unified-exec process set and refresh whichever summary surface is active.
///
/// The summary may be displayed inline in the status row or as a dedicated
@@ -980,14 +997,20 @@ impl BottomPane {
if self.status.is_none() && !self.unified_exec_footer.is_empty() {
flex.push(0, RenderableItem::Borrowed(&self.unified_exec_footer));
}
let has_pending_thread_approvals = !self.pending_thread_approvals.is_empty();
let has_queued_messages = !self.queued_user_messages.messages.is_empty();
let has_status_or_footer =
self.status.is_some() || !self.unified_exec_footer.is_empty();
if has_queued_messages && has_status_or_footer {
let has_inline_previews = has_pending_thread_approvals || has_queued_messages;
if has_inline_previews && has_status_or_footer {
flex.push(0, RenderableItem::Owned("".into()));
}
flex.push(1, RenderableItem::Borrowed(&self.pending_thread_approvals));
if has_pending_thread_approvals && has_queued_messages {
flex.push(0, RenderableItem::Owned("".into()));
}
flex.push(1, RenderableItem::Borrowed(&self.queued_user_messages));
if !has_queued_messages && has_status_or_footer {
if !has_inline_previews && has_status_or_footer {
flex.push(0, RenderableItem::Owned("".into()));
}
let mut flex2 = FlexRenderable::new();