mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
cca1e0ba1d
## Summary - Bump the workspace Rust toolchain from `1.93.0` to `1.95.0` across Cargo, Bazel, CI, release workflows, devcontainers, and the Codex environment config. - Refresh `MODULE.bazel.lock` so the Bazel Rust toolchain artifacts match the new version. - Leave purpose-specific toolchains unchanged, including the `argument-comment-lint` nightly and the upstream `rusty_v8` `1.91.0` build pin. - Includes fixes for new lints from `just fix` and a few codex-authored fixes for lints without a suggestion.
354 lines
15 KiB
Rust
354 lines
15 KiB
Rust
use super::*;
|
|
|
|
impl ChatWidget {
|
|
pub(crate) fn handle_server_notification(
|
|
&mut self,
|
|
notification: ServerNotification,
|
|
replay_kind: Option<ReplayKind>,
|
|
) {
|
|
if self.active_side_conversation
|
|
&& replay_kind.is_none()
|
|
&& matches!(notification, ServerNotification::McpServerStatusUpdated(_))
|
|
{
|
|
return;
|
|
}
|
|
let from_replay = replay_kind.is_some();
|
|
let is_resume_initial_replay =
|
|
matches!(replay_kind, Some(ReplayKind::ResumeInitialMessages));
|
|
let is_retry_error = matches!(
|
|
¬ification,
|
|
ServerNotification::Error(ErrorNotification {
|
|
will_retry: true,
|
|
..
|
|
})
|
|
);
|
|
if !is_resume_initial_replay && !is_retry_error {
|
|
self.restore_retry_status_header_if_present();
|
|
}
|
|
match notification {
|
|
ServerNotification::ThreadTokenUsageUpdated(notification) => {
|
|
self.set_token_info(Some(token_usage_info_from_app_server(
|
|
notification.token_usage,
|
|
)));
|
|
}
|
|
ServerNotification::ThreadNameUpdated(notification) => {
|
|
match ThreadId::from_string(¬ification.thread_id) {
|
|
Ok(thread_id) => {
|
|
self.on_thread_name_updated(thread_id, notification.thread_name)
|
|
}
|
|
Err(err) => {
|
|
tracing::warn!(
|
|
thread_id = notification.thread_id,
|
|
error = %err,
|
|
"ignoring app-server ThreadNameUpdated with invalid thread_id"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
ServerNotification::ThreadGoalUpdated(notification) => {
|
|
self.on_thread_goal_updated(notification.goal, notification.turn_id);
|
|
}
|
|
ServerNotification::ThreadGoalCleared(notification) => {
|
|
self.on_thread_goal_cleared(notification.thread_id.as_str());
|
|
}
|
|
ServerNotification::ThreadSettingsUpdated(notification) => {
|
|
self.on_thread_settings_updated(notification);
|
|
}
|
|
ServerNotification::TurnStarted(notification) => {
|
|
self.turn_lifecycle.last_turn_id = Some(notification.turn.id);
|
|
self.last_non_retry_error = None;
|
|
if !matches!(replay_kind, Some(ReplayKind::ResumeInitialMessages)) {
|
|
self.on_task_started();
|
|
}
|
|
}
|
|
ServerNotification::TurnCompleted(notification) => {
|
|
self.handle_turn_completed_notification(notification, replay_kind);
|
|
}
|
|
ServerNotification::ItemStarted(notification) => {
|
|
self.handle_item_started_notification(notification, replay_kind.is_some());
|
|
}
|
|
ServerNotification::ItemCompleted(notification) => {
|
|
self.handle_item_completed_notification(notification, replay_kind);
|
|
}
|
|
ServerNotification::AgentMessageDelta(notification) => {
|
|
self.on_agent_message_delta(notification.delta);
|
|
}
|
|
ServerNotification::PlanDelta(notification) => self.on_plan_delta(notification.delta),
|
|
ServerNotification::ReasoningSummaryTextDelta(notification) => {
|
|
self.on_agent_reasoning_delta(notification.delta);
|
|
}
|
|
ServerNotification::ReasoningTextDelta(notification) => {
|
|
if self.config.show_raw_agent_reasoning {
|
|
self.on_agent_reasoning_delta(notification.delta);
|
|
}
|
|
}
|
|
ServerNotification::ReasoningSummaryPartAdded(_) => self.on_reasoning_section_break(),
|
|
ServerNotification::TerminalInteraction(notification) => {
|
|
self.on_terminal_interaction(notification.process_id, notification.stdin)
|
|
}
|
|
ServerNotification::CommandExecutionOutputDelta(notification) => {
|
|
self.on_exec_command_output_delta(¬ification.item_id, ¬ification.delta);
|
|
}
|
|
ServerNotification::FileChangeOutputDelta(notification) => {
|
|
self.on_patch_apply_output_delta(notification.item_id, notification.delta);
|
|
}
|
|
ServerNotification::TurnDiffUpdated(notification) => {
|
|
self.on_turn_diff(notification.diff)
|
|
}
|
|
ServerNotification::TurnPlanUpdated(notification) => {
|
|
self.on_plan_update(UpdatePlanArgs {
|
|
explanation: notification.explanation,
|
|
plan: notification
|
|
.plan
|
|
.into_iter()
|
|
.map(|step| UpdatePlanItemArg {
|
|
step: step.step,
|
|
status: match step.status {
|
|
TurnPlanStepStatus::Pending => UpdatePlanItemStatus::Pending,
|
|
TurnPlanStepStatus::InProgress => UpdatePlanItemStatus::InProgress,
|
|
TurnPlanStepStatus::Completed => UpdatePlanItemStatus::Completed,
|
|
},
|
|
})
|
|
.collect(),
|
|
})
|
|
}
|
|
ServerNotification::HookStarted(notification) => {
|
|
self.on_hook_started(notification.run);
|
|
}
|
|
ServerNotification::HookCompleted(notification) => {
|
|
self.on_hook_completed(notification.run);
|
|
}
|
|
ServerNotification::Error(notification) => {
|
|
if notification.will_retry {
|
|
if !from_replay {
|
|
self.on_stream_error(
|
|
notification.error.message,
|
|
notification.error.additional_details,
|
|
);
|
|
}
|
|
} else {
|
|
self.last_non_retry_error = Some((
|
|
notification.turn_id.clone(),
|
|
notification.error.message.clone(),
|
|
));
|
|
self.handle_non_retry_error(
|
|
notification.error.message,
|
|
notification.error.codex_error_info,
|
|
);
|
|
}
|
|
}
|
|
ServerNotification::SkillsChanged(_) => {
|
|
self.refresh_skills_for_current_cwd(/*force_reload*/ true);
|
|
}
|
|
ServerNotification::ModelRerouted(_) => {}
|
|
ServerNotification::ModelVerification(notification) => {
|
|
self.on_app_server_model_verification(¬ification.verifications)
|
|
}
|
|
ServerNotification::Warning(notification) => self.on_warning(notification.message),
|
|
ServerNotification::GuardianWarning(notification) => {
|
|
self.on_warning(notification.message)
|
|
}
|
|
ServerNotification::DeprecationNotice(notification) => {
|
|
self.on_deprecation_notice(notification.summary, notification.details)
|
|
}
|
|
ServerNotification::ConfigWarning(notification) => self.on_warning(
|
|
notification
|
|
.details
|
|
.map(|details| format!("{}: {details}", notification.summary))
|
|
.unwrap_or(notification.summary),
|
|
),
|
|
ServerNotification::McpServerStatusUpdated(notification) => {
|
|
self.on_mcp_server_status_updated(notification)
|
|
}
|
|
ServerNotification::ItemGuardianApprovalReviewStarted(notification) => {
|
|
self.on_guardian_review_notification(
|
|
notification.review_id,
|
|
notification.turn_id,
|
|
notification.started_at_ms,
|
|
notification.review,
|
|
/*completion*/ None,
|
|
notification.action,
|
|
);
|
|
}
|
|
ServerNotification::ItemGuardianApprovalReviewCompleted(notification) => {
|
|
self.on_guardian_review_notification(
|
|
notification.review_id,
|
|
notification.turn_id,
|
|
notification.started_at_ms,
|
|
notification.review,
|
|
Some((notification.completed_at_ms, notification.decision_source)),
|
|
notification.action,
|
|
);
|
|
}
|
|
ServerNotification::ThreadClosed(_) => {
|
|
if !from_replay {
|
|
self.on_shutdown_complete();
|
|
}
|
|
}
|
|
ServerNotification::ThreadRealtimeStarted(notification) => {
|
|
if !from_replay {
|
|
self.on_realtime_conversation_started(notification);
|
|
}
|
|
}
|
|
ServerNotification::ThreadRealtimeItemAdded(notification) => {
|
|
if !from_replay {
|
|
self.on_realtime_item_added(notification);
|
|
}
|
|
}
|
|
ServerNotification::ThreadRealtimeOutputAudioDelta(notification) => {
|
|
if !from_replay {
|
|
self.on_realtime_output_audio_delta(notification);
|
|
}
|
|
}
|
|
ServerNotification::ThreadRealtimeError(notification) => {
|
|
if !from_replay {
|
|
self.on_realtime_error(notification);
|
|
}
|
|
}
|
|
ServerNotification::ThreadRealtimeClosed(notification) => {
|
|
if !from_replay {
|
|
self.on_realtime_conversation_closed(notification);
|
|
}
|
|
}
|
|
ServerNotification::ThreadRealtimeSdp(notification) => {
|
|
if !from_replay {
|
|
self.on_realtime_conversation_sdp(notification.sdp);
|
|
}
|
|
}
|
|
ServerNotification::ServerRequestResolved(_)
|
|
| ServerNotification::AccountUpdated(_)
|
|
| ServerNotification::AccountRateLimitsUpdated(_)
|
|
| ServerNotification::ThreadStarted(_)
|
|
| ServerNotification::ThreadStatusChanged(_)
|
|
| ServerNotification::ThreadArchived(_)
|
|
| ServerNotification::ThreadUnarchived(_)
|
|
| ServerNotification::RawResponseItemCompleted(_)
|
|
| ServerNotification::CommandExecOutputDelta(_)
|
|
| ServerNotification::ProcessOutputDelta(_)
|
|
| ServerNotification::ProcessExited(_)
|
|
| ServerNotification::FileChangePatchUpdated(_)
|
|
| ServerNotification::McpToolCallProgress(_)
|
|
| ServerNotification::McpServerOauthLoginCompleted(_)
|
|
| ServerNotification::AppListUpdated(_)
|
|
| ServerNotification::RemoteControlStatusChanged(_)
|
|
| ServerNotification::ExternalAgentConfigImportCompleted(_)
|
|
| ServerNotification::FsChanged(_)
|
|
| ServerNotification::FuzzyFileSearchSessionUpdated(_)
|
|
| ServerNotification::FuzzyFileSearchSessionCompleted(_)
|
|
| ServerNotification::ThreadRealtimeTranscriptDelta(_)
|
|
| ServerNotification::ThreadRealtimeTranscriptDone(_)
|
|
| ServerNotification::WindowsWorldWritableWarning(_)
|
|
| ServerNotification::WindowsSandboxSetupCompleted(_)
|
|
| ServerNotification::AccountLoginCompleted(_) => {}
|
|
ServerNotification::ContextCompacted(_) => {}
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_turn_completed_notification(
|
|
&mut self,
|
|
notification: TurnCompletedNotification,
|
|
replay_kind: Option<ReplayKind>,
|
|
) {
|
|
// User-message dedupe only suppresses the app-server echo of a prompt
|
|
// this TUI already rendered locally. Once that turn ends, another
|
|
// client can submit the same text and it still needs its own user cell.
|
|
self.last_rendered_user_message_display = None;
|
|
match notification.turn.status {
|
|
TurnStatus::Completed => {
|
|
self.last_non_retry_error = None;
|
|
self.on_task_complete(
|
|
/*last_agent_message*/ None,
|
|
notification.turn.duration_ms,
|
|
replay_kind.is_some(),
|
|
)
|
|
}
|
|
TurnStatus::Interrupted => {
|
|
self.last_non_retry_error = None;
|
|
let reason = if self
|
|
.turn_lifecycle
|
|
.take_budget_limited(notification.turn.id.as_str())
|
|
{
|
|
TurnAbortReason::BudgetLimited
|
|
} else {
|
|
TurnAbortReason::Interrupted
|
|
};
|
|
self.on_interrupted_turn(reason);
|
|
}
|
|
TurnStatus::Failed => {
|
|
if let Some(error) = notification.turn.error {
|
|
if self.last_non_retry_error.as_ref()
|
|
== Some(&(notification.turn.id.clone(), error.message.clone()))
|
|
{
|
|
self.last_non_retry_error = None;
|
|
} else {
|
|
self.handle_non_retry_error(error.message, error.codex_error_info);
|
|
}
|
|
} else {
|
|
self.last_non_retry_error = None;
|
|
self.finalize_turn();
|
|
self.request_redraw();
|
|
self.maybe_send_next_queued_input();
|
|
}
|
|
}
|
|
TurnStatus::InProgress => {}
|
|
}
|
|
}
|
|
|
|
fn handle_item_started_notification(
|
|
&mut self,
|
|
notification: ItemStartedNotification,
|
|
from_replay: bool,
|
|
) {
|
|
match notification.item {
|
|
item @ ThreadItem::CommandExecution { .. } => self.on_command_execution_started(item),
|
|
ThreadItem::FileChange { id: _, changes, .. } => {
|
|
self.on_patch_apply_begin(file_update_changes_to_display(changes));
|
|
}
|
|
item @ ThreadItem::McpToolCall { .. } => self.on_mcp_tool_call_started(item),
|
|
ThreadItem::WebSearch { id, .. } => {
|
|
self.on_web_search_begin(id);
|
|
}
|
|
ThreadItem::ImageGeneration { .. } => {
|
|
self.on_image_generation_begin();
|
|
}
|
|
ThreadItem::CollabAgentToolCall {
|
|
id,
|
|
tool,
|
|
status,
|
|
sender_thread_id,
|
|
receiver_thread_ids,
|
|
prompt,
|
|
model,
|
|
reasoning_effort,
|
|
agents_states,
|
|
} => self.on_collab_agent_tool_call(ThreadItem::CollabAgentToolCall {
|
|
id,
|
|
tool,
|
|
status,
|
|
sender_thread_id,
|
|
receiver_thread_ids,
|
|
prompt,
|
|
model,
|
|
reasoning_effort,
|
|
agents_states,
|
|
}),
|
|
ThreadItem::EnteredReviewMode { review, .. } if !from_replay => {
|
|
self.enter_review_mode_with_hint(review, /*from_replay*/ false);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn handle_item_completed_notification(
|
|
&mut self,
|
|
notification: ItemCompletedNotification,
|
|
replay_kind: Option<ReplayKind>,
|
|
) {
|
|
self.handle_thread_item(
|
|
notification.item,
|
|
notification.turn_id,
|
|
replay_kind.map_or(ThreadItemRenderSource::Live, ThreadItemRenderSource::Replay),
|
|
);
|
|
}
|
|
}
|