mirror of
https://github.com/openai/codex.git
synced 2026-11-14 14:49:31 +00:00
Move Guardian reviewer lifecycle into the extension (#45537)
## Why Guardian reviews must stop when their parent shuts down or their history is reset. Shutdown must also finish reviewer cleanup before closing the parent's persistent history, including when a review is waiting to retry after a rate limit. ## What changed - Let the Guardian extension own reviewer startup, prewarming, and shutdown through `ThreadManager`, tracking and joining outstanding work during teardown. - Cancel reviews on history reset or parent shutdown and reject decisions returned after cancellation, including cached extension decisions. - Keep background prewarm previews from overwriting the active turn's model metadata. ## Testing Extend regression coverage to verify reviewer cleanup after parent shutdown, prompt shutdown during a 60-second Guardian retry with the network request denied, and preservation of active model review requirements during prewarming. GitOrigin-RevId: 05efc369b9a1642a10365eac8a09da77f3f6e28c
This commit is contained in:
1 parent
12b0164a48
commit
18d7ace221
31 files changed
+495
-308
No files matched your search
@@ -308,12 +308,12 @@ pub(super) async fn shutdown_session_runtime(sess: &Arc<Session>) {
|
||||
sess.mcp_refresh.close();
|
||||
sess.services.mcp_runtime.shutdown().await;
|
||||
}
|
||||
sess.guardian_review_session().shutdown().await;
|
||||
|
||||
crate::hook_runtime::run_session_end_hooks(sess).await;
|
||||
emit_thread_stop_lifecycle(sess).await;
|
||||
}
|
||||
|
||||
pub(super) async fn emit_thread_stop_lifecycle(sess: &Session) {
|
||||
async fn emit_thread_stop_lifecycle(sess: &Session) {
|
||||
for contributor in sess.services.extensions.thread_lifecycle_contributors() {
|
||||
contributor
|
||||
.on_thread_stop(codex_extension_api::ThreadStopInput {
|
||||
@@ -338,8 +338,6 @@ pub async fn shutdown(sess: &Arc<Session>, sub_id: String) -> bool {
|
||||
&[],
|
||||
);
|
||||
|
||||
emit_thread_stop_lifecycle(sess.as_ref()).await;
|
||||
|
||||
// Gracefully flush and shutdown thread persistence on session end so tests
|
||||
// that inspect durable state do not race with the background writer.
|
||||
if let Some(live_thread) = sess.live_thread()
|
||||
@@ -607,7 +605,6 @@ pub(super) async fn submission_loop(
|
||||
// explicit shutdown op, still run session teardown.
|
||||
if !shutdown_received {
|
||||
shutdown_session_runtime(&sess).await;
|
||||
emit_thread_stop_lifecycle(sess.as_ref()).await;
|
||||
if let Some(live_thread) = sess.live_thread()
|
||||
&& let Err(err) = live_thread.shutdown().await
|
||||
{
|
||||
|
||||
@@ -2245,13 +2245,11 @@ impl Session {
|
||||
));
|
||||
}
|
||||
|
||||
/// Uses the extension-owned reviewer, or the same manager for standalone hosts.
|
||||
pub(crate) fn guardian_review_session(&self) -> Arc<GuardianReviewSessionManager> {
|
||||
self.services.thread_extension_data.get_or_init(|| {
|
||||
GuardianReviewSessionManager::new(|context, key, kind, snapshot, cancel| {
|
||||
Box::pin(async move { context.spawn(key, kind, snapshot, cancel).await })
|
||||
})
|
||||
})
|
||||
/// Returns the reviewer pool installed by the Guardian extension.
|
||||
pub(crate) fn guardian_review_session(&self) -> Option<Arc<GuardianReviewSessionManager>> {
|
||||
self.services
|
||||
.thread_extension_data
|
||||
.get::<GuardianReviewSessionManager>()
|
||||
}
|
||||
|
||||
pub(crate) async fn emit_turn_started(&self, turn_context: &TurnContext) {
|
||||
@@ -4393,6 +4391,15 @@ impl Session {
|
||||
state.clone_history()
|
||||
}
|
||||
|
||||
/// Captures the history lifetime without exposing mutable session state.
|
||||
pub(crate) async fn history_reset(&self) -> (u64, CancellationToken) {
|
||||
let state = self.state.lock().await;
|
||||
(
|
||||
state.history.reset_version,
|
||||
state.history_reset.child_token(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) async fn conversation_history_snapshot(
|
||||
&self,
|
||||
) -> Arc<dyn ConversationHistorySnapshot> {
|
||||
|
||||
@@ -1021,12 +1021,12 @@ impl Session {
|
||||
self.features.enabled(Feature::Personality),
|
||||
)
|
||||
.await;
|
||||
self.services
|
||||
.thread_extension_data
|
||||
.insert(model_info.clone());
|
||||
|
||||
let multi_agent_version = match multi_agent_runtime {
|
||||
TurnMultiAgentRuntime::ResolveAndStore => {
|
||||
// A background preview must not overwrite a newer turn's model metadata.
|
||||
self.services
|
||||
.thread_extension_data
|
||||
.insert(model_info.clone());
|
||||
self.resolve_multi_agent_version_for_model(&model_info, &per_turn_config)
|
||||
}
|
||||
TurnMultiAgentRuntime::Preview => per_turn_config.multi_agent_version_for_model(
|
||||
|
||||
@@ -107,9 +107,8 @@ pub(super) async fn suspend_turn_and_shutdown(
|
||||
live_thread.shutdown().await.map_err(|error| {
|
||||
CodexErr::Fatal(format!("close suspended root turn writer failed: {error}"))
|
||||
})?;
|
||||
// Announce thread shutdown only after its writer closes so a replacement worker
|
||||
// cannot write the same thread concurrently.
|
||||
handlers::emit_thread_stop_lifecycle(session.as_ref()).await;
|
||||
// Announce completion only after extension cleanup and writer closure so a
|
||||
// replacement worker cannot write the same thread concurrently.
|
||||
session
|
||||
.deliver_event_raw(Event {
|
||||
id: submission_id,
|
||||
|
||||
Reference in new issue
Block a user