mirror of
https://github.com/openai/codex.git
synced 2026-11-09 00:39:32 +00:00
Preserve originating context for yielded code-mode tool calls (#44866)
## Why A code-mode cell can outlive its turn. Nested tool calls dispatched during a later turn need the original step's tools and context, while approval handling must still work across that turn boundary. ## What changed - Retain each cell's originating `StepContext` for nested tool dispatch and cancel queued calls whose context has been released. - Keep MCP approval metadata available for the lifetime of the running invocation, keyed by server and call ID. - Apply Guardian denial accounting and interruption to the active turn, including reviews from an earlier cell. - Carry `turn_id` in legacy MCP begin/end events so history reconstruction and rollout migration assign late completions to their original turn. Preserve the current-turn fallback for older records without it. ## Testing Add regression coverage for originating tool selection across turns, callback and metadata cleanup, delayed MCP elicitation review, Guardian interruption of the active turn, and late MCP completion attribution with legacy compatibility. GitOrigin-RevId: 4533379aae6e4ecb4aabe5a25e4aca5d65385d76
This commit is contained in:
1 parent
3305c4f31d
commit
2fc4bda3ca
25 files changed
+838
-147
No files matched your search
@@ -789,9 +789,8 @@ async fn review_guardian_mcp_elicitation(
|
||||
.meta()
|
||||
.and_then(|meta| meta.get("callId"))
|
||||
.and_then(Value::as_str)
|
||||
&& let Some((Some(invocation), _)) = session
|
||||
.mcp_tool_approval_metadata(&turn_context.sub_id, call_id)
|
||||
.await
|
||||
&& let Some((Some(invocation), _)) =
|
||||
session.mcp_tool_approval_metadata(&request.server_name, call_id)
|
||||
&& invocation.server == request.server_name
|
||||
{
|
||||
Some(call_id)
|
||||
@@ -828,9 +827,8 @@ async fn review_guardian_mcp_elicitation(
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some((Some(invocation), metadata)) = session
|
||||
.mcp_tool_approval_metadata(&turn_context.sub_id, call_id)
|
||||
.await
|
||||
let Some((Some(invocation), metadata)) =
|
||||
session.mcp_tool_approval_metadata(&request.server_name, call_id)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::context::GuardianContextMode;
|
||||
use crate::environment_selection::ThreadEnvironments;
|
||||
use crate::environment_selection::TurnEnvironmentSnapshot;
|
||||
use crate::hook_mcp_executor::CoreHookMcpExecutor;
|
||||
use crate::mcp_tool_call::McpToolApprovalMetadata;
|
||||
use crate::responses_metadata::CodexResponsesMetadata;
|
||||
use crate::responses_metadata::CodexResponsesRequestKind;
|
||||
use crate::shell_snapshot::ShellSnapshot;
|
||||
@@ -30,6 +31,7 @@ use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::protocol::EnvironmentConfig;
|
||||
use codex_protocol::protocol::HookCompletedEvent;
|
||||
use codex_protocol::protocol::McpInvocation;
|
||||
use codex_protocol::protocol::MultiAgentVersion;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_protocol::protocol::ThreadSource;
|
||||
@@ -40,6 +42,9 @@ use codex_utils_path::replace_path_and_deduplicate;
|
||||
use std::sync::OnceLock;
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
type McpToolApprovalMetadataMap =
|
||||
HashMap<(String, String), std::sync::Weak<(Option<McpInvocation>, McpToolApprovalMetadata)>>;
|
||||
|
||||
/// Context for an initialized model agent
|
||||
///
|
||||
/// A session has at most 1 running task at a time, and can be interrupted by user input.
|
||||
@@ -65,6 +70,8 @@ pub(crate) struct Session {
|
||||
pub(super) multi_agent_version: OnceLock<MultiAgentVersion>,
|
||||
/// Owns invalidation and serializes refreshes without blocking captured calls.
|
||||
pub(super) mcp_refresh: McpRefresh,
|
||||
/// Non-owning lookup for approval data retained by running MCP invocations.
|
||||
pub(crate) mcp_tool_approval_metadata: std::sync::Mutex<McpToolApprovalMetadataMap>,
|
||||
pub(super) mcp_elicitation_reviewer_handle: OnceLock<codex_mcp::ElicitationReviewerHandle>,
|
||||
pub(super) mcp_elicitation_lifecycle_handle: OnceLock<codex_mcp::ElicitationLifecycle>,
|
||||
pub(super) mcp_prewarm_tx: async_channel::Sender<()>,
|
||||
@@ -1625,7 +1632,8 @@ impl Session {
|
||||
windows_sandbox_proxy_settings_mode,
|
||||
multi_agent_version,
|
||||
mcp_refresh: McpRefresh::new(),
|
||||
mcp_elicitation_reviewer_handle: OnceLock::new(),
|
||||
mcp_tool_approval_metadata: Default::default(),
|
||||
mcp_elicitation_reviewer_handle: OnceLock::new(),
|
||||
mcp_elicitation_lifecycle_handle: OnceLock::new(),
|
||||
mcp_prewarm_tx,
|
||||
mcp_prewarm_shutdown: CancellationToken::new(),
|
||||
|
||||
@@ -6743,6 +6743,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
|
||||
multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()),
|
||||
mcp_refresh: McpRefresh::new(),
|
||||
mcp_tool_approval_metadata: Default::default(),
|
||||
mcp_elicitation_reviewer_handle: OnceLock::new(),
|
||||
mcp_elicitation_lifecycle_handle: OnceLock::new(),
|
||||
mcp_prewarm_tx: async_channel::bounded(1).0,
|
||||
@@ -8917,6 +8918,7 @@ where
|
||||
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
|
||||
multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()),
|
||||
mcp_refresh: McpRefresh::new(),
|
||||
mcp_tool_approval_metadata: Default::default(),
|
||||
mcp_elicitation_reviewer_handle: OnceLock::new(),
|
||||
mcp_elicitation_lifecycle_handle: OnceLock::new(),
|
||||
mcp_prewarm_tx: async_channel::bounded(1).0,
|
||||
@@ -11245,12 +11247,12 @@ impl SessionTask for GuardianDeniedApprovalTask {
|
||||
async fn run(
|
||||
self: Arc<Self>,
|
||||
session: Arc<Session>,
|
||||
ctx: Arc<TurnContext>,
|
||||
_ctx: Arc<TurnContext>,
|
||||
_input: Vec<TurnInput>,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> SessionTaskResult {
|
||||
for _ in 0..3 {
|
||||
crate::guardian::record_guardian_denial_for_test(&session, &ctx, &ctx.sub_id).await;
|
||||
crate::guardian::record_guardian_denial_for_test(&session).await;
|
||||
}
|
||||
|
||||
cancellation_token.cancelled().await;
|
||||
@@ -11621,8 +11623,6 @@ async fn guardian_helper_review_interrupts_after_three_consecutive_denials() {
|
||||
.await;
|
||||
|
||||
let session_for_review = Arc::clone(&sess);
|
||||
let turn_for_review = Arc::clone(&tc);
|
||||
let turn_id = tc.sub_id.clone();
|
||||
let review_thread = std::thread::spawn(move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
@@ -11630,12 +11630,7 @@ async fn guardian_helper_review_interrupts_after_three_consecutive_denials() {
|
||||
.expect("helper review runtime");
|
||||
runtime.block_on(async move {
|
||||
for _ in 0..3 {
|
||||
crate::guardian::record_guardian_denial_for_test(
|
||||
&session_for_review,
|
||||
&turn_for_review,
|
||||
&turn_id,
|
||||
)
|
||||
.await;
|
||||
crate::guardian::record_guardian_denial_for_test(&session_for_review).await;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user