From a5aa9d25928e2f338744fa8296e3ab397cd159b0 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Mon, 2 Nov 2026 14:43:21 +0700 Subject: [PATCH] An ACP chat starts the agent's session again after a failed start, and Stop ends a waiting message Fixes #271 --- crates/ai/src/cli_agents/acp.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/ai/src/cli_agents/acp.rs b/crates/ai/src/cli_agents/acp.rs index 27e0461e..d3792d08 100644 --- a/crates/ai/src/cli_agents/acp.rs +++ b/crates/ai/src/cli_agents/acp.rs @@ -152,6 +152,11 @@ impl AcpCore { /// A message from the person: sent now, or once the session is ready. pub fn prompt(&mut self, text: &str) -> Vec { self.queued.push_back(text.to_string()); + // A start that failed earlier (an `initialize` error) left nothing under way: start again, + // or the message would wait for a session that never comes (#271). + if !self.ready && self.pending.is_empty() { + return self.start(); + } self.flush() } @@ -288,6 +293,8 @@ impl AcpCore { /// Stop the current message: cancel the prompt and any open permission requests. pub fn cancel(&mut self) -> Vec { + // A message still waiting for the session has no prompt to cancel: its turn ends here. + let waiting = !self.queued.is_empty() && !self.pending.values().any(|p| *p == Pending::Prompt); self.queued.clear(); let mut out: Vec = self .asks @@ -297,6 +304,9 @@ impl AcpCore { if let Some(sid) = self.session.clone().filter(|_| self.pending.values().any(|p| *p == Pending::Prompt)) { out.push(Out::Send(json!({"jsonrpc": "2.0", "method": "session/cancel", "params": {"sessionId": sid}}).to_string())); } + if waiting { + out.extend([self.turn_done(), Out::TurnEnded]); + } out } @@ -694,6 +704,24 @@ mod tests { assert!(c.answer(call_id, true).is_empty(), "answered once"); } + /// #271: after `initialize` failed, the next message waited for a session forever, and Stop + /// didn't end it. + #[test] + fn a_failed_start_is_tried_again_and_stop_ends_a_waiting_message() { + let mut c = core(None); + c.start(); + let failed = c.line(r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"not signed in"}}"#); + assert!(failed.contains(&Out::TurnEnded)); + let again = sent(&c.prompt("Hello")); + assert_eq!(again[0]["method"], "initialize", "the next message starts the agent's session again"); + let failed = c.line(r#"{"jsonrpc":"2.0","id":2,"error":{"code":-32603,"message":"not signed in"}}"#); + assert!(failed.contains(&Out::TurnEnded), "a second failure ends that message too"); + c.prompt("Hello"); + let stopped = c.cancel(); + assert!(stopped.contains(&Out::TurnEnded), "Stop ends a message still waiting for the session"); + assert!(sent(&c.line(r#"{"jsonrpc":"2.0","id":3,"result":{}}"#)).iter().all(|m| m["method"] != "session/prompt")); + } + #[test] fn errors_end_the_turn_and_unoffered_requests_are_refused() { let mut c = core(None);