fix: introduce EventMsg::TurnAborted (#2365)

Introduces `EventMsg::TurnAborted` that should be sent in response to
`Op::Interrupt`.

In the MCP server, updates the handling of a
`ClientRequest::InterruptConversation` request such that it sends the
`Op::Interrupt` but does not respond to the request until it sees an
`EventMsg::TurnAborted`.
This commit is contained in:
Michael Bolin
2025-08-17 21:40:31 -07:00
committed by GitHub
parent 71cae06e66
commit b581498882
9 changed files with 80 additions and 50 deletions

View File

@@ -14,6 +14,8 @@ use codex_apply_patch::ApplyPatchAction;
use codex_apply_patch::MaybeApplyPatchVerified;
use codex_apply_patch::maybe_parse_apply_patch_verified;
use codex_login::CodexAuth;
use codex_protocol::protocol::TurnAbortReason;
use codex_protocol::protocol::TurnAbortedEvent;
use futures::prelude::*;
use mcp_types::CallToolResult;
use serde::Serialize;
@@ -535,7 +537,7 @@ impl Session {
pub fn set_task(&self, task: AgentTask) {
let mut state = self.state.lock_unchecked();
if let Some(current_task) = state.current_task.take() {
current_task.abort();
current_task.abort(TurnAbortReason::Replaced);
}
state.current_task = Some(task);
}
@@ -852,13 +854,13 @@ impl Session {
.await
}
fn abort(&self) {
info!("Aborting existing session");
fn interrupt_task(&self) {
info!("interrupt received: abort current task, if any");
let mut state = self.state.lock_unchecked();
state.pending_approvals.clear();
state.pending_input.clear();
if let Some(task) = state.current_task.take() {
task.abort();
task.abort(TurnAbortReason::Interrupted);
}
}
@@ -894,7 +896,7 @@ impl Session {
impl Drop for Session {
fn drop(&mut self) {
self.abort();
self.interrupt_task();
}
}
@@ -964,14 +966,13 @@ impl AgentTask {
}
}
fn abort(self) {
fn abort(self, reason: TurnAbortReason) {
// TOCTOU?
if !self.handle.is_finished() {
self.handle.abort();
let event = Event {
id: self.sub_id,
msg: EventMsg::Error(ErrorEvent {
message: " Turn interrupted".to_string(),
}),
msg: EventMsg::TurnAborted(TurnAbortedEvent { reason }),
};
let tx_event = self.sess.tx_event.clone();
tokio::spawn(async move {
@@ -994,7 +995,7 @@ async fn submission_loop(
debug!(?sub, "Submission");
match sub.op {
Op::Interrupt => {
sess.abort();
sess.interrupt_task();
}
Op::UserInput { items } => {
// attempt to inject input into current task
@@ -1065,13 +1066,13 @@ async fn submission_loop(
}
Op::ExecApproval { id, decision } => match decision {
ReviewDecision::Abort => {
sess.abort();
sess.interrupt_task();
}
other => sess.notify_approval(&id, other),
},
Op::PatchApproval { id, decision } => match decision {
ReviewDecision::Abort => {
sess.abort();
sess.interrupt_task();
}
other => sess.notify_approval(&id, other),
},