@nuanu-ai/agentbrowse 0.2.30 → 0.2.32

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.
Files changed (29) hide show
  1. package/dist/commands/act.js +1 -1
  2. package/dist/commands/extract-snapshot-sanitizer.d.ts +3 -0
  3. package/dist/commands/extract-snapshot-sanitizer.d.ts.map +1 -1
  4. package/dist/commands/extract-snapshot-sanitizer.js +33 -0
  5. package/dist/commands/extract-stagehand-executor.d.ts.map +1 -1
  6. package/dist/commands/extract-stagehand-executor.js +84 -20
  7. package/dist/commands/observe-inventory.d.ts +10 -0
  8. package/dist/commands/observe-inventory.d.ts.map +1 -1
  9. package/dist/commands/observe-inventory.js +388 -1
  10. package/dist/commands/observe.d.ts +1 -0
  11. package/dist/commands/observe.d.ts.map +1 -1
  12. package/dist/commands/observe.js +1 -1
  13. package/dist/commands/observe.test-harness.d.ts +1 -0
  14. package/dist/commands/observe.test-harness.d.ts.map +1 -1
  15. package/dist/commands/semantic-observe.d.ts +4 -1
  16. package/dist/commands/semantic-observe.d.ts.map +1 -1
  17. package/dist/commands/semantic-observe.js +187 -88
  18. package/dist/runtime-state.d.ts +47 -1
  19. package/dist/runtime-state.d.ts.map +1 -1
  20. package/dist/runtime-state.js +88 -2
  21. package/dist/secrets/protected-field-values.d.ts +2 -0
  22. package/dist/secrets/protected-field-values.d.ts.map +1 -1
  23. package/dist/secrets/protected-field-values.js +50 -17
  24. package/dist/secrets/protected-fill.d.ts.map +1 -1
  25. package/dist/secrets/protected-fill.js +6 -0
  26. package/dist/secrets/protected-value-adapters.d.ts +3 -0
  27. package/dist/secrets/protected-value-adapters.d.ts.map +1 -0
  28. package/dist/secrets/protected-value-adapters.js +39 -0
  29. package/package.json +1 -1
@@ -24,7 +24,7 @@ function ensureValue(action, value) {
24
24
  return value;
25
25
  throw new Error(`Act value is required for action: ${action}`);
26
26
  }
