Allow reviewing and continuing misalignment-paused chats (#42410)

## Why

Misalignment policy failures pause the current chat. When the server provides findings and a continuation request, users should be able to inspect them and explicitly decide whether to proceed.

## What changed

- Add a `Review findings` flow that shows the detailed explanation and quoted continuation request in an overlay.
- Require explicit acknowledgement before starting a continuation turn, while preserving the active approval and sandbox settings.
- Keep ordinary input blocked and reject stale, replaced, invalid, or duplicate continuation attempts.
- Label the overlay shortcut as `q close` to match its behavior.

## Testing

- Add TUI tests covering findings review, explicit confirmation, successful and failed continuation, stale findings, duplicate events, and invalid continuation data.

GitOrigin-RevId: 36a738adf272f95db92eaee5712fb00061d0fa52
This commit is contained in:
Eric Traut authored and copyberry committed 2026-09-03 01:51:14 +00:00
1 parent 93053c7f5d
commit fdf23b4097
31 files changed
+740 -41

No files matched your search

+24 -5
View File
@@ -60,6 +60,9 @@ impl ChatWidget {
self.on_thread_settings_updated(notification);
}
ServerNotification::TurnStarted(notification) => {
if replay_kind.is_none() {
self.clear_misalignment_for_new_turn(&notification.turn.id);
}
self.turn_lifecycle.last_turn_id = Some(notification.turn.id);
self.last_non_retry_error = None;
if !matches!(replay_kind, Some(ReplayKind::ResumeInitialMessages)) {
@@ -136,10 +139,20 @@ impl ChatWidget {
notification.turn_id.clone(),
notification.error.message.clone(),
));
self.handle_non_retry_error(
notification.error.message,
notification.error.codex_error_info,
);
if !from_replay
&& notification.error.codex_error_info
== Some(AppServerCodexErrorInfo::MisalignmentPolicyViolation)
{
self.on_misalignment_error(
Some(notification.turn_id),
notification.error.misalignment,
);
} else {
self.handle_non_retry_error(
notification.error.message,
notification.error.codex_error_info,
);
}
}
}
ServerNotification::SkillsChanged(_) => {
@@ -330,7 +343,13 @@ impl ChatWidget {
}
TurnStatus::Failed => {
if let Some(error) = notification.turn.error {
if self.last_non_retry_error.as_ref()
if replay_kind.is_none()
&& error.codex_error_info
== Some(AppServerCodexErrorInfo::MisalignmentPolicyViolation)
{
self.on_misalignment_error(Some(notification.turn.id), error.misalignment);
self.last_non_retry_error = None;
} else if self.last_non_retry_error.as_ref()
== Some(&(notification.turn.id.clone(), error.message.clone()))
{
self.last_non_retry_error = None;