@riddledc/riddle-proof 0.7.58 → 0.7.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -270,6 +270,13 @@ can include `repeat` / `repeat_count` / `times` from 1 to 100; each repetition
270
270
  is recorded with `repeat_index` and `repeat_count`, and `after_ms` runs after
271
271
  each repetition. Use it for bounded game proof helpers, retry controls, or other
272
272
  workflows where one declarative action needs to advance the app several times.
273
+ Add `frame_selector` / `frameSelector` to a setup action when the interaction
274
+ target lives inside an embedded iframe, such as a community game player or
275
+ hosted preview surface. Selector-based actions, storage actions, window calls,
276
+ and setup assertions then execute in that frame context and record
277
+ `frame_selector`, `frame_index`, and `frame_count` in setup-action evidence.
278
+ Use `frame_index` / `frameIndex` when more than one matching iframe is present;
279
+ it defaults to the first frame.
273
280
 
274
281
  `target.timeout_sec` is optional. Use it for known-heavy profile targets so the
275
282
  profile carries its own hosted Riddle worker budget; an explicit CLI `--timeout`
@@ -236,6 +236,11 @@ function normalizeSetupAction(input, index) {
236
236
  if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
237
237
  const type = normalizeSetupActionType(stringValue(input.type), index);
238
238
  const selector = stringValue(input.selector);
239
+ const frameSelector = stringFromOwn(input, "frame_selector", "frameSelector", "iframe_selector", "iframeSelector");
240
+ const frameIndex = numberValue(valueFromOwn(input, "frame_index", "frameIndex", "iframe_index", "iframeIndex"));
241
+ if (frameIndex !== void 0 && (!Number.isInteger(frameIndex) || frameIndex < 0)) {
242
+ throw new Error(`target.setup_actions[${index}].frame_index must be a non-negative integer.`);
243
+ }
239
244
  if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
240
245
  throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
241
246
  }
@@ -288,6 +293,8 @@ function normalizeSetupAction(input, index) {
288
293
  return {
289
294
  type,
290
295
  selector,
296
+ frame_selector: frameSelector,
297
+ frame_index: frameIndex,
291
298
  force: type === "click" && (input.force === true || input.force_click === true || input.forceClick === true),
292
299
  key,
293
300
  value,
@@ -1174,6 +1181,8 @@ function assessSetupActionsFromEvidence(profile, evidence) {
1174
1181
  viewport: viewport.name,
1175
1182
  action: result.action ?? result.type ?? null,
1176
1183
  selector: result.selector ?? null,
1184
+ frame_selector: result.frame_selector ?? null,
1185
+ frame_index: result.frame_index ?? null,
1177
1186
  reason: result.reason ?? result.error ?? null
1178
1187
  });
1179
1188
  }
@@ -1686,10 +1695,12 @@ function assessProfile(profile, evidence) {
1686
1695
  if (result && result.ok === false) {
1687
1696
  failed.push({
1688
1697
  viewport: viewport.name,
1689
- action: result.action || result.type || null,
1690
- selector: result.selector || null,
1691
- reason: result.reason || result.error || null,
1692
- });
1698
+ action: result.action || result.type || null,
1699
+ selector: result.selector || null,
1700
+ frame_selector: result.frame_selector || null,
1701
+ frame_index: result.frame_index ?? null,
1702
+ reason: result.reason || result.error || null,
1703
+ });
1693
1704
  }
1694
1705
  }
