diff --git a/Cargo.lock b/Cargo.lock index 7b3db1e49e..24952a4988 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1332,7 +1332,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chat-cli-ui" -version = "1.19.2" +version = "1.19.3" dependencies = [ "chrono", "crossterm", @@ -1359,7 +1359,7 @@ dependencies = [ [[package]] name = "chat_cli" -version = "1.19.2" +version = "1.19.3" dependencies = [ "amzn-codewhisperer-client", "amzn-codewhisperer-streaming-client", @@ -6366,7 +6366,7 @@ dependencies = [ [[package]] name = "semantic_search_client" -version = "1.19.2" +version = "1.19.3" dependencies = [ "anyhow", "bm25", diff --git a/Cargo.toml b/Cargo.toml index 21303957a8..69a554c5f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ authors = ["Amazon Q CLI Team (q-cli@amazon.com)", "Chay Nabors (nabochay@amazon edition = "2024" homepage = "https://aws.amazon.com/q/" publish = false -version = "1.19.2" +version = "1.19.3" license = "MIT OR Apache-2.0" [workspace.dependencies] diff --git a/crates/chat-cli-ui/src/conduit.rs b/crates/chat-cli-ui/src/conduit.rs index e347b9da30..37706e56f2 100644 --- a/crates/chat-cli-ui/src/conduit.rs +++ b/crates/chat-cli-ui/src/conduit.rs @@ -15,6 +15,7 @@ use crate::legacy_ui_util::ThemeSource; use crate::protocol::{ Event, LegacyPassThroughOutput, + MetaEvent, ToolCallRejection, ToolCallStart, }; @@ -53,6 +54,7 @@ impl ViewEnd { pub fn into_legacy_mode( self, theme_source: impl ThemeSource, + prompt_ack: Option>, mut stderr: std::io::Stderr, mut stdout: std::io::Stdout, ) -> Result<(), ConduitError> { @@ -153,7 +155,17 @@ impl ViewEnd { Event::ReasoningMessageEnd(_reasoning_message_end) => {}, Event::ReasoningMessageChunk(_reasoning_message_chunk) => {}, Event::ReasoningEnd(_reasoning_end) => {}, - Event::MetaEvent(_meta_event) => {}, + Event::MetaEvent(MetaEvent { meta_type, payload }) => { + if meta_type.as_str() == "timing" { + if let serde_json::Value::String(s) = payload { + if s.as_str() == "prompt_user" { + if let Some(prompt_ack) = prompt_ack.as_ref() { + _ = prompt_ack.send(()); + } + } + } + } + }, Event::ToolCallRejection(tool_call_rejection) => { let ToolCallRejection { reason, name, .. } = tool_call_rejection; diff --git a/crates/chat-cli/src/cli/chat/mod.rs b/crates/chat-cli/src/cli/chat/mod.rs index e1eab6ddd6..5ba5b36317 100644 --- a/crates/chat-cli/src/cli/chat/mod.rs +++ b/crates/chat-cli/src/cli/chat/mod.rs @@ -605,6 +605,7 @@ pub struct ChatSession { inner: Option, ctrlc_rx: broadcast::Receiver<()>, wrap: Option, + prompt_ack_rx: std::sync::mpsc::Receiver<()>, } impl ChatSession { @@ -630,11 +631,12 @@ impl ChatSession { let should_send_structured_msg = should_send_structured_message(os); let (view_end, _byte_receiver, mut control_end_stderr, control_end_stdout) = get_legacy_conduits(should_send_structured_msg); + let (prompt_ack_tx, prompt_ack_rx) = std::sync::mpsc::channel::<()>(); tokio::task::spawn_blocking(move || { let stderr = std::io::stderr(); let stdout = std::io::stdout(); - if let Err(e) = view_end.into_legacy_mode(StyledText, stderr, stdout) { + if let Err(e) = view_end.into_legacy_mode(StyledText, Some(prompt_ack_tx), stderr, stdout) { error!("Conduit view end legacy mode exited: {:?}", e); } }); @@ -739,6 +741,7 @@ impl ChatSession { inner: Some(ChatState::default()), ctrlc_rx, wrap, + prompt_ack_rx, }) } @@ -1942,6 +1945,25 @@ impl ChatSession { execute!(self.stderr, StyledText::reset(), StyledText::reset_attributes())?; let prompt = self.generate_tool_trust_prompt(os).await; + + // Here we are signaling to the ui layer that the event loop wants to prompt user + // This is necessitated by the fact that what is actually writing to stderr or stdout is + // not on the same thread as what is prompting the user (in an ideal world they would be). + // As a bandaid fix (to hold us until we move to the new event loop where everything is in + // their rightful place), we are first signaling to the ui layer we are about to prompt + // users, and we are going to wait until the ui layer acknowledges. + // Note that this works because [std::sync::mpsc] preserves order between sending and + // receiving + self.stderr + .send(Event::MetaEvent(chat_cli_ui::protocol::MetaEvent { + meta_type: "timing".to_string(), + payload: serde_json::Value::String("prompt_user".to_string()), + })) + .map_err(|_e| ChatError::Custom("Error sending timing event for prompting user".into()))?; + if let Err(e) = self.prompt_ack_rx.recv_timeout(std::time::Duration::from_secs(10)) { + error!("Failed to receive user prompting acknowledgement from UI: {:?}", e); + } + let user_input = match self.read_user_input(&prompt, false) { Some(input) => input, None => return Ok(ChatState::Exit), diff --git a/crates/chat-cli/src/cli/feed.json b/crates/chat-cli/src/cli/feed.json index 938d58abac..52ae369009 100644 --- a/crates/chat-cli/src/cli/feed.json +++ b/crates/chat-cli/src/cli/feed.json @@ -10,6 +10,18 @@ "hidden": true, "changes": [] }, + { + "type": "release", + "date": "2025-10-29", + "version": "1.19.3", + "title": "Version 1.19.3", + "changes": [ + { + "type": "fixed", + "description": "Racing condition between prompting and printing - [#3308](https://github.com/aws/amazon-q-developer-cli/pull/3308)" + } + ] + }, { "type": "release", "date": "2025-10-28",