mirror of
https://github.com/openai/codex.git
synced 2026-05-04 21:32:21 +03:00
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:
@@ -44,6 +44,20 @@ pub(super) struct PendingInteractiveReplayState {
|
||||
}
|
||||
|
||||
impl PendingInteractiveReplayState {
|
||||
pub(super) fn event_can_change_pending_thread_approvals(event: &Event) -> bool {
|
||||
matches!(
|
||||
&event.msg,
|
||||
EventMsg::ExecApprovalRequest(_)
|
||||
| EventMsg::ApplyPatchApprovalRequest(_)
|
||||
| EventMsg::ElicitationRequest(_)
|
||||
| EventMsg::ExecCommandBegin(_)
|
||||
| EventMsg::PatchApplyBegin(_)
|
||||
| EventMsg::TurnComplete(_)
|
||||
| EventMsg::TurnAborted(_)
|
||||
| EventMsg::ShutdownComplete
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn op_can_change_state(op: &Op) -> bool {
|
||||
matches!(
|
||||
op,
|
||||
@@ -240,6 +254,12 @@ impl PendingInteractiveReplayState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn has_pending_thread_approvals(&self) -> bool {
|
||||
!self.exec_approval_call_ids.is_empty()
|
||||
|| !self.patch_approval_call_ids.is_empty()
|
||||
|| !self.elicitation_requests.is_empty()
|
||||
}
|
||||
|
||||
fn clear_request_user_input_turn(&mut self, turn_id: &str) {
|
||||
if let Some(call_ids) = self.request_user_input_call_ids_by_turn_id.remove(turn_id) {
|
||||
for call_id in call_ids {
|
||||
@@ -582,4 +602,56 @@ mod tests {
|
||||
"resolved elicitation prompt should not replay on thread switch"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_event_store_reports_pending_thread_approvals() {
|
||||
let mut store = ThreadEventStore::new(8);
|
||||
assert_eq!(store.has_pending_thread_approvals(), false);
|
||||
|
||||
store.push_event(Event {
|
||||
id: "ev-1".to_string(),
|
||||
msg: EventMsg::ExecApprovalRequest(
|
||||
codex_protocol::protocol::ExecApprovalRequestEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
approval_id: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
command: vec!["echo".to_string(), "hi".to_string()],
|
||||
cwd: PathBuf::from("/tmp"),
|
||||
reason: None,
|
||||
network_approval_context: None,
|
||||
proposed_execpolicy_amendment: None,
|
||||
proposed_network_policy_amendments: None,
|
||||
additional_permissions: None,
|
||||
parsed_cmd: Vec::new(),
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
assert_eq!(store.has_pending_thread_approvals(), true);
|
||||
|
||||
store.note_outbound_op(&Op::ExecApproval {
|
||||
id: "call-1".to_string(),
|
||||
turn_id: Some("turn-1".to_string()),
|
||||
decision: codex_protocol::protocol::ReviewDecision::Approved,
|
||||
});
|
||||
|
||||
assert_eq!(store.has_pending_thread_approvals(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_user_input_does_not_count_as_pending_thread_approval() {
|
||||
let mut store = ThreadEventStore::new(8);
|
||||
store.push_event(Event {
|
||||
id: "ev-1".to_string(),
|
||||
msg: EventMsg::RequestUserInput(
|
||||
codex_protocol::request_user_input::RequestUserInputEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
questions: Vec::new(),
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
assert_eq!(store.has_pending_thread_approvals(), false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user