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
+14
View File
@@ -217,6 +217,16 @@ function dismissToolConfirm() {
toolConfirm.value = null
}
function onToolConfirmExpired(e: Event) {
// 13-08 on-device UAT: the node no longer holds this pending action
// (timed out, or resolved elsewhere) — close the dialog rather than
// leave the human an Approve button whose click can only be refused.
const detail = (e as CustomEvent).detail as { reqId?: string }
if (toolConfirm.value && detail?.reqId === toolConfirm.value.reqId) {
toolConfirm.value = null
}
}
function onAiuiMessage(event: MessageEvent) {
if (!aiuiUrl.value) return
// Validate origin — only accept messages from AIUI
@@ -246,6 +256,8 @@ function armChatLive() {
window.addEventListener('message', onAiuiMessage)
window.removeEventListener('aiui:tool-confirm-request', onToolConfirmRequest)
window.addEventListener('aiui:tool-confirm-request', onToolConfirmRequest)
window.removeEventListener('aiui:tool-confirm-expired', onToolConfirmExpired)
window.addEventListener('aiui:tool-confirm-expired', onToolConfirmExpired)
broker?.stop()
broker = null
if (aiuiUrl.value) {
@@ -266,6 +278,7 @@ onActivated(() => armChatLive())
onDeactivated(() => {
window.removeEventListener('message', onAiuiMessage)
window.removeEventListener('aiui:tool-confirm-request', onToolConfirmRequest)
window.removeEventListener('aiui:tool-confirm-expired', onToolConfirmExpired)
broker?.stop()
broker = null
if (loadTimeout) { clearTimeout(loadTimeout); loadTimeout = null }
@@ -280,6 +293,7 @@ onMounted(() => armChatLive())
onBeforeUnmount(() => {
window.removeEventListener('message', onAiuiMessage)
window.removeEventListener('aiui:tool-confirm-request', onToolConfirmRequest)
window.removeEventListener('aiui:tool-confirm-expired', onToolConfirmExpired)
broker?.stop()
broker = null
if (loadTimeout) { clearTimeout(loadTimeout); loadTimeout = null }