fix(13-08): confirm timeout 120s→300s + chrome closes an expired dialog

On-device UAT: the operator was timed out mid-read (120s), the chat turn
returned 'declined' while the dialog was still up, and their Approve then
hit a dead entry ('no such pending confirmation', 13:37:12 log). Nothing
executed — the gate failed safe — but the UX was a lie in both directions.

- CONFIRM_TIMEOUT 120s→300s: human-speed per T-13-51's own rubric.
- ContextBroker dispatches aiui:tool-confirm-expired when a pending action
  vanishes node-side (poll) or the turn ends; Chat.vue closes the modal on
  it. Same host-only CustomEvent discipline; iframe has no path to it.
- Two new tests; 21/21 green across toolConfirm + chatAiuiEmbed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-05 13:56:49 -04:00
co-authored by Claude Fable 5
parent 44f552cc3d
commit 31f9a4d5bf
4 changed files with 91 additions and 2 deletions
+7 -2
View File
@@ -33,8 +33,13 @@ use super::tools::{ToolArgs, ToolDef};
/// How long an unresolved confirmation waits before declining on its own.
/// Human-speed (the operator may be reading carefully), but bounded — an
/// abandoned dialog must never leak its waiting task (T-13-51).
pub const CONFIRM_TIMEOUT: Duration = Duration::from_secs(120);
/// abandoned dialog must never leak its waiting task (T-13-51). 120s
/// proved too short in 13-08's on-device UAT: a real operator reading the
/// dialog (and screenshotting it, per the checkpoint script) was timed out
/// mid-decision, and their Approve then landed on a dead entry. Five
/// minutes keeps the bound while making that race an edge case; the
/// chrome now also closes the dialog when its pending action expires.
pub const CONFIRM_TIMEOUT: Duration = Duration::from_secs(300);
/// The human's answer, as seen by the suspended tool call.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]