mirror of
https://github.com/openai/codex.git
synced 2026-11-09 08:40:33 +00:00
Run executor hooks for interrupted turns (#41432)
## Why Executor-scoped cleanup hooks need to observe an interrupted turn as well as a normally completed one. ## What changed - Allow bundled executor plugins to register the allowlisted `Interrupt` hook alongside `Stop`, while preserving multiple allowed hook entries from the selected executor environment. - Run interrupt hooks with the interrupted turn's last step discovery and turn metadata. Turns without a step context do not reuse discovery from an earlier turn. - Keep executor-scoped interrupt hooks asynchronous and omit public hook lifecycle events for them, matching executor `Stop` hook behavior. ## Testing Added coverage for hook filtering and registration, interrupt execution and metadata propagation, environment selection, and turns without a step context. GitOrigin-RevId: f0c536b3382a37cf3bf693c4573d21f6f66ace4e
This commit is contained in:
1 parent
0d22692962
commit
c2abf869d5
9 files changed
+473
-165
No files matched your search
@@ -12,6 +12,7 @@ use codex_diagnostics::Gauge;
|
||||
use codex_extension_api::ThreadIdleCause;
|
||||
use futures::future::BoxFuture;
|
||||
use tokio::select;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::Notify;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::AbortOnDropHandle;
|
||||
@@ -35,6 +36,7 @@ use crate::session::turn_context::TurnContext;
|
||||
use crate::state::ActiveTurn;
|
||||
use crate::state::RunningTask;
|
||||
use crate::state::TaskKind;
|
||||
use crate::state::TurnState;
|
||||
use codex_analytics::TurnProfileFact;
|
||||
use codex_analytics::TurnTokenUsageFact;
|
||||
use codex_context_fragments::RenderedFragment;
|
||||
@@ -518,7 +520,8 @@ impl Session {
|
||||
aborted_turn = task.is_some();
|
||||
turn_context = task.as_ref().map(|task| Arc::clone(&task.turn_context));
|
||||
if let Some(task) = task {
|
||||
self.handle_task_abort(task, reason.clone()).await;
|
||||
self.handle_task_abort(task, reason.clone(), &active_turn.turn_state)
|
||||
.await;
|
||||
}
|
||||
if aborted_turn {
|
||||
active_turn_to_clear = Some(active_turn);
|
||||
@@ -569,7 +572,8 @@ impl Session {
|
||||
let task = active_turn.task.take();
|
||||
let turn_context = task.as_ref().map(|task| Arc::clone(&task.turn_context));
|
||||
if let Some(task) = task {
|
||||
self.handle_task_abort(task, reason.clone()).await;
|
||||
self.handle_task_abort(task, reason.clone(), &active_turn.turn_state)
|
||||
.await;
|
||||
}
|
||||
if let Some(turn_context) = turn_context.as_deref() {
|
||||
self.emit_turn_abort_lifecycle(reason.clone(), turn_context.extension_data.as_ref())
|
||||
@@ -805,7 +809,7 @@ impl Session {
|
||||
};
|
||||
let event = if let Some(reason) = abort_reason {
|
||||
if reason == TurnAbortReason::Interrupted {
|
||||
run_turn_interrupt_hooks(self, &turn_context).await;
|
||||
run_turn_interrupt_hooks(self, &turn_context, &turn_state).await;
|
||||
}
|
||||
self.emit_turn_abort_lifecycle(reason.clone(), turn_context.extension_data.as_ref())
|
||||
.await;
|
||||
@@ -898,7 +902,12 @@ impl Session {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn handle_task_abort(self: &Arc<Self>, task: RunningTask, reason: TurnAbortReason) {
|
||||
async fn handle_task_abort(
|
||||
self: &Arc<Self>,
|
||||
task: RunningTask,
|
||||
reason: TurnAbortReason,
|
||||
turn_state: &Mutex<TurnState>,
|
||||
) {
|
||||
let sub_id = task.turn_context.sub_id.clone();
|
||||
if task.cancellation_token.is_cancelled() {
|
||||
return;
|
||||
@@ -958,7 +967,7 @@ impl Session {
|
||||
}
|
||||
|
||||
if reason == TurnAbortReason::Interrupted {
|
||||
run_turn_interrupt_hooks(self, &task.turn_context).await;
|
||||
run_turn_interrupt_hooks(self, &task.turn_context, turn_state).await;
|
||||
}
|
||||
|
||||
let started_at = task
|
||||
|
||||
Reference in new issue
Block a user