mirror of
https://github.com/openai/codex.git
synced 2026-11-06 08:31:30 +00:00
## Why Completion receipts for identical user messages could acknowledge the wrong pending steer. When the receipt arrived while another thread was displayed, the stale pending state could later be restored and resubmitted. ## What changed - Assign each TUI submission a client message ID and pass it through turn start and steer requests. - Preserve pending steer IDs across thread input snapshots and match completion receipts by ID, while retaining content-based matching for older app servers that do not echo IDs. - Acknowledge matching receipts for inactive threads before their notifications can be discarded, including threads stored in the agents overview. ## Testing Added coverage for identical steer receipts and for switching within and between tasks before interrupting an active turn. GitOrigin-RevId: 28bd80a735ff86cd7bf836f24b448e7499f0a902
255 lines
7.7 KiB
Rust
255 lines
7.7 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use codex_app_server_protocol::AskForApproval;
|
|
use codex_app_server_protocol::CommandExecutionApprovalDecision;
|
|
use codex_app_server_protocol::FileChangeApprovalDecision;
|
|
use codex_app_server_protocol::McpServerElicitationAction;
|
|
use codex_app_server_protocol::RequestId as AppServerRequestId;
|
|
use codex_app_server_protocol::ReviewTarget;
|
|
use codex_app_server_protocol::ToolRequestUserInputResponse;
|
|
use codex_app_server_protocol::UserInput;
|
|
use codex_config::types::ApprovalsReviewer;
|
|
use codex_protocol::approvals::GuardianAssessmentEvent;
|
|
use codex_protocol::config_types::CollaborationMode;
|
|
use codex_protocol::config_types::Personality;
|
|
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
|
use codex_protocol::config_types::WindowsSandboxLevel;
|
|
use codex_protocol::models::ActivePermissionProfile;
|
|
use codex_protocol::models::PermissionProfile;
|
|
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
|
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
|
use serde::Serialize;
|
|
use serde_json::Value;
|
|
|
|
#[allow(clippy::large_enum_variant)]
|
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
|
pub(crate) enum AppCommand {
|
|
Interrupt,
|
|
CleanBackgroundTerminals,
|
|
RunUserShellCommand {
|
|
command: String,
|
|
},
|
|
UserTurn {
|
|
client_user_message_id: String,
|
|
items: Vec<UserInput>,
|
|
cwd: PathBuf,
|
|
approval_policy: AskForApproval,
|
|
approvals_reviewer: Option<ApprovalsReviewer>,
|
|
active_permission_profile: Option<ActivePermissionProfile>,
|
|
model: String,
|
|
effort: Option<ReasoningEffortConfig>,
|
|
summary: Option<ReasoningSummaryConfig>,
|
|
service_tier: Option<Option<String>>,
|
|
final_output_json_schema: Option<Value>,
|
|
collaboration_mode: Option<CollaborationMode>,
|
|
personality: Option<Personality>,
|
|
},
|
|
OverrideTurnContext {
|
|
cwd: Option<PathBuf>,
|
|
approval_policy: Option<AskForApproval>,
|
|
approvals_reviewer: Option<ApprovalsReviewer>,
|
|
permission_profile: Option<PermissionProfile>,
|
|
active_permission_profile: Option<ActivePermissionProfile>,
|
|
windows_sandbox_level: Option<WindowsSandboxLevel>,
|
|
model: Option<String>,
|
|
effort: Option<Option<ReasoningEffortConfig>>,
|
|
summary: Option<ReasoningSummaryConfig>,
|
|
service_tier: Option<Option<String>>,
|
|
collaboration_mode: Option<CollaborationMode>,
|
|
personality: Option<Personality>,
|
|
},
|
|
ExecApproval {
|
|
id: String,
|
|
turn_id: Option<String>,
|
|
decision: CommandExecutionApprovalDecision,
|
|
},
|
|
PatchApproval {
|
|
id: String,
|
|
decision: FileChangeApprovalDecision,
|
|
},
|
|
ResolveElicitation {
|
|
server_name: String,
|
|
request_id: AppServerRequestId,
|
|
decision: McpServerElicitationAction,
|
|
content: Option<Value>,
|
|
meta: Option<Value>,
|
|
},
|
|
UserInputAnswer {
|
|
id: String,
|
|
response: ToolRequestUserInputResponse,
|
|
},
|
|
RequestPermissionsResponse {
|
|
id: String,
|
|
response: RequestPermissionsResponse,
|
|
},
|
|
ReloadUserConfig,
|
|
ListSkills {
|
|
cwds: Vec<PathBuf>,
|
|
force_reload: bool,
|
|
},
|
|
Compact,
|
|
SetThreadName {
|
|
name: String,
|
|
},
|
|
Review {
|
|
target: ReviewTarget,
|
|
},
|
|
ApproveGuardianDeniedAction {
|
|
event: GuardianAssessmentEvent,
|
|
},
|
|
}
|
|
|
|
impl AppCommand {
|
|
pub(crate) fn interrupt() -> Self {
|
|
Self::Interrupt
|
|
}
|
|
|
|
pub(crate) fn clean_background_terminals() -> Self {
|
|
Self::CleanBackgroundTerminals
|
|
}
|
|
|
|
pub(crate) fn run_user_shell_command(command: String) -> Self {
|
|
Self::RunUserShellCommand { command }
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub(crate) fn user_turn(
|
|
client_user_message_id: String,
|
|
items: Vec<UserInput>,
|
|
cwd: PathBuf,
|
|
approval_policy: AskForApproval,
|
|
active_permission_profile: Option<ActivePermissionProfile>,
|
|
model: String,
|
|
effort: Option<ReasoningEffortConfig>,
|
|
summary: Option<ReasoningSummaryConfig>,
|
|
service_tier: Option<Option<String>>,
|
|
final_output_json_schema: Option<Value>,
|
|
collaboration_mode: Option<CollaborationMode>,
|
|
personality: Option<Personality>,
|
|
) -> Self {
|
|
Self::UserTurn {
|
|
client_user_message_id,
|
|
items,
|
|
cwd,
|
|
approval_policy,
|
|
approvals_reviewer: None,
|
|
active_permission_profile,
|
|
model,
|
|
effort,
|
|
summary,
|
|
service_tier,
|
|
final_output_json_schema,
|
|
collaboration_mode,
|
|
personality,
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub(crate) fn override_turn_context(
|
|
cwd: Option<PathBuf>,
|
|
approval_policy: Option<AskForApproval>,
|
|
approvals_reviewer: Option<ApprovalsReviewer>,
|
|
permission_profile: Option<PermissionProfile>,
|
|
active_permission_profile: Option<ActivePermissionProfile>,
|
|
windows_sandbox_level: Option<WindowsSandboxLevel>,
|
|
model: Option<String>,
|
|
effort: Option<Option<ReasoningEffortConfig>>,
|
|
summary: Option<ReasoningSummaryConfig>,
|
|
service_tier: Option<Option<String>>,
|
|
collaboration_mode: Option<CollaborationMode>,
|
|
personality: Option<Personality>,
|
|
) -> Self {
|
|
Self::OverrideTurnContext {
|
|
cwd,
|
|
approval_policy,
|
|
approvals_reviewer,
|
|
permission_profile,
|
|
active_permission_profile,
|
|
windows_sandbox_level,
|
|
model,
|
|
effort,
|
|
summary,
|
|
service_tier,
|
|
collaboration_mode,
|
|
personality,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn exec_approval(
|
|
id: String,
|
|
turn_id: Option<String>,
|
|
decision: CommandExecutionApprovalDecision,
|
|
) -> Self {
|
|
Self::ExecApproval {
|
|
id,
|
|
turn_id,
|
|
decision,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn patch_approval(id: String, decision: FileChangeApprovalDecision) -> Self {
|
|
Self::PatchApproval { id, decision }
|
|
}
|
|
|
|
pub(crate) fn resolve_elicitation(
|
|
server_name: String,
|
|
request_id: AppServerRequestId,
|
|
decision: McpServerElicitationAction,
|
|
content: Option<Value>,
|
|
meta: Option<Value>,
|
|
) -> Self {
|
|
Self::ResolveElicitation {
|
|
server_name,
|
|
request_id,
|
|
decision,
|
|
content,
|
|
meta,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn user_input_answer(id: String, response: ToolRequestUserInputResponse) -> Self {
|
|
Self::UserInputAnswer { id, response }
|
|
}
|
|
|
|
pub(crate) fn request_permissions_response(
|
|
id: String,
|
|
response: RequestPermissionsResponse,
|
|
) -> Self {
|
|
Self::RequestPermissionsResponse { id, response }
|
|
}
|
|
|
|
pub(crate) fn reload_user_config() -> Self {
|
|
Self::ReloadUserConfig
|
|
}
|
|
|
|
pub(crate) fn list_skills(cwds: Vec<PathBuf>, force_reload: bool) -> Self {
|
|
Self::ListSkills { cwds, force_reload }
|
|
}
|
|
|
|
pub(crate) fn compact() -> Self {
|
|
Self::Compact
|
|
}
|
|
|
|
pub(crate) fn set_thread_name(name: String) -> Self {
|
|
Self::SetThreadName { name }
|
|
}
|
|
|
|
pub(crate) fn review(target: ReviewTarget) -> Self {
|
|
Self::Review { target }
|
|
}
|
|
|
|
pub(crate) fn approve_guardian_denied_action(event: GuardianAssessmentEvent) -> Self {
|
|
Self::ApproveGuardianDeniedAction { event }
|
|
}
|
|
|
|
pub(crate) fn is_review(&self) -> bool {
|
|
matches!(self, Self::Review { .. })
|
|
}
|
|
}
|
|
|
|
impl From<&AppCommand> for AppCommand {
|
|
fn from(value: &AppCommand) -> Self {
|
|
value.clone()
|
|
}
|
|
}
|