@@ -2867,6 +2867,21 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
28672867 frameId: Number.isInteger(entry?.frameId) ? entry.frameId : 0,
28682868 payload: entry?.result,
28692869 }));
2870+ const hiddenChallenges = entries.flatMap(entry => {
2871+ const label = String(entry.payload?.hiddenChallenge?.label || '');
2872+ if (!label) return [];
2873+ const normalized = detectChallengeDialog(
2874+ `dialog ${JSON.stringify(label.slice(0, 200))}`,
2875+ { allowGenericFailure: options?.allowGenericFailure === true },
2876+ );
2877+ if (!normalized?.normalizedLabel) return [];
2878+ return [{
2879+ frameId: entry.frameId,
2880+ frameUrl: String(entry.payload?.frameContext?.frameUrl || ''),
2881+ label: normalized.label,
2882+ normalizedLabel: normalized.normalizedLabel,
2883+ }];
2884+ });
28702885 const inspectedFrameIds = new Set(entries
28712886 .filter(entry => entry.payload && typeof entry.payload === 'object')
28722887 .map(entry => entry.frameId));
@@ -2876,9 +2891,14 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
28762891 return includeStatus
28772892 ? {
28782893 challenge,
2894+ challengeHidden: hiddenChallenges.length > 0,
2895+ hiddenChallenges,
2896+ // With no expected frame, "complete" still requires at least one
2897+ // frame to have actually been inspected.
28792898 inspectionComplete: expectedFrameId === null
2880- || inspectedFrameIds.has(expectedFrameId)
2881- || (navigationInspectionComplete && !expectedFrameStillExists),
2899+ ? inspectedFrameIds.size > 0
2900+ : (inspectedFrameIds.has(expectedFrameId)
2901+ || (navigationInspectionComplete && !expectedFrameStillExists)),
28822902 }
28832903 : challenge;
28842904 };
@@ -2891,7 +2911,12 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
28912911 return inspected(results);
28922912 } catch {
28932913 return includeStatus
2894- ? { challenge: null, inspectionComplete: false }
2914+ ? {
2915+ challenge: null,
2916+ challengeHidden: false,
2917+ hiddenChallenges: [],
2918+ inspectionComplete: false,
2919+ }
28952920 : null;
28962921 }
28972922 }
@@ -2919,6 +2944,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
29192944 ? challenge.frameId
29202945 : null,
29212946 captchaChallengeFrameUrl: challenge.frameUrl || '',
2947+ captchaChallengeVisibilityConfirmed: true,
29222948 },
29232949 {},
29242950 );
@@ -2937,6 +2963,11 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
29372963 }
29382964
29392965 const activeGate = this._captchaGateStates.get(tabId);
2966+ const treeFilter = String(toolArgs?.filter || 'all').toLowerCase();
2967+ let observedChallengeFrameId = Number.isInteger(toolResult.captchaChallengeFrameId)
2968+ ? toolResult.captchaChallengeFrameId
2969+ : null;
2970+ let observedChallengeFrameUrl = String(toolResult.captchaChallengeFrameUrl || '');
29402971 let challenge = detectChallengeDialog(toolResult.pageContent, {
29412972 allowGenericFailure: !!activeGate,
29422973 });
@@ -2946,11 +2977,36 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
29462977 { allowGenericFailure: !!activeGate },
29472978 );
29482979 }
2980+ if (
2981+ challenge
2982+ && treeFilter !== 'visible'
2983+ && toolResult.captchaChallengeVisibilityConfirmed !== true
2984+ ) {
2985+ const recheck = await this._detectChallengeDialogBeforeMutation(tabId, {
2986+ includeStatus: true,
2987+ expectedFrameId: observedChallengeFrameId,
2988+ allowGenericFailure: !!activeGate,
2989+ });
2990+ if (recheck.challenge?.label) {
2991+ challenge = detectChallengeDialog(
2992+ `dialog ${JSON.stringify(String(recheck.challenge.label).slice(0, 200))}`,
2993+ { allowGenericFailure: !!activeGate },
2994+ );
2995+ observedChallengeFrameId = Number.isInteger(recheck.challenge.frameId)
2996+ ? recheck.challenge.frameId
2997+ : null;
2998+ observedChallengeFrameUrl = String(recheck.challenge.frameUrl || '');
2999+ }
3000+ // A DOM scan cannot prove that the tree-observed dialog is the same
3001+ // element as a hidden match: a page may retain a hidden template while
3002+ // rendering the active challenge in a closed shadow root. Hidden,
3003+ // missing, and inconclusive scan results therefore remain diagnostic
3004+ // only and keep the gate fail-closed.
3005+ }
29493006 let pageUrl = String(toolResult.currentUrl || toolResult.pageUrl || '');
29503007 if (!pageUrl) {
29513008 try { pageUrl = await this._currentUrl(tabId); } catch {}
29523009 }
2953- const treeFilter = String(toolArgs?.filter || 'all').toLowerCase();
29543010 const requestedPage = toolArgs?.page;
29553011 const requestedMaxDepth = toolArgs?.maxDepth;
29563012 const parsedMaxDepth = Number(requestedMaxDepth);
@@ -3148,10 +3204,10 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
31483204 key,
31493205 status: publicGate.status,
31503206 publicGate,
3151- ...(Number.isInteger(toolResult.captchaChallengeFrameId )
3207+ ...(Number.isInteger(observedChallengeFrameId )
31523208 ? {
3153- challengeFrameId: toolResult.captchaChallengeFrameId ,
3154- challengeFrameUrl: String(toolResult.captchaChallengeFrameUrl || '') ,
3209+ challengeFrameId: observedChallengeFrameId ,
3210+ challengeFrameUrl: observedChallengeFrameUrl ,
31553211 }
31563212 : {}),
31573213 });
0 commit comments