@@ -2827,6 +2827,21 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
28272827 frameId: Number.isInteger(entry?.frameId) ? entry.frameId : 0,
28282828 payload: entry?.result,
28292829 }));
2830+ const hiddenChallenges = entries.flatMap(entry => {
2831+ const label = String(entry.payload?.hiddenChallenge?.label || '');
2832+ if (!label) return [];
2833+ const normalized = detectChallengeDialog(
2834+ `dialog ${JSON.stringify(label.slice(0, 200))}`,
2835+ { allowGenericFailure: options?.allowGenericFailure === true },
2836+ );
2837+ if (!normalized?.normalizedLabel) return [];
2838+ return [{
2839+ frameId: entry.frameId,
2840+ frameUrl: String(entry.payload?.frameContext?.frameUrl || ''),
2841+ label: normalized.label,
2842+ normalizedLabel: normalized.normalizedLabel,
2843+ }];
2844+ });
28302845 const inspectedFrameIds = new Set(entries
28312846 .filter(entry => entry.payload && typeof entry.payload === 'object')
28322847 .map(entry => entry.frameId));
@@ -2836,9 +2851,8 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
28362851 return includeStatus
28372852 ? {
28382853 challenge,
2839- challengeHidden: entries.some(entry =>
2840- entry.payload && typeof entry.payload === 'object' && entry.payload.hiddenChallenge?.label
2841- ),
2854+ challengeHidden: hiddenChallenges.length > 0,
2855+ hiddenChallenges,
28422856 // With no expected frame, "complete" still requires at least one
28432857 // frame to have actually been inspected.
28442858 inspectionComplete: expectedFrameId === null
@@ -2857,7 +2871,12 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
28572871 return inspected(results);
28582872 } catch {
28592873 return includeStatus
2860- ? { challenge: null, challengeHidden: false, inspectionComplete: false }
2874+ ? {
2875+ challenge: null,
2876+ challengeHidden: false,
2877+ hiddenChallenges: [],
2878+ inspectionComplete: false,
2879+ }
28612880 : null;
28622881 }
28632882 }
@@ -2937,14 +2956,23 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
29372956 ? recheck.challenge.frameId
29382957 : null;
29392958 observedChallengeFrameUrl = String(recheck.challenge.frameUrl || '');
2940- } else if (recheck.inspectionComplete && recheck.challengeHidden) {
2959+ } else if (
2960+ recheck.inspectionComplete
2961+ && recheck.hiddenChallenges?.some(hidden => (
2962+ hidden.frameId === (
2963+ Number.isInteger(observedChallengeFrameId)
2964+ ? observedChallengeFrameId
2965+ : 0
2966+ )
2967+ && hidden.normalizedLabel === challenge.normalizedLabel
2968+ ))
2969+ ) {
29412970 challenge = null;
29422971 }
2943- // Only a completed scan that positively found the dialog hidden clears
2944- // the tree's observation. "Not found" is not disproof — the DOM scan
2945- // cannot see closed shadow roots or unreachable frames the tree can —
2946- // and an inconclusive scan proves nothing; keep the challenge rather
2947- // than failing the gate open.
2972+ // Only a completed scan that found the same dialog hidden in the same
2973+ // frame clears the tree observation. An unrelated hidden challenge,
2974+ // "not found", or an inconclusive scan cannot disprove a dialog in a
2975+ // closed shadow root or unreachable frame; keep the gate fail-closed.
29482976 }
29492977 let pageUrl = String(toolResult.currentUrl || toolResult.pageUrl || '');
29502978 if (!pageUrl) {
0 commit comments