mirror of
https://github.com/openai/codex.git
synced 2026-05-02 10:26:45 +00:00
Render delegated patch approval details (#19709)
## Why Fixes #19632. When a delegated agent requests approval for an in-progress file change, the parent TUI handles that request from an inactive thread. The app server already sent the `FileChange` item with the proposed diff, but the inactive-thread approval path was not recovering and rendering it the same way as the active-thread path. The result was an inconsistent approval prompt: main-thread edits show a normal patch preview history item before the approval modal, while delegated edits did not show that preview in the transcript flow. ## What Changed - Recover buffered or historical `FileChange` item changes when building inactive-thread file-change approval requests. - Reuse the app-server file-change conversion helper for both live transcript rendering and inactive-thread approvals. - Render recovered delegated patches as a normal patch preview history cell before the approval modal. - Keep apply-patch approval modals focused on the decision prompt and optional metadata; they do not render a synthetic command line or embed the diff body. ## Manual Repro And Verification I manually reproduced the issue using a file under `~/Desktop` so the write would require approval. Before the fix: 1. Ask the main thread: `Use apply_patch, not shell redirection or Python, to create ~/Desktop/bug1.txt with three short lines.` 2. Observe the expected TUI shape: the transcript shows a normal patch preview such as `• Added ~/Desktop/bug1.txt (+N -0)` above the approval modal, and the modal contains only the approval prompt/options without a synthetic command line. 3. Ask for the delegated path: `Spawn a worker. Have it use apply_patch, not shell redirection or Python, to create ~/Desktop/bug1.txt with four short lines.` 4. Observe the delegated approval is inconsistent: the parent view does not render the proposed patch as the normal transcript preview before the modal, so the diff context is missing from the stream or appears inside the modal instead of in the history flow. After the fix: 1. Repeat the delegated worker prompt with `apply_patch`. 2. Confirm the parent view renders the same normal patch preview history cell (`• Added ~/Desktop/bug1.txt (+N -0)` plus the diff) immediately before the approval modal. 3. Confirm the approval modal remains focused on the decision prompt. For delegated approvals it may show the worker thread label, but it should not show a `$ apply_patch` command line or embed the diff body in the modal.
This commit is contained in:
@@ -194,6 +194,17 @@ impl App {
|
||||
store.session.as_ref().map(|session| session.cwd.clone())
|
||||
}
|
||||
|
||||
async fn thread_file_change_changes(
|
||||
&self,
|
||||
thread_id: ThreadId,
|
||||
turn_id: &str,
|
||||
item_id: &str,
|
||||
) -> Option<Vec<codex_app_server_protocol::FileUpdateChange>> {
|
||||
let channel = self.thread_event_channels.get(&thread_id)?;
|
||||
let store = channel.store.lock().await;
|
||||
store.file_change_changes(turn_id, item_id)
|
||||
}
|
||||
|
||||
pub(super) async fn interactive_request_for_thread_request(
|
||||
&self,
|
||||
thread_id: ThreadId,
|
||||
@@ -264,7 +275,11 @@ impl App {
|
||||
.thread_cwd(thread_id)
|
||||
.await
|
||||
.unwrap_or_else(|| self.config.cwd.clone()),
|
||||
changes: HashMap::new(),
|
||||
changes: self
|
||||
.thread_file_change_changes(thread_id, ¶ms.turn_id, ¶ms.item_id)
|
||||
.await
|
||||
.map(crate::app_server_approval_conversions::file_update_changes_to_core)
|
||||
.unwrap_or_default(),
|
||||
}),
|
||||
),
|
||||
ServerRequest::McpServerElicitationRequest { request_id, params } => {
|
||||
@@ -311,6 +326,7 @@ impl App {
|
||||
pub(super) fn push_thread_interactive_request(&mut self, request: ThreadInteractiveRequest) {
|
||||
match request {
|
||||
ThreadInteractiveRequest::Approval(request) => {
|
||||
self.render_inactive_patch_preview(&request);
|
||||
self.chat_widget.push_approval_request(request);
|
||||
}
|
||||
ThreadInteractiveRequest::McpServerElicitation(request) => {
|
||||
@@ -320,6 +336,23 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_inactive_patch_preview(&mut self, request: &ApprovalRequest) {
|
||||
let ApprovalRequest::ApplyPatch {
|
||||
thread_label,
|
||||
cwd,
|
||||
changes,
|
||||
..
|
||||
} = request
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if thread_label.is_none() || changes.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.chat_widget
|
||||
.add_to_history(history_cell::new_patch_event(changes.clone(), cwd));
|
||||
}
|
||||
|
||||
pub(super) async fn pending_inactive_thread_requests(&self) -> Vec<(ThreadId, ServerRequest)> {
|
||||
let channels: Vec<(ThreadId, Arc<Mutex<ThreadEventStore>>)> = self
|
||||
.thread_event_channels
|
||||
|
||||
Reference in New Issue
Block a user