27
- const MUTABLE_FIELD_REBIND_RETRY_DELAYS_MS = [0, 25, 50];
27
+ const MUTABLE_FIELD_REBIND_RETRY_DELAYS_MS = [0, 25, 50, 100];
28
28
  function emitActPreflightFailure(params) {
29
29
  return outputContractFailure({
30
30
  error: params.error,
@@ -1,2 +1,5 @@
1
1
  export declare function sanitizeExtractSnapshot(snapshot: string): string;
2
+ export declare function budgetExtractSnapshot(snapshot: string, options?: {
3
+ scoped?: boolean;
4
+ }): string;
2
5
  //# sourceMappingURL=extract-snapshot-sanitizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract-snapshot-sanitizer.d.ts","sourceRoot":"","sources":["../../src/commands/extract-snapshot-sanitizer.ts"],"names":[],"mappings":"AAuEA,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQhE"}
1
+ {"version":3,"file":"extract-snapshot-sanitizer.d.ts","sourceRoot":"","sources":["../../src/commands/extract-snapshot-sanitizer.ts"],"names":[],"mappings":"AAyEA,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQhE;AA4BD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,MAAM,CAcR"}
@@ -1,6 +1,8 @@
1
1
  const SUSPICIOUS_LINE_LENGTH = 220;
2
2
  const SUSPICIOUS_TOKEN_LENGTH = 120;
3
3
  const MIN_MACHINE_PUNCTUATION_RATIO = 0.18;
4
+ const MAX_SCOPED_EXTRACT_LINES = 160;
5
+ const MAX_SCOPED_EXTRACT_CHARS = 12_000;
4
6
  const SCRIPT_MARKERS = [
5
7
  /\bwindow\./i,
6
8
  /\bdocument\./i,
@@ -63,3 +65,34 @@ export function sanitizeExtractSnapshot(snapshot) {
63
65
  });
64
66
  return sanitized.some((line) => line.trim()) ? sanitized.join('\n') : snapshot;
65
67
  }
68
+ function trimSnapshotToBudget(snapshot, options) {
69
+ const lines = snapshot.split(/\r?\n/).filter((line) => line.trim().length > 0);
70
+ const kept = [];
71
+ let totalChars = 0;
72
+ for (const line of lines) {
73
+ const nextChars = totalChars + line.length + (kept.length > 0 ? 1 : 0);
74
+ if (kept.length >= options.maxLines || nextChars > options.maxChars) {
75
+ break;
76
+ }
77
+ kept.push(line);
78
+ totalChars = nextChars;
79
+ }
80
+ if (kept.length === 0) {
81
+ return snapshot;
82
+ }
83
+ const trimmed = kept.join('\n');
84
+ return trimmed.length >= Math.floor(snapshot.length * 0.35) ? trimmed : snapshot;
85
+ }
86
+ export function budgetExtractSnapshot(snapshot, options = {}) {
87
+ if (!options.scoped) {
88
+ return snapshot;
89
+ }
90
+ const lines = snapshot.split(/\r?\n/);
91
+ if (lines.length <= MAX_SCOPED_EXTRACT_LINES && snapshot.length <= MAX_SCOPED_EXTRACT_CHARS) {
92
+ return snapshot;
93
+ }
94
+ return trimSnapshotToBudget(snapshot, {
95
+ maxLines: MAX_SCOPED_EXTRACT_LINES,
96
+ maxChars: MAX_SCOPED_EXTRACT_CHARS,
97
+ });
98
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"extract-stagehand-executor.d.ts","sourceRoot":"","sources":["../../src/commands/extract-stagehand-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAI5C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAA8B,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAI9F,MAAM,MAAM,yBAAyB,GAAG,iBAAiB,GAAG;IAC1D,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,mBAAmB,CAAC;CACjC,CAAC;AAiBF,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IAClD,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;IAClB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAsDrC"}
1
+ {"version":3,"file":"extract-stagehand-executor.d.ts","sourceRoot":"","sources":["../../src/commands/extract-stagehand-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAI5C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAA8B,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAK9F,MAAM,MAAM,yBAAyB,GAAG,iBAAiB,GAAG;IAC1D,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,mBAAmB,CAAC;CACjC,CAAC;AAiEF,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IAClD,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;IAClB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAwFrC"}
@@ -1,10 +1,41 @@
1
1
  import { extract as runExtract } from '@browserbasehq/stagehand/lib/inference.js';
2
2
  import { v3Logger } from '@browserbasehq/stagehand/lib/v3/logger.js';
3
3
  import { captureHybridSnapshot } from '@browserbasehq/stagehand/lib/v3/understudy/a11y/snapshot/index.js';
4
- import { incrementMetric } from '../runtime-state.js';
4
+ import { incrementMetric, recordLlmUsage, recordPayloadBudget } from '../runtime-state.js';
5
5
  import { stagehandRuntimeResolution } from '../runtime-resolution.js';
6
6
  import { withStagehand } from '../stagehand-runtime.js';
7
- import { sanitizeExtractSnapshot } from './extract-snapshot-sanitizer.js';
7
+ import { readLocatorOuterHtml } from './descriptor-validation.js';
8
+ import { budgetExtractSnapshot, sanitizeExtractSnapshot } from './extract-snapshot-sanitizer.js';
9
+ const SCOPED_EXTRACT_MIRROR_SELECTOR = '#agentbrowse-scope';
10
+ async function mirrorScopedSnapshotPage(page, selector) {
11
+ if (!selector || selector === SCOPED_EXTRACT_MIRROR_SELECTOR) {
12
+ return null;
13
+ }
14
+ const locator = page.locator(selector);
15
+ const first = locator.first();
16
+ const count = typeof first.count === 'function' ? await first.count().catch(() => 0) : 1;
17
+ if (count === 0) {
18
+ return null;
19
+ }
20
+ let scopedHtml = await readLocatorOuterHtml(first).catch(() => null);
21
+ if (!scopedHtml) {
22
+ scopedHtml = await first
23
+ .evaluate((element) => (element instanceof Element ? element.outerHTML : null))
24
+ .catch(() => null);
25
+ }
26
+ if (!scopedHtml) {
27
+ return null;
28
+ }
29
+ const scratchPage = await page.context().newPage();
30
+ await scratchPage.setContent(`<!doctype html><html lang="en"><body><div id="agentbrowse-scope">${scopedHtml}</div></body></html>`);
31
+ return {
32
+ page: scratchPage,
33
+ focusSelector: SCOPED_EXTRACT_MIRROR_SELECTOR,
34
+ cleanup: async () => {
35
+ await scratchPage.close().catch(() => undefined);
36
+ },
37
+ };
38
+ }
8
39
  export async function executeStagehandExtract(args) {
9
40
  const { session, instruction, schema, page, selector, degradationReason } = args;
10
41
  const data = await withStagehand(session, async (stagehand) => {
@@ -21,24 +52,57 @@ export async function executeStagehandExtract(args) {
21
52
  });
22
53
  }
23
54
  const extractPage = await resolvePage.call(internalStagehand, page);
24
- const focusSelector = selector?.replace(/^xpath=/i, '') ?? '';
25
- const snapshot = await captureHybridSnapshot(extractPage, {
26
- experimental: internalStagehand.experimental ?? false,
27
- focusSelector,
28
- });
29
- // Deferred decision: budget-aware narrowing stays separate from this change.
30
- const sanitizedTree = sanitizeExtractSnapshot(snapshot.combinedTree);
31
- const extractionResponse = await runExtract({
32
- instruction,
33
- domElements: sanitizedTree,
34
- schema: schema,
35
- llmClient: internalStagehand.llmClient,
36
- userProvidedInstructions: internalStagehand.opts?.systemPrompt ?? '',
37
- logger: v3Logger,
38
- logInferenceToFile: internalStagehand.logInferenceToFile ?? false,
39
- });
40
- const { metadata: _metadata, prompt_tokens: _promptTokens, completion_tokens: _completionTokens, reasoning_tokens: _reasoningTokens, cached_input_tokens: _cachedInputTokens, inference_time_ms: _inferenceTimeMs, ...result } = extractionResponse;
41
- return result;
55
+ let snapshotPage = extractPage;
56
+ let focusSelector = selector?.replace(/^xpath=/i, '') ?? '';
57
+ let cleanupMirroredSnapshot = null;
58
+ try {
59
+ if (selector) {
60
+ const mirroredSnapshot = await mirrorScopedSnapshotPage(page, selector);
61
+ if (mirroredSnapshot) {
62
+ cleanupMirroredSnapshot = mirroredSnapshot.cleanup;
63
+ snapshotPage = await resolvePage.call(internalStagehand, mirroredSnapshot.page);
64
+ focusSelector = mirroredSnapshot.focusSelector;
65
+ }
66
+ }
67
+ const snapshot = await captureHybridSnapshot(snapshotPage, {
68
+ experimental: internalStagehand.experimental ?? false,
69
+ focusSelector,
70
+ });
71
+ recordPayloadBudget(session, {
72
+ extractSnapshotLinesSeen: snapshot.combinedTree.split(/\r?\n/).length,
73
+ });
74
+ const sanitizedTree = sanitizeExtractSnapshot(snapshot.combinedTree);
75
+ const budgetedTree = budgetExtractSnapshot(sanitizedTree, {
76
+ scoped: Boolean(selector),
77
+ });
78
+ recordPayloadBudget(session, {
79
+ extractSnapshotLinesSent: budgetedTree.split(/\r?\n/).length,
80
+ });
81
+ const extractionResponse = await runExtract({
82
+ instruction,
83
+ domElements: budgetedTree,
84
+ schema: schema,
85
+ llmClient: internalStagehand.llmClient,
86
+ userProvidedInstructions: internalStagehand.opts?.systemPrompt ?? '',
87
+ logger: v3Logger,
88
+ logInferenceToFile: internalStagehand.logInferenceToFile ?? false,
89
+ });
90
+ const { metadata: _metadata, prompt_tokens: promptTokens, completion_tokens: completionTokens, reasoning_tokens: reasoningTokens, cached_input_tokens: cachedInputTokens, inference_time_ms: _inferenceTimeMs, ...result } = extractionResponse;
91
+ recordLlmUsage(session, {
92
+ purpose: 'browse.extract',
93
+ inputChars: budgetedTree.length,
94
+ promptTokens: typeof promptTokens === 'number' ? promptTokens : undefined,
95
+ completionTokens: typeof completionTokens === 'number' ? completionTokens : undefined,
96
+ cachedInputTokens: typeof cachedInputTokens === 'number' ? cachedInputTokens : undefined,
97
+ reasoningTokens: typeof reasoningTokens === 'number' ? reasoningTokens : undefined,
98
+ });
99
+ return result;
100
+ }
101
+ finally {
102
+ if (cleanupMirroredSnapshot) {
103
+ await cleanupMirroredSnapshot().catch(() => undefined);
104
+ }
105
+ }
42
106
  });
43
107
  return {
44
108
  resolvedBy: 'stagehand-extract',
@@ -93,7 +93,16 @@ type StructuredCellVariantEvidence = {
93
93
  hasSeatColumnAttribute?: boolean;
94
94
  hasDateMetadata?: boolean;
95
95
  };
96
+ export type DirectionalControlFallbackPosition = 'leading' | 'trailing' | 'upper' | 'lower';
97
+ export type DirectionalControlFallbackEvidence = {
98
+ kind?: string;
99
+ role?: string;
100
+ groupLabel?: string;
101
+ anchorText?: string;
102
+ position?: DirectionalControlFallbackPosition;
103
+ };
96
104
  export declare function inferStructuredCellVariantFromEvidence(evidence: StructuredCellVariantEvidence): 'date-cell' | 'seat-cell' | 'grid-cell' | undefined;
105
+ export declare function inferDirectionalControlFallbackFromEvidence(evidence: DirectionalControlFallbackEvidence): string | undefined;
97
106
  export declare function readStagehandLocatorSnapshot(locator: Locator): Promise<StagehandLocatorSnapshot>;
98
107
  declare function readStagehandDomFactsInBrowser(element: Element): StagehandDomFacts | null;
99
108
  declare function readStagehandDomFacts(locator: Locator): Promise<StagehandDomFacts | null>;
@@ -114,6 +123,7 @@ export declare function collectDomTargets(page: {
114
123
  export declare const __testDomTargetCollection: {
115
124
  collectDomTargetsFromDocument: typeof collectDomTargetsFromDocument;
116
125
  inferStructuredCellVariantFromEvidence: typeof inferStructuredCellVariantFromEvidence;
126
+ inferDirectionalControlFallbackFromEvidence: typeof inferDirectionalControlFallbackFromEvidence;
117
127
  locatorDomSignatureScript: string;
118
128
  };
119
129
  export declare const __testStagehandDescriptor: {
@@ -1 +1 @@
1
- {"version":3,"file":"observe-inventory.d.ts","sourceRoot":"","sources":["../../src/commands/observe-inventory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"observe-inventory.d.ts","sourceRoot":"","sources":["../../src/commands/observe-inventory.ts"],"names":[],"mappings":"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"}
@@ -31,7 +31,78 @@ export function inferStructuredCellVariantFromEvidence(evidence) {
31
31
  }
32
32
  return undefined;
33
33
  }
34
+ export function inferDirectionalControlFallbackFromEvidence(evidence) {
35
+ const normalize = (value) => {
36
+ const normalized = (value ?? '').replace(/\s+/g, ' ').trim();
37
+ return normalized || undefined;
38
+ };
39
+ const normalizedKind = normalize(evidence.kind)?.toLowerCase();
40
+ const normalizedRole = normalize(evidence.role)?.toLowerCase();
41
+ const buttonLike = normalizedKind === 'button' ||
42
+ normalizedRole === 'button' ||
43
+ (normalizedKind === 'input' && normalizedRole === 'button');
44
+ if (!buttonLike) {
45
+ return undefined;
46
+ }
47
+ const position = normalize(evidence.position)?.toLowerCase();
48
+ const anchorText = normalize(evidence.anchorText);
49
+ const groupLabel = normalize(evidence.groupLabel);
50
+ if (!position || !anchorText) {
51
+ return undefined;
52
+ }
53
+ const monthYearLike = /\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?|январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\b/i.test(anchorText);
54
+ if (monthYearLike && (position === 'leading' || position === 'trailing')) {
55
+ return position === 'leading' ? 'Previous month' : 'Next month';
56
+ }
57
+ const counterLike = /^\d{1,3}$/.test(anchorText);
58
+ if (counterLike && groupLabel && (position === 'leading' || position === 'trailing')) {
59
+ const subject = groupLabel.toLowerCase();
60
+ return position === 'leading' ? `Decrease ${subject}` : `Increase ${subject}`;
61
+ }
62
+ return undefined;
63
+ }
34
64
  const INFER_STRUCTURED_CELL_VARIANT_HELPER_SCRIPT = `const inferStructuredCellVariantFromEvidence = ${inferStructuredCellVariantFromEvidence.toString()};`;
65
+ const INFER_DIRECTIONAL_CONTROL_FALLBACK_HELPER_SCRIPT = String.raw `
66
+ const inferDirectionalControlFallbackFromEvidence = (evidence) => {
67
+ const normalizeDirectionalControlFallbackValue = (value) => {
68
+ const normalized = (value || '').replace(/\s+/g, ' ').trim();
69
+ return normalized || undefined;
70
+ };
71
+
72
+ const normalizedKind = normalizeDirectionalControlFallbackValue(evidence?.kind)?.toLowerCase();
73
+ const normalizedRole = normalizeDirectionalControlFallbackValue(evidence?.role)?.toLowerCase();
74
+ const buttonLike =
75
+ normalizedKind === 'button' ||
76
+ normalizedRole === 'button' ||
77
+ (normalizedKind === 'input' && normalizedRole === 'button');
78
+ if (!buttonLike) {
79
+ return undefined;
80
+ }
81
+
82
+ const position = normalizeDirectionalControlFallbackValue(evidence?.position)?.toLowerCase();
83
+ const anchorText = normalizeDirectionalControlFallbackValue(evidence?.anchorText);
84
+ const groupLabel = normalizeDirectionalControlFallbackValue(evidence?.groupLabel);
85
+ if (!position || !anchorText) {
86
+ return undefined;
87
+ }
88
+
89
+ const monthYearLike =
90
+ /\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?|январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\b/i.test(
91
+ anchorText
92
+ );
93
+ if (monthYearLike && (position === 'leading' || position === 'trailing')) {
94
+ return position === 'leading' ? 'Previous month' : 'Next month';
95
+ }
96
+
97
+ const counterLike = /^\d{1,3}$/.test(anchorText);
98
+ if (counterLike && groupLabel && (position === 'leading' || position === 'trailing')) {
99
+ const subject = groupLabel.toLowerCase();
100
+ return position === 'leading' ? 'Decrease ' + subject : 'Increase ' + subject;
101
+ }
102
+
103
+ return undefined;
104
+ };
105
+ `;
35
106
  function sleep(ms) {
36
107
  return new Promise((resolve) => setTimeout(resolve, ms));
37
108
  }
@@ -352,6 +423,7 @@ async function collectDomTargetsFromDocument(context, options) {
352
423
  ${TRANSPARENT_ACTIONABLE_CONTROL_HELPER_SCRIPT}
353
424
  ${OBSERVE_DOM_LABEL_CONTRACT_HELPER_SCRIPT}
354
425
  ${INFER_STRUCTURED_CELL_VARIANT_HELPER_SCRIPT}
426
+ ${INFER_DIRECTIONAL_CONTROL_FALLBACK_HELPER_SCRIPT}
355
427
 
356
428
  const normalizeDescriptorText = (value) => observedNormalizeDescriptorText(value);
357
429
 
@@ -1144,6 +1216,315 @@ async function collectDomTargetsFromDocument(context, options) {
1144
1216
  return composedClosest(element, itemSelector) || undefined;
1145
1217
  };
1146
1218
 
1219
+ const directionalControlSelector =
1220
+ 'button, [role="button"], input[type="button"], input[type="submit"], input[type="reset"]';
1221
+ const weakDirectionalTextRe = /^[+\-<>‹›«»←→↑↓]+$/;
1222
+
1223
+ const isButtonLikeDirectionalElement = (element) => {
1224
+ if (!isHTMLElementNode(element)) {
1225
+ return false;
1226
+ }
1227
+
1228
+ const tag = element.tagName.toLowerCase();
1229
+ const role = (element.getAttribute('role') || '').trim().toLowerCase();
1230
+ if (tag === 'button') {
1231
+ return true;
1232
+ }
1233
+ if (tag === 'input') {
1234
+ return isButtonLikeInput(element);
1235
+ }
1236
+ return role === 'button';
1237
+ };
1238
+
1239
+ const isWeakDirectionalText = (value) => {
1240
+ const normalized = normalizeDescriptorText(value || '');
1241
+ return Boolean(normalized) && weakDirectionalTextRe.test(normalized);
1242
+ };
1243
+
1244
+ const directionalButtonsOf = (scope) => {
1245
+ if (!isHTMLElementNode(scope)) {
1246
+ return [];
1247
+ }
1248
+
1249
+ return Array.from(scope.querySelectorAll(directionalControlSelector))
1250
+ .filter((candidate) => isHTMLElementNode(candidate))
1251
+ .filter((candidate) => isVisible(candidate))
1252
+ .filter((candidate) => isButtonLikeDirectionalElement(candidate))
1253
+ .slice(0, 6);
1254
+ };
1255
+
1256
+ const directionalChildTextCandidatesOf = (scope, exclude = []) => {
1257
+ if (!isHTMLElementNode(scope)) {
1258
+ return [];
1259
+ }
1260
+
1261
+ const excluded = exclude.filter((candidate) => isHTMLElementNode(candidate));
1262
+ const candidates = [];
1263
+ for (const child of Array.from(scope.children).slice(0, 12)) {
1264
+ if (!isHTMLElementNode(child) || !isVisible(child)) {
1265
+ continue;
1266
+ }
1267
+ if (
1268
+ excluded.some(
1269
+ (candidate) =>
1270
+ child === candidate || child.contains(candidate) || candidate.contains(child)
1271
+ )
1272
+ ) {
1273
+ continue;
1274
+ }
1275
+ if (isButtonLikeDirectionalElement(child)) {
1276
+ continue;
1277
+ }
1278
+ if (directionalButtonsOf(child).length > 0) {
1279
+ continue;
1280
+ }
1281
+
1282
+ const text = normalizeDescriptorText(textOf(child, { container: true }) || '');
1283
+ if (!text || text.length > 48) {
1284
+ continue;
1285
+ }
1286
+
1287
+ candidates.push({
1288
+ element: child,
1289
+ text,
1290
+ rect: child.getBoundingClientRect(),
1291
+ });
1292
+ }
1293
+
1294
+ return candidates;
1295
+ };
1296
+
1297
+ const directionalAxisOf = (buttons) => {
1298
+ if (buttons.length !== 2) {
1299
+ return undefined;
1300
+ }
1301
+
1302
+ const firstRect = buttons[0].getBoundingClientRect();
1303
+ const secondRect = buttons[1].getBoundingClientRect();
1304
+ const deltaX = Math.abs(
1305
+ firstRect.left + firstRect.width / 2 - (secondRect.left + secondRect.width / 2)
1306
+ );
1307
+ const deltaY = Math.abs(
1308
+ firstRect.top + firstRect.height / 2 - (secondRect.top + secondRect.height / 2)
1309
+ );
1310
+ return deltaX >= deltaY ? 'horizontal' : 'vertical';
1311
+ };
1312
+
1313
+ const orderedDirectionalButtonsOf = (buttons, axis) => {
1314
+ return [...buttons].sort((left, right) => {
1315
+ const leftRect = left.getBoundingClientRect();
1316
+ const rightRect = right.getBoundingClientRect();
1317
+ return axis === 'vertical'
1318
+ ? leftRect.top - rightRect.top
1319
+ : leftRect.left - rightRect.left;
1320
+ });
1321
+ };
1322
+
1323
+ const directionalPositionOf = (element, orderedButtons, axis) => {
1324
+ if (orderedButtons.length !== 2) {
1325
+ return undefined;
1326
+ }
1327
+
1328
+ if (orderedButtons[0] === element) {
1329
+ return axis === 'vertical' ? 'upper' : 'leading';
1330
+ }
1331
+ if (orderedButtons[1] === element) {
1332
+ return axis === 'vertical' ? 'lower' : 'trailing';
1333
+ }
1334
+ return undefined;
1335
+ };
1336
+
1337
+ const directionalAnchorCandidateOf = (cluster, orderedButtons, axis) => {
1338
+ if (!isHTMLElementNode(cluster) || orderedButtons.length !== 2) {
1339
+ return undefined;
1340
+ }
1341
+
1342
+ const firstRect = orderedButtons[0].getBoundingClientRect();
1343
+ const secondRect = orderedButtons[1].getBoundingClientRect();
1344
+ const midpointX =
1345
+ (firstRect.left + firstRect.width / 2 + (secondRect.left + secondRect.width / 2)) / 2;
1346
+ const midpointY =
1347
+ (firstRect.top + firstRect.height / 2 + (secondRect.top + secondRect.height / 2)) / 2;
1348
+
1349
+ const candidates = directionalChildTextCandidatesOf(cluster, orderedButtons)
1350
+ .filter((candidate) => {
1351
+ const centerX = candidate.rect.left + candidate.rect.width / 2;
1352
+ const centerY = candidate.rect.top + candidate.rect.height / 2;
1353
+ if (axis === 'vertical') {
1354
+ return (
1355
+ centerY >= Math.min(midpointY, secondRect.top + secondRect.height / 2) - 40 &&
1356
+ centerY <= Math.max(midpointY, firstRect.top + firstRect.height / 2) + 40 &&
1357
+ Math.abs(centerX - midpointX) <= Math.max(40, firstRect.width * 1.5)
1358
+ );
1359
+ }
1360
+
1361
+ return (
1362
+ centerX >= Math.min(firstRect.left + firstRect.width / 2, midpointX) - 40 &&
1363
+ centerX <= Math.max(secondRect.left + secondRect.width / 2, midpointX) + 40 &&
1364
+ Math.abs(centerY - midpointY) <= Math.max(32, firstRect.height * 1.5)
1365
+ );
1366
+ })
1367
+ .sort((left, right) => {
1368
+ const leftCenterX = left.rect.left + left.rect.width / 2;
1369
+ const leftCenterY = left.rect.top + left.rect.height / 2;
1370
+ const rightCenterX = right.rect.left + right.rect.width / 2;
1371
+ const rightCenterY = right.rect.top + right.rect.height / 2;
1372
+ const leftDistance = Math.hypot(leftCenterX - midpointX, leftCenterY - midpointY);
1373
+ const rightDistance = Math.hypot(rightCenterX - midpointX, rightCenterY - midpointY);
1374
+ return leftDistance - rightDistance;
1375
+ });
1376
+
1377
+ return candidates[0];
1378
+ };
1379
+
1380
+ const stepperGroupLabelOf = (element, cluster, axis, anchorText) => {
1381
+ const scopes = [];
1382
+ const parent = composedParentElement(cluster);
1383
+ const grandParent = composedParentElement(parent);
1384
+ const item = itemOf(element);
1385
+
1386
+ for (const scope of [parent, item, grandParent]) {
1387
+ if (
1388
+ isHTMLElementNode(scope) &&
1389
+ scope !== cluster &&
1390
+ !scopes.includes(scope)
1391
+ ) {
1392
+ scopes.push(scope);
1393
+ }
1394
+ }
1395
+
1396
+ const anchorMonthLike = Boolean(
1397
+ inferDirectionalControlFallbackFromEvidence({
1398
+ kind: 'button',
1399
+ anchorText,
1400
+ position: 'leading',
1401
+ })
1402
+ );
1403
+
1404
+ for (const scope of scopes) {
1405
+ const scopeRect = cluster.getBoundingClientRect();
1406
+ const clusterCenterX = scopeRect.left + scopeRect.width / 2;
1407
+ const clusterCenterY = scopeRect.top + scopeRect.height / 2;
1408
+ const ranked = directionalChildTextCandidatesOf(scope, [cluster])
1409
+ .filter((candidate) => candidate.text !== anchorText)
1410
+ .filter((candidate) => !/^\d{1,3}$/.test(candidate.text))
1411
+ .filter(
1412
+ (candidate) =>
1413
+ !Boolean(
1414
+ inferDirectionalControlFallbackFromEvidence({
1415
+ kind: 'button',
1416
+ anchorText: candidate.text,
1417
+ position: 'leading',
1418
+ })
1419
+ ) || !anchorMonthLike
1420
+ )
1421
+ .sort((left, right) => {
1422
+ const leftRect = left.rect;
1423
+ const rightRect = right.rect;
1424
+ const leftCenterX = leftRect.left + leftRect.width / 2;
1425
+ const leftCenterY = leftRect.top + leftRect.height / 2;
1426
+ const rightCenterX = rightRect.left + rightRect.width / 2;
1427
+ const rightCenterY = rightRect.top + rightRect.height / 2;
1428
+ const leftBias =
1429
+ axis === 'vertical'
1430
+ ? leftRect.bottom <= scopeRect.top + 16
1431
+ ? 0
1432
+ : leftRect.right <= scopeRect.left + 16
1433
+ ? 1
1434
+ : 2
1435
+ : leftRect.right <= scopeRect.left + 16
1436
+ ? 0
1437
+ : leftRect.bottom <= scopeRect.top + 16
1438
+ ? 1
1439
+ : 2;
1440
+ const rightBias =
1441
+ axis === 'vertical'
1442
+ ? rightRect.bottom <= scopeRect.top + 16
1443
+ ? 0
1444
+ : rightRect.right <= scopeRect.left + 16
1445
+ ? 1
1446
+ : 2
1447
+ : rightRect.right <= scopeRect.left + 16
1448
+ ? 0
1449
+ : rightRect.bottom <= scopeRect.top + 16
1450
+ ? 1
1451
+ : 2;
1452
+ if (leftBias !== rightBias) {
1453
+ return leftBias - rightBias;
1454
+ }
1455
+ const leftDistance = Math.hypot(leftCenterX - clusterCenterX, leftCenterY - clusterCenterY);
1456
+ const rightDistance = Math.hypot(rightCenterX - clusterCenterX, rightCenterY - clusterCenterY);
1457
+ if (leftDistance !== rightDistance) {
1458
+ return leftDistance - rightDistance;
1459
+ }
1460
+ return left.text.length - right.text.length;
1461
+ });
1462
+
1463
+ const subject = ranked[0]?.text;
1464
+ if (subject) {
1465
+ return subject;
1466
+ }
1467
+ }
1468
+
1469
+ return undefined;
1470
+ };
1471
+
1472
+ const directionalControlFallbackLabelOf = (element, directFallbackLabel) => {
1473
+ if (!isButtonLikeDirectionalElement(element)) {
1474
+ return undefined;
1475
+ }
1476
+ if (directFallbackLabel && !isWeakDirectionalText(directFallbackLabel)) {
1477
+ return undefined;
1478
+ }
1479
+
1480
+ let current = composedParentElement(element);
1481
+ let depth = 0;
1482
+ while (current && depth < 4) {
1483
+ if (!isHTMLElementNode(current) || !isVisible(current)) {
1484
+ current = composedParentElement(current);
1485
+ depth += 1;
1486
+ continue;
1487
+ }
1488
+
1489
+ const buttons = directionalButtonsOf(current);
1490
+ if (buttons.length !== 2 || !buttons.includes(element)) {
1491
+ current = composedParentElement(current);
1492
+ depth += 1;
1493
+ continue;
1494
+ }
1495
+
1496
+ const axis = directionalAxisOf(buttons);
1497
+ if (!axis) {
1498
+ return undefined;
1499
+ }
1500
+ const orderedButtons = orderedDirectionalButtonsOf(buttons, axis);
1501
+ const position = directionalPositionOf(element, orderedButtons, axis);
1502
+ const anchorCandidate = directionalAnchorCandidateOf(current, orderedButtons, axis);
1503
+ const anchorText = anchorCandidate?.text;
1504
+ if (!position || !anchorText) {
1505
+ return undefined;
1506
+ }
1507
+
1508
+ const groupLabel = /^\d{1,3}$/.test(anchorText)
1509
+ ? stepperGroupLabelOf(element, current, axis, anchorText)
1510
+ : undefined;
1511
+ const fallbackLabel = inferDirectionalControlFallbackFromEvidence({
1512
+ kind: element.tagName.toLowerCase(),
1513
+ role: element.getAttribute('role')?.trim() || undefined,
1514
+ groupLabel,
1515
+ anchorText,
1516
+ position,
1517
+ });
1518
+ if (fallbackLabel) {
1519
+ return fallbackLabel;
1520
+ }
1521
+
1522
+ return undefined;
1523
+ }
1524
+
1525
+ return undefined;
1526
+ };
1527
+
1147
1528
  const normalizeText = (value) => (value || '').replace(/\s+/g, ' ').trim().toLowerCase();
1148
1529
 
1149
1530
  const VALIDATION_CLASS_RE = /\b(?:error|invalid|warning|danger|alert|failed)\b/i;
@@ -2192,7 +2573,12 @@ async function collectDomTargetsFromDocument(context, options) {
2192
2573
  (isHTMLElementNode(overlaySurface) ? overlaySurface : localSurface || selfSurface);
2193
2574
  const surfaceSelectors = surfaceSelectorsOf(element, localSurface || selfSurface);
2194
2575
  const structure = visualSeatGrid?.structure || inferStructuredCell(element, surface);
2195
- const fallbackLabel = explicitLabelOf(element) || looseFieldLabelOf(element);
2576
+ const directFallbackLabel = explicitLabelOf(element) || looseFieldLabelOf(element);
2577
+ const directionalFallbackLabel = directionalControlFallbackLabelOf(
2578
+ element,
2579
+ directFallbackLabel
2580
+ );
2581
+ const fallbackLabel = directionalFallbackLabel || directFallbackLabel;
2196
2582
  const currentValue = popupCurrentValueOf(element);
2197
2583
  const role = inferRole(element);
2198
2584
  const surfaceKind = visualSeatGrid?.surfaceKind || surfaceKindOf(surface);
@@ -2681,6 +3067,7 @@ export async function collectDomTargets(page, options) {
2681
3067
  export const __testDomTargetCollection = {
2682
3068
  collectDomTargetsFromDocument,
2683
3069
  inferStructuredCellVariantFromEvidence,
3070
+ inferDirectionalControlFallbackFromEvidence,
2684
3071
  locatorDomSignatureScript: LOCATOR_DOM_SIGNATURE_SCRIPT,
2685
3072
  };
2686
3073
  export const __testStagehandDescriptor = {