1695
1706
  if (results.length < actionCount && results.every((result) => !result || result.ok !== false)) {
@@ -2152,8 +2163,8 @@ function setupJsonValue(value) {
2152
2163
  function setupValuesEqual(left, right) {
2153
2164
  return JSON.stringify(setupJsonValue(left)) === JSON.stringify(setupJsonValue(right));
2154
2165
  }
2155
- async function setupReadWindowValue(path) {
2156
- return await page.evaluate(({ path }) => {
2166
+ async function setupReadWindowValue(context, path) {
2167
+ return await context.evaluate(({ path }) => {
2157
2168
  const toJsonValue = (value) => {
2158
2169
  if (value === null || value === undefined) return null;
2159
2170
  if (typeof value === "string" || typeof value === "boolean") return value;
@@ -2175,6 +2186,80 @@ async function setupReadWindowValue(path) {
2175
2186
  return { ok: true, value: toJsonValue(current) };
2176
2187
  }, { path });
2177
2188
  }
2189
+ function setupFrameSelector(action) {
2190
+ return String(action?.frame_selector || action?.frameSelector || action?.iframe_selector || action?.iframeSelector || "").trim();
2191
+ }
2192
+ function setupFrameIndex(action) {
2193
+ const raw = action?.frame_index ?? action?.frameIndex ?? action?.iframe_index ?? action?.iframeIndex ?? 0;
2194
+ const number = Number(raw);
2195
+ return Number.isInteger(number) && number >= 0 ? number : 0;
2196
+ }
2197
+ function setupScopeEvidence(scope) {
2198
+ if (!scope || !scope.frame_selector) return {};
2199
+ return {
2200
+ frame_selector: scope.frame_selector,
2201
+ frame_index: scope.frame_index,
2202
+ frame_count: scope.frame_count,
2203
+ };
2204
+ }
2205
+ function setupScopeFailure(base, scope) {
2206
+ return {
2207
+ ...base,
2208
+ ...setupScopeEvidence(scope),
2209
+ reason: scope?.reason || "frame_scope_unavailable",
2210
+ error: scope?.error || undefined,
2211
+ };
2212
+ }
2213
+ async function setupActionScope(action, timeout) {
2214
+ const frameSelector = setupFrameSelector(action);
2215
+ if (!frameSelector) return { ok: true, context: page };
2216
+ const frameIndex = setupFrameIndex(action);
2217
+ let frameCount = 0;
2218
+ let locator = null;
2219
+ try {
2220
+ await page.waitForSelector(frameSelector, { state: "attached", timeout });
2221
+ locator = page.locator(frameSelector);
2222
+ frameCount = await locator.count();
2223
+ } catch (error) {
2224
+ return {
2225
+ ok: false,
2226
+ reason: "frame_selector_not_found",
2227
+ frame_selector: frameSelector,
2228
+ frame_index: frameIndex,
2229
+ frame_count: frameCount,
2230
+ error: String(error && error.message ? error.message : error).slice(0, 1000),
2231
+ };
2232
+ }
2233
+ if (!frameCount) {
2234
+ return { ok: false, reason: "frame_selector_not_found", frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
2235
+ }
2236
+ if (frameIndex >= frameCount) {
2237
+ return { ok: false, reason: "frame_index_out_of_range", frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
2238
+ }
2239
+ const handle = await locator.nth(frameIndex).elementHandle({ timeout }).catch((error) => ({ __riddle_error: error }));
2240
+ if (!handle || handle.__riddle_error) {
2241
+ return {
2242
+ ok: false,
2243
+ reason: "frame_element_unavailable",
2244
+ frame_selector: frameSelector,
2245
+ frame_index: frameIndex,
2246
+ frame_count: frameCount,
2247
+ error: handle?.__riddle_error ? String(handle.__riddle_error && handle.__riddle_error.message ? handle.__riddle_error.message : handle.__riddle_error).slice(0, 1000) : undefined,
2248
+ };
2249
+ }
2250
+ const frame = typeof handle.contentFrame === "function" ? await handle.contentFrame().catch((error) => ({ __riddle_error: error })) : null;
2251
+ if (!frame || frame.__riddle_error) {
2252
+ return {
2253
+ ok: false,
2254
+ reason: "content_frame_unavailable",
2255
+ frame_selector: frameSelector,
2256
+ frame_index: frameIndex,
2257
+ frame_count: frameCount,
2258
+ error: frame?.__riddle_error ? String(frame.__riddle_error && frame.__riddle_error.message ? frame.__riddle_error.message : frame.__riddle_error).slice(0, 1000) : undefined,
2259
+ };
2260
+ }
2261
+ return { ok: true, context: frame, frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
2262
+ }
2178
2263
  async function setupLocatorText(locator, index) {
2179
2264
  return await locator.nth(index).textContent({ timeout: 1000 }).catch(() => "");
2180
2265
  }
@@ -2336,7 +2421,8 @@ async function registerNetworkMocks(mocks) {
2336
2421
  }
2337
2422
  async function executeSetupAction(action, ordinal) {
2338
2423
  const type = setupActionType(action);
2339
- const base = { ok: false, action: type || "unknown", ordinal, selector: action.selector || null };
2424
+ const frameSelector = setupFrameSelector(action);
2425
+ const base = { ok: false, action: type || "unknown", ordinal, selector: action.selector || null, frame_selector: frameSelector || null };
2340
2426
  const timeout = setupNumber(action.timeout_ms, 5000);
2341
2427
  try {
2342
2428
  if (type === "wait") {
@@ -2345,51 +2431,65 @@ async function executeSetupAction(action, ordinal) {
2345
2431
  return { ...base, ok: true, ms };
2346
2432
  }
2347
2433
  if (type === "wait_for_selector") {
2348
- await page.waitForSelector(action.selector, { state: "visible", timeout });
2349
- return { ...base, ok: true, timeout_ms: timeout };
2434
+ const scope = await setupActionScope(action, timeout);
2435
+ if (!scope.ok) return setupScopeFailure(base, scope);
2436
+ await scope.context.waitForSelector(action.selector, { state: "visible", timeout });
2437
+ return { ...base, ...setupScopeEvidence(scope), ok: true, timeout_ms: timeout };
2350
2438
  }
2351
2439
  if (type === "press") {
2352
2440
  const key = String(action.key || "").trim();
2353
2441
  if (!key) return { ...base, reason: "missing_key" };
2442
+ const scope = await setupActionScope(action, timeout);
2443
+ if (!scope.ok) return setupScopeFailure(base, scope);
2354
2444
  if (!action.selector) {
2355
- await page.keyboard.press(key);
2356
- return { ...base, ok: true, key };
2445
+ if (scope.frame_selector) {
2446
+ await scope.context.locator("body").press(key, { timeout });
2447
+ } else {
2448
+ await page.keyboard.press(key);
2449
+ }
2450
+ return { ...base, ...setupScopeEvidence(scope), ok: true, key };
2357
2451
  }
2358
- const locator = page.locator(action.selector);
2452
+ const locator = scope.context.locator(action.selector);
2359
2453
  const count = await locator.count();
2360
2454
  if (!count) return { ...base, reason: "selector_not_found", count, key };
2361
2455
  const targetIndex = Number.isInteger(action.index) ? action.index : 0;
2362
2456
  if (targetIndex < 0 || targetIndex >= count) return { ...base, reason: "index_out_of_range", count, target_index: targetIndex, key };
2363
2457
  await locator.nth(targetIndex).press(key, { timeout });
2364
- return { ...base, ok: true, count, target_index: targetIndex, key };
2458
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, key };
2365
2459
  }
2366
2460
  if (type === "local_storage" || type === "session_storage") {
2367
2461
  const value = setupActionValue(action);
2368
- await page.evaluate(({ type, key, value }) => {
2462
+ const scope = await setupActionScope(action, timeout);
2463
+ if (!scope.ok) return setupScopeFailure(base, scope);
2464
+ await scope.context.evaluate(({ type, key, value }) => {
2369
2465
  const storage = type === "session_storage" ? window.sessionStorage : window.localStorage;
2370
2466
  storage.setItem(key, value);
2371
2467
  }, { type, key: action.key, value });
2372
2468
  if (action.reload === true) {
2373
2469
  await page.reload({ waitUntil: "domcontentloaded", timeout: 45000 });
2374
2470
  }
2375
- return { ...base, ok: true, key: action.key, value_length: value.length, reload: action.reload === true };
2471
+ return { ...base, ...setupScopeEvidence(scope), ok: true, key: action.key, value_length: value.length, reload: action.reload === true };
2376
2472
  }
2377
2473
  if (type === "clear_storage") {
2378
2474
  const storage = action.storage || "both";
2379
- await page.evaluate(({ storage }) => {
2475
+ const scope = await setupActionScope(action, timeout);
2476
+ if (!scope.ok) return setupScopeFailure(base, scope);
2477
+ await scope.context.evaluate(({ storage }) => {
2380
2478
  if (storage === "local" || storage === "both") window.localStorage.clear();
2381
2479
  if (storage === "session" || storage === "both") window.sessionStorage.clear();
2382
2480
  }, { storage });
2383
2481
  if (action.reload === true) {
2384
2482
  await page.reload({ waitUntil: "domcontentloaded", timeout: 45000 });
2385
2483
  }
2386
- return { ...base, ok: true, storage, reload: action.reload === true };
2484
+ return { ...base, ...setupScopeEvidence(scope), ok: true, storage, reload: action.reload === true };
2387
2485
  }
2388
2486
  if (type === "window_call") {
2389
2487
  const path = String(action.path || action.function_path || action.functionPath || "");
2390
2488
  const args = Array.isArray(action.args) ? action.args : [];
2391
2489
  if (!path) return { ...base, path, reason: "missing_path" };
2392
- const result = await page.evaluate(async ({ path, args }) => {
2490
+ const scope = await setupActionScope(action, timeout);
2491
+ if (!scope.ok) return setupScopeFailure(base, scope);
2492
+ const result = await scope.context.evaluate(async ({ path, args }) => {
2393
2493
  const toJsonValue = (value) => {
2394
2494
  if (value === null || value === undefined) return null;
2395
2495
  if (typeof value === "string" || typeof value === "boolean") return value;
@@ -2429,6 +2529,7 @@ async function executeSetupAction(action, ordinal) {
2429
2529
  const expectationMet = !hasExpectation || setupValuesEqual(result.returned, expected);
2430
2530
  return {
2431
2531
  ...base,
2532
+ ...setupScopeEvidence(scope),
2432
2533
  ok: Boolean(result.ok && expectationMet),
2433
2534
  path,
2434
2535
  arg_count: args.length,
@@ -2459,13 +2560,16 @@ async function executeSetupAction(action, ordinal) {
2459
2560
  : action.expect;
2460
2561
  if (!path) return { ...base, path, reason: "missing_path" };
2461
2562
  if (!hasExpected) return { ...base, path, reason: "missing_expected_value" };
2563
+ const scope = await setupActionScope(action, timeout);
2564
+ if (!scope.ok) return setupScopeFailure(base, scope);
2462
2565
  const startedAt = Date.now();
2463
2566
  let result = null;
2464
2567
  while (Date.now() - startedAt <= timeout) {
2465
- result = await setupReadWindowValue(path);
2568
+ result = await setupReadWindowValue(scope.context, path);
2466
2569
  if (result.ok && setupValuesEqual(result.value, expected)) {
2467
2570
  return {
2468
2571
  ...base,
2572
+ ...setupScopeEvidence(scope),
2469
2573
  ok: true,
2470
2574
  path,
2471
2575
  value: setupJsonValue(result.value),
@@ -2477,6 +2581,7 @@ async function executeSetupAction(action, ordinal) {
2477
2581
  }
2478
2582
  return {
2479
2583
  ...base,
2584
+ ...setupScopeEvidence(scope),
2480
2585
  path,
2481
2586
  reason: result?.ok ? "unexpected_value" : result?.reason || "path_not_found",
2482
2587
  missing_part: result?.missing_part || undefined,
@@ -2493,11 +2598,13 @@ async function executeSetupAction(action, ordinal) {
2493
2598
  const hasExpected = expected !== undefined;
2494
2599
  if (!path) return { ...base, path, reason: "missing_path" };
2495
2600
  if (!hasExpected && minValue === undefined && maxValue === undefined) return { ...base, path, reason: "missing_number_expectation" };
2601
+ const scope = await setupActionScope(action, timeout);
2602
+ if (!scope.ok) return setupScopeFailure(base, scope);
2496
2603
  const startedAt = Date.now();
2497
2604
  let result = null;
2498
2605
  let lastReason = "path_not_found";
2499
2606
  while (Date.now() - startedAt <= timeout) {
2500
- result = await setupReadWindowValue(path);
2607
+ result = await setupReadWindowValue(scope.context, path);
2501
2608
  if (result.ok) {
2502
2609
  const actual = setupFiniteNumber(result.value);
2503
2610
  if (actual === undefined) {
@@ -2511,6 +2618,7 @@ async function executeSetupAction(action, ordinal) {
2511
2618
  } else {
2512
2619
  return {
2513
2620
  ...base,
2621
+ ...setupScopeEvidence(scope),
2514
2622
  ok: true,
2515
2623
  path,
2516
2624
  value: actual,
@@ -2527,6 +2635,7 @@ async function executeSetupAction(action, ordinal) {
2527
2635
  }
2528
2636
  return {
2529
2637
  ...base,
2638
+ ...setupScopeEvidence(scope),
2530
2639
  path,
2531
2640
  reason: lastReason,
2532
2641
  missing_part: result?.missing_part || undefined,
@@ -2538,7 +2647,9 @@ async function executeSetupAction(action, ordinal) {
2538
2647
  };
2539
2648
  }
2540
2649
  if (type === "click") {
2541
- const locator = page.locator(action.selector);
2650
+ const scope = await setupActionScope(action, timeout);
2651
+ if (!scope.ok) return setupScopeFailure(base, scope);
2652
+ const locator = scope.context.locator(action.selector);
2542
2653
  const count = await locator.count();
2543
2654
  if (!count) return { ...base, reason: "selector_not_found", count };
2544
2655
  let targetIndex = Number.isInteger(action.index) ? action.index : 0;
@@ -2570,33 +2681,39 @@ async function executeSetupAction(action, ordinal) {
2570
2681
  ? { timeout, noWaitAfter: true, force: true }
2571
2682
  : { timeout, noWaitAfter: true };
2572
2683
  await locator.nth(targetIndex).click(clickOptions);
2573
- return { ...base, ok: true, count, target_index: targetIndex, text: matchedText, force: action.force === true || undefined };
2684
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, text: matchedText, force: action.force === true || undefined };
2574
2685
  }
2575
2686
  if (type === "fill" || type === "set_input_value") {
2576
- const locator = page.locator(action.selector);
2687
+ const scope = await setupActionScope(action, timeout);
2688
+ if (!scope.ok) return setupScopeFailure(base, scope);
2689
+ const locator = scope.context.locator(action.selector);
2577
2690
  const count = await locator.count();
2578
2691
  if (!count) return { ...base, reason: "selector_not_found", count };
2579
2692
  const targetIndex = Number.isInteger(action.index) ? action.index : 0;
2580
2693
  if (targetIndex < 0 || targetIndex >= count) return { ...base, reason: "index_out_of_range", count, target_index: targetIndex };
2581
2694
  const value = setupActionValue(action);
2582
2695
  await locator.nth(targetIndex).fill(value, { timeout });
2583
- return { ...base, ok: true, count, target_index: targetIndex, value_length: value.length };
2696
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, value_length: value.length };
2584
2697
  }
2585
2698
  if (type === "assert_selector_count") {
2586
- const locator = page.locator(action.selector);
2699
+ const scope = await setupActionScope(action, timeout);
2700
+ if (!scope.ok) return setupScopeFailure(base, scope);
2701
+ const locator = scope.context.locator(action.selector);
2587
2702
  const expectedCount = setupNumber(action.expected_count, -1);
2588
2703
  if (!Number.isInteger(expectedCount) || expectedCount < 0) return { ...base, reason: "invalid_expected_count", expected_count: action.expected_count };
2589
2704
  const startedAt = Date.now();
2590
2705
  let count = 0;
2591
2706
  while (Date.now() - startedAt <= timeout) {
2592
2707
  count = await locator.count().catch(() => 0);
2593
- if (count === expectedCount) return { ...base, ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
2708
+ if (count === expectedCount) return { ...base, ...setupScopeEvidence(scope), ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
2594
2709
  await page.waitForTimeout(100);
2595
2710
  }
2596
- return { ...base, reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
2711
+ return { ...base, ...setupScopeEvidence(scope), reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
2597
2712
  }
2598
2713
  if (type === "wait_for_text") {
2599
- const locator = page.locator(action.selector);
2714
+ const scope = await setupActionScope(action, timeout);
2715
+ if (!scope.ok) return setupScopeFailure(base, scope);
2716
+ const locator = scope.context.locator(action.selector);
2600
2717
  const startedAt = Date.now();
2601
2718
  let lastText = "";
2602
2719
  while (Date.now() - startedAt <= timeout) {
@@ -2605,15 +2722,17 @@ async function executeSetupAction(action, ordinal) {
2605
2722
  const text = await setupLocatorText(locator, index);
2606
2723
  lastText = text || lastText;
2607
2724
  if (setupTextMatches(text, action)) {
2608
- return { ...base, ok: true, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2725
+ return { ...base, ...setupScopeEvidence(scope), ok: true, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2609
2726
  }
2610
2727
  }
2611
2728
  await page.waitForTimeout(100);
2612
2729
  }
2613
- return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2730
+ return { ...base, ...setupScopeEvidence(scope), reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2614
2731
  }
2615
2732
  if (type === "assert_text_visible" || type === "assert_text_absent") {
2616
- const locator = page.locator(action.selector);
2733
+ const scope = await setupActionScope(action, timeout);
2734
+ if (!scope.ok) return setupScopeFailure(base, scope);
2735
+ const locator = scope.context.locator(action.selector);
2617
2736
  const startedAt = Date.now();
2618
2737
  let lastText = "";
2619
2738
  let matchedText = "";
@@ -2632,7 +2751,7 @@ async function executeSetupAction(action, ordinal) {
2632
2751
  if (type === "assert_text_visible") {
2633
2752
  const visible = await setupLocatorVisible(locator, index);
2634
2753
  if (visible) {
2635
- return { ...base, ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2754
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2636
2755
  }
2637
2756
  hiddenMatch = true;
2638
2757
  break;
@@ -2641,17 +2760,17 @@ async function executeSetupAction(action, ordinal) {
2641
2760
  }
2642
2761
  }
2643
2762
  if (type === "assert_text_absent" && !matched) {
2644
- return { ...base, ok: true, count, timeout_ms: timeout };
2763
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, timeout_ms: timeout };
2645
2764
  }
2646
2765
  await page.waitForTimeout(100);
2647
2766
  }
2648
2767
  if (type === "assert_text_visible") {
2649
2768
  if (hiddenMatch) {
2650
- return { ...base, reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
2769
+ return { ...base, ...setupScopeEvidence(scope), reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
2651
2770
  }
2652
- return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2771
+ return { ...base, ...setupScopeEvidence(scope), reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2653
2772
  }
2654
- return { ...base, reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
2773
+ return { ...base, ...setupScopeEvidence(scope), reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
2655
2774
  }
2656
2775
  return { ...base, reason: "unsupported_action" };
2657
2776
  } catch (error) {