@nuanu-ai/agentbrowse 0.2.36 → 0.2.38

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.
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { AgentpayStagehandLlmClient } from '../agentpay-stagehand-llm.js';
3
3
  import { resolveAgentpayGatewayConfig } from '../agentpay-gateway.js';
4
4
  import { recordLlmUsage, recordPayloadBudget } from '../runtime-state.js';
5
+ import { analyzeSemanticObserveText, buildSemanticObserveBm25CorpusStats, buildSemanticObserveLexicalDocument, normalizeSemanticObserveText, scoreSemanticObserveBm25, } from './semantic-observe-lexical.js';
5
6
  const rerankSchema = z.object({
6
7
  matches: z
7
8
  .array(z.object({
@@ -17,6 +18,31 @@ const SCOPE_BUCKET_RESERVE_LIMIT = 24;
17
18
  const SCOPE_BUCKET_RESERVE_PER_BUCKET = 2;
18
19
  const IFRAME_BUCKET_RESERVE_LIMIT = 24;
19
20
  const IFRAME_BUCKET_RESERVE_PER_BUCKET = 2;
21
+ const RETRIEVAL_FIELD_WEIGHTS = {
22
+ entityLabel: 5.5,
23
+ representativeLabel: 5,
24
+ representativeLabels: 4,
25
+ surfaceLabel: 2.25,
26
+ placeholder: 2.5,
27
+ title: 1.25,
28
+ itemLabel: 2.5,
29
+ itemText: 1.5,
30
+ groupLabel: 1.75,
31
+ groupText: 1,
32
+ containerLabel: 1.5,
33
+ containerText: 0.9,
34
+ landmarkLabel: 1.25,
35
+ landmarkText: 0.75,
36
+ hintText: 1.5,
37
+ kind: 0.6,
38
+ role: 0.6,
39
+ surfaceKind: 0.75,
40
+ controlFamily: 0.9,
41
+ acceptancePolicy: 1,
42
+ allowedActions: 0.9,
43
+ structure: 1.25,
44
+ latentIntent: 1.2,
45
+ };
20
46
  const HIGH_SIGNAL_SCOPE_KINDS = new Set([
21
47
  'dialog',
22
48
  'listbox',
@@ -29,27 +55,6 @@ const HIGH_SIGNAL_SCOPE_KINDS = new Set([
29
55
  'card',
30
56
  'form',
31
57
  ]);
32
- const GOAL_TEXT_STOPWORDS = new Set([
33
- 'a',
34
- 'an',
35
- 'and',
36
- 'at',
37
- 'for',
38
- 'from',
39
- 'in',
40
- 'of',
41
- 'on',
42
- 'or',
43
- 'the',
44
- 'to',
45
- 'with',
46
- 'в',
47
- 'для',
48
- 'и',
49
- 'или',
50
- 'на',
51
- 'найти',
52
- ]);
53
58
  function isFieldLikeTarget(target) {
54
59
  const kind = (target.kind ?? '').trim().toLowerCase();
55
60
  const role = (target.role ?? '').trim().toLowerCase();
@@ -73,16 +78,6 @@ function isActionLikeTargetCandidate(target) {
73
78
  kind === 'link' ||
74
79
  role === 'link');
75
80
  }
76
- function normalizeRetrievalText(value) {
77
- const normalized = value?.replace(/\s+/g, ' ').trim().toLowerCase();
78
- return normalized ? normalized : undefined;
79
- }
80
- function tokenizeRetrievalText(value) {
81
- if (!value) {
82
- return [];
83
- }
84
- return (value.match(/[\p{L}\p{N}]+/gu) ?? []).map((token) => token.toLowerCase());
85
- }
86
81
  function semanticFormContextKey(context) {
87
82
  for (const node of [context?.landmark, context?.container, context?.group, context?.item]) {
88
83
  const kind = (node?.kind ?? '').trim().toLowerCase();
@@ -140,6 +135,13 @@ function isPrimaryFormControlTarget(target) {
140
135
  acceptancePolicy === 'disclosure' &&
141
136
  (kind === 'button' || role === 'button'));
142
137
  }
138
+ function shouldPreserveStandaloneFormActionEntity(target) {
139
+ return (Boolean(formBucketKey(target)) &&
140
+ !isScopeLikeCandidate(target) &&
141
+ !isFieldLikeTarget(target) &&
142
+ isActionLikeTargetCandidate(target) &&
143
+ isPrimaryFormControlTarget(target));
144
+ }
143
145
  function primaryFormTargetPriority(target) {
144
146
  const acceptancePolicy = (target.acceptancePolicy ?? '').trim().toLowerCase();
145
147
  if (isFieldLikeTarget(target)) {
@@ -178,11 +180,11 @@ function contentItemBucketKey(target) {
178
180
  if (surfaceIdentity) {
179
181
  return `surface:${surfaceIdentity}`;
180
182
  }
181
- const itemKey = normalizeRetrievalText(target.context?.item?.label ?? target.context?.item?.text);
183
+ const itemKey = normalizeSemanticObserveText(target.context?.item?.label ?? target.context?.item?.text);
182
184
  if (itemKey) {
183
185
  return `item:${itemKey}`;
184
186
  }
185
- const containerKey = normalizeRetrievalText(target.context?.container?.label ?? target.context?.container?.text);
187
+ const containerKey = normalizeSemanticObserveText(target.context?.container?.label ?? target.context?.container?.text);
186
188
  if (containerKey) {
187
189
  return `container:${containerKey}`;
188
190
  }
@@ -205,7 +207,7 @@ function scopeCandidatePriority(target) {
205
207
  return 4;
206
208
  }
207
209
  function representativeCandidateScore(target) {
208
- const normalizedLabel = normalizeRetrievalText(target.label);
210
+ const normalizedLabel = normalizeSemanticObserveText(target.label);
209
211
  const kind = (target.kind ?? '').trim().toLowerCase();
210
212
  const role = (target.role ?? '').trim().toLowerCase();
211
213
  let score = (target.surfacePriority ?? 0) * 10;
@@ -295,26 +297,131 @@ function collectRepresentativeLabels(targets) {
295
297
  }
296
298
  return labels;
297
299
  }
298
- function buildEntitySearchText(entityLabel, representative, representativeLabels) {
300
+ function structureLexicalValue(structure) {
301
+ if (!structure) {
302
+ return undefined;
303
+ }
299
304
  return [
300
- entityLabel,
301
- representative.label,
302
- ...representativeLabels,
303
- representative.surfaceLabel,
304
- representative.context?.item?.label,
305
- representative.context?.item?.text,
306
- representative.context?.group?.label,
307
- representative.context?.group?.text,
308
- representative.context?.container?.label,
309
- representative.context?.container?.text,
310
- representative.context?.landmark?.label,
311
- representative.context?.landmark?.text,
312
- representative.context?.hintText,
305
+ structure.family,
306
+ structure.variant,
307
+ structure.row,
308
+ structure.column,
309
+ structure.zone,
310
+ structure.cellLabel,
313
311
  ]
314
- .map((value) => normalizeRetrievalText(value))
315
- .filter((value) => Boolean(value))
312
+ .filter(Boolean)
316
313
  .join(' ');
317
314
  }
315
+ function latentActionHintText(target) {
316
+ const acceptancePolicy = (target.acceptancePolicy ?? '').trim().toLowerCase();
317
+ const controlFamily = (target.controlFamily ?? '').trim().toLowerCase();
318
+ const surfaceKind = (target.surfaceKind ?? '').trim().toLowerCase();
319
+ const kind = (target.kind ?? '').trim().toLowerCase();
320
+ const role = (target.role ?? '').trim().toLowerCase();
321
+ const popupBacked = acceptancePolicy === 'disclosure' ||
322
+ Boolean(target.controlsSurfaceSelector) ||
323
+ target.states?.expanded !== undefined;
324
+ if (!popupBacked) {
325
+ return undefined;
326
+ }
327
+ const hints = new Set();
328
+ hints.add('open menu options choices');
329
+ if (!isFieldLikeTarget(target)) {
330
+ hints.add('sort sorting filter filters view switch picker');
331
+ }
332
+ if (controlFamily === 'select' ||
333
+ ['menu', 'listbox', 'dropdown', 'popover'].includes(surfaceKind)) {
334
+ hints.add('select choose option options menu dropdown listbox');
335
+ }
336
+ if (controlFamily === 'datepicker' || surfaceKind === 'datepicker') {
337
+ hints.add('open calendar datepicker date picker choose date calendar');
338
+ }
339
+ const evidenceText = [
340
+ target.label,
341
+ target.placeholder,
342
+ target.title,
343
+ target.surfaceLabel,
344
+ target.context?.group?.label,
345
+ target.context?.container?.label,
346
+ target.context?.landmark?.label,
347
+ target.context?.hintText,
348
+ ]
349
+ .filter((value) => Boolean(value))
350
+ .join(' ')
351
+ .toLowerCase();
352
+ if (/(?:sort|sorting|show first|ordered by|order by|popular|relevance|recommended|price|cheap|cheapest|expensive|сорт|сначала|популяр|релевант|рекоменд|цене|дешев|дорог)/i.test(evidenceText)) {
353
+ hints.add('sort sorting order order by relevance popular recommended price cheapest cheapest first');
354
+ }
355
+ if (/(?:filter|filters|refine|brand|airline|amenit|фильтр|фильтры|бренд|авиакомпан|удобств)/i.test(evidenceText)) {
356
+ hints.add('filter filters refine refine results brand airline amenities');
357
+ }
358
+ if (/(?:view|layout|grid|list|map|calendar view|вид|список|сетка|карта|раскладк)/i.test(evidenceText)) {
359
+ hints.add('view layout grid list map switch');
360
+ }
361
+ if (kind === 'button' &&
362
+ role === 'button' &&
363
+ (acceptancePolicy === 'disclosure' || controlFamily === 'select' || controlFamily === 'datepicker')) {
364
+ hints.add('open selector choose mode');
365
+ }
366
+ return hints.size > 0 ? [...hints].join(' ') : undefined;
367
+ }
368
+ function buildEntityLexicalFields(entityLabel, representative, representativeLabels) {
369
+ return [
370
+ { value: entityLabel, weight: RETRIEVAL_FIELD_WEIGHTS.entityLabel },
371
+ { value: representative.label, weight: RETRIEVAL_FIELD_WEIGHTS.representativeLabel },
372
+ ...representativeLabels.map((value) => ({
373
+ value,
374
+ weight: RETRIEVAL_FIELD_WEIGHTS.representativeLabels,
375
+ })),
376
+ { value: representative.surfaceLabel, weight: RETRIEVAL_FIELD_WEIGHTS.surfaceLabel },
377
+ { value: representative.placeholder, weight: RETRIEVAL_FIELD_WEIGHTS.placeholder },
378
+ { value: representative.title, weight: RETRIEVAL_FIELD_WEIGHTS.title },
379
+ { value: representative.context?.item?.label, weight: RETRIEVAL_FIELD_WEIGHTS.itemLabel },
380
+ { value: representative.context?.item?.text, weight: RETRIEVAL_FIELD_WEIGHTS.itemText },
381
+ { value: representative.context?.group?.label, weight: RETRIEVAL_FIELD_WEIGHTS.groupLabel },
382
+ { value: representative.context?.group?.text, weight: RETRIEVAL_FIELD_WEIGHTS.groupText },
383
+ {
384
+ value: representative.context?.container?.label,
385
+ weight: RETRIEVAL_FIELD_WEIGHTS.containerLabel,
386
+ },
387
+ {
388
+ value: representative.context?.container?.text,
389
+ weight: RETRIEVAL_FIELD_WEIGHTS.containerText,
390
+ },
391
+ {
392
+ value: representative.context?.landmark?.label,
393
+ weight: RETRIEVAL_FIELD_WEIGHTS.landmarkLabel,
394
+ },
395
+ {
396
+ value: representative.context?.landmark?.text,
397
+ weight: RETRIEVAL_FIELD_WEIGHTS.landmarkText,
398
+ },
399
+ { value: representative.context?.hintText, weight: RETRIEVAL_FIELD_WEIGHTS.hintText },
400
+ { value: representative.kind, weight: RETRIEVAL_FIELD_WEIGHTS.kind },
401
+ { value: representative.role, weight: RETRIEVAL_FIELD_WEIGHTS.role },
402
+ { value: representative.surfaceKind, weight: RETRIEVAL_FIELD_WEIGHTS.surfaceKind },
403
+ {
404
+ value: representative.controlFamily,
405
+ weight: RETRIEVAL_FIELD_WEIGHTS.controlFamily,
406
+ },
407
+ {
408
+ value: representative.acceptancePolicy,
409
+ weight: RETRIEVAL_FIELD_WEIGHTS.acceptancePolicy,
410
+ },
411
+ {
412
+ value: representative.allowedActions?.join(' '),
413
+ weight: RETRIEVAL_FIELD_WEIGHTS.allowedActions,
414
+ },
415
+ {
416
+ value: structureLexicalValue(representative.structure),
417
+ weight: RETRIEVAL_FIELD_WEIGHTS.structure,
418
+ },
419
+ {
420
+ value: latentActionHintText(representative),
421
+ weight: RETRIEVAL_FIELD_WEIGHTS.latentIntent,
422
+ },
423
+ ];
424
+ }
318
425
  function buildGoalRetrievalEntity(entityKind, entityKey, memberIndexes, targets) {
319
426
  const orderedMembers = [...memberIndexes]
320
427
  .map((index) => ({ index, target: targets[index] }))
@@ -322,6 +429,8 @@ function buildGoalRetrievalEntity(entityKind, entityKey, memberIndexes, targets)
322
429
  const representative = orderedMembers[0].target;
323
430
  const representativeLabels = collectRepresentativeLabels(orderedMembers.map((entry) => entry.target));
324
431
  const label = pickEntityLabel(entityKind, representative);
432
+ const lexicalFields = buildEntityLexicalFields(label, representative, representativeLabels);
433
+ const lexicalDocument = buildSemanticObserveLexicalDocument(lexicalFields);
325
434
  return {
326
435
  entityKind,
327
436
  entityKey,
@@ -342,7 +451,8 @@ function buildGoalRetrievalEntity(entityKind, entityKey, memberIndexes, targets)
342
451
  context: representative.context,
343
452
  structure: representative.structure,
344
453
  representativeLabels,
345
- searchText: buildEntitySearchText(label, representative, representativeLabels),
454
+ analyzerKey: lexicalDocument.analyzerKey,
455
+ lexicalDocument,
346
456
  };
347
457
  }
348
458
  function buildGoalRetrievalEntities(targets) {
@@ -376,7 +486,12 @@ function buildGoalRetrievalEntities(targets) {
376
486
  }
377
487
  for (const [key, memberIndexes] of formGroups.entries()) {
378
488
  entities.push(buildGoalRetrievalEntity('form', `form:${key}`, memberIndexes, targets));
379
- memberIndexes.forEach((index) => groupedTargetIndexes.add(index));
489
+ memberIndexes.forEach((index) => {
490
+ if (shouldPreserveStandaloneFormActionEntity(targets[index])) {
491
+ return;
492
+ }
493
+ groupedTargetIndexes.add(index);
494
+ });
380
495
  }
381
496
  const itemGroups = new Map();
382
497
  for (const [index, target] of targets.entries()) {
@@ -445,58 +560,28 @@ function retrievalEntityPriority(entity) {
445
560
  score += representativeCandidateScore(entity.representative);
446
561
  return score;
447
562
  }
448
- function scoreRetrievalEntityAgainstGoal(goal, entity) {
449
- const normalizedGoal = normalizeRetrievalText(goal);
450
- if (!normalizedGoal) {
563
+ function scoreRetrievalEntityAgainstGoal(analyzedGoal, entity, corpusStats) {
564
+ if (!analyzedGoal.normalizedText) {
451
565
  return 0;
452
566
  }
453
- const entityText = entity.searchText;
454
- if (!entityText) {
567
+ if (entity.lexicalDocument.weightedLength <= 0) {
455
568
  return 0;
456
569
  }
457
- let score = 0;
458
- const goalTokens = tokenizeRetrievalText(normalizedGoal).filter((token) => token.length >= 2 && !GOAL_TEXT_STOPWORDS.has(token));
459
- const entityTokens = new Set(tokenizeRetrievalText(entityText));
460
- if (entityText.includes(normalizedGoal)) {
461
- score += 220;
462
- }
463
- const entityLabel = normalizeRetrievalText(entity.label);
464
- if (entityLabel) {
465
- if (normalizedGoal.includes(entityLabel)) {
466
- score += 180;
467
- }
468
- if (entityLabel.includes(normalizedGoal)) {
469
- score += 140;
470
- }
471
- }
472
- let matchedTokenCount = 0;
473
- for (const token of goalTokens) {
474
- if (entityTokens.has(token)) {
475
- matchedTokenCount += 1;
476
- score += 8 + Math.min(token.length, 12);
477
- }
478
- }
479
- if (goalTokens.length > 0) {
480
- const coverage = matchedTokenCount / goalTokens.length;
481
- if (coverage === 1) {
482
- score += 120;
483
- }
484
- else if (coverage >= 0.75) {
485
- score += 70;
486
- }
487
- else if (coverage >= 0.5) {
488
- score += 35;
489
- }
490
- }
491
- return score;
570
+ const bm25Score = scoreSemanticObserveBm25(analyzedGoal, entity.lexicalDocument, corpusStats);
571
+ const analyzerAlignmentScore = analyzedGoal.analyzerKey !== 'neutral' && analyzedGoal.analyzerKey === entity.analyzerKey
572
+ ? 15
573
+ : 0;
574
+ return bm25Score * 100 + analyzerAlignmentScore;
492
575
  }
493
576
  function preselectRetrievalEntities(goal, entities) {
494
577
  if (entities.length <= GOAL_RETRIEVAL_ENTITY_LIMIT) {
495
578
  return [...entities];
496
579
  }
580
+ const analyzedGoal = analyzeSemanticObserveText(goal);
581
+ const corpusStats = buildSemanticObserveBm25CorpusStats(entities.map((entity) => entity.lexicalDocument));
497
582
  const scored = entities.map((entity) => ({
498
583
  entity,
499
- lexicalScore: scoreRetrievalEntityAgainstGoal(goal, entity),
584
+ lexicalScore: scoreRetrievalEntityAgainstGoal(analyzedGoal, entity, corpusStats),
500
585
  priority: retrievalEntityPriority(entity),
501
586
  }));
502
587
  const selected = [];
@@ -634,9 +719,6 @@ function diversifyCandidates(targets) {
634
719
  }
635
720
  return orderedIndexes.map((index) => targets[index]).slice(0, RERANK_CANDIDATE_LIMIT);
636
721
  }
637
- function buildCandidateSummary(target, index, surfaceSummaryIdBySurfaceKey) {
638
- return buildCompactCandidateSummary(target, index, surfaceSummaryIdBySurfaceKey);
639
- }
640
722
  function compactContextValue(value, maxLength = 80) {
641
723
  const normalized = value?.replace(/\s+/g, ' ').trim();
642
724
  if (!normalized) {
@@ -791,6 +873,9 @@ function buildCompactCandidateSummary(target, index, surfaceSummaryIdBySurfaceKe
791
873
  if (target.controlFamily) {
792
874
  parts.push(`family=${JSON.stringify(target.controlFamily)}`);
793
875
  }
876
+ if (target.controlsSurfaceSelector) {
877
+ parts.push('controlsPopup=true');
878
+ }
794
879
  if (target.capability && target.capability !== 'actionable') {
795
880
  parts.push(`capability=${JSON.stringify(target.capability)}`);
796
881
  }
@@ -836,6 +921,7 @@ export async function rerankDomTargetsForGoal(instruction, targets, options = {}
836
921
  const prompt = [
837
922
  'You are choosing from already discovered visible webpage candidates.',
838
923
  'Select only candidate IDs that directly satisfy the goal.',
924
+ 'A disclosure trigger with a current-state label can still directly satisfy sort, filter, view-switch, or picker goals when opening it is the immediate next step.',
839
925
  'Prefer direct actionable controls over surrounding regions unless the goal explicitly asks for a form, region, widget, or set of controls.',
840
926
  'Use owning surface, compact context, explicit state, and structure metadata to disambiguate similar labels.',
841
927
  'For input/value goals, prefer the directly editable field or primary picker trigger over wrappers or mirrored summary content.',
@@ -847,7 +933,7 @@ export async function rerankDomTargetsForGoal(instruction, targets, options = {}
847
933
  '',
848
934
  ...(surfaceSummaries.length > 0 ? ['Surfaces:', ...surfaceSummaries.map((entry) => entry.line), ''] : []),
849
935
  'Candidates:',
850
- ...candidates.map((target, index) => buildCandidateSummary(target, index, summaryIdBySurfaceKey)),
936
+ ...candidates.map((target, index) => buildCompactCandidateSummary(target, index, summaryIdBySurfaceKey)),
851
937
  ].join('\n');
852
938
  const result = await client.createChatCompletion({
853
939
  logger: () => { },
@@ -1 +1 @@
1
- {"version":3,"file":"control-semantics.d.ts","sourceRoot":"","sources":["../src/control-semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,aAAa,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAyCtF,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,oBAAoB,GAC1B,mBAAmB,GAAG,SAAS,CAuCjC;AAsCD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAYxE;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,oBAAoB,GAAG,mBAAmB,EAAE,CAoG/F;AAED,wBAAgB,0BAA0B,CACxC,MAAM,CAAC,EAAE,gBAAgB,EACzB,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,GAAE;IACP,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC1B,GACL,uBAAuB,CAyBzB;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,EAC3B,cAAc,EAAE,aAAa,CAAC,mBAAmB,CAAC,GACjD,mBAAmB,GAAG,SAAS,CA0EjC;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,oBAAoB,EAC3B,cAAc,EAAE,aAAa,CAAC,mBAAmB,CAAC,GACjD,sBAAsB,GAAG,SAAS,CAiDpC"}
1
+ {"version":3,"file":"control-semantics.d.ts","sourceRoot":"","sources":["../src/control-semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,aAAa,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAwCtF,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,oBAAoB,GAC1B,mBAAmB,GAAG,SAAS,CAuCjC;AAyID,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAYxE;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,oBAAoB,GAAG,mBAAmB,EAAE,CAoG/F;AAED,wBAAgB,0BAA0B,CACxC,MAAM,CAAC,EAAE,gBAAgB,EACzB,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,GAAE;IACP,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC1B,GACL,uBAAuB,CAyBzB;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,EAC3B,cAAc,EAAE,aAAa,CAAC,mBAAmB,CAAC,GACjD,mBAAmB,GAAG,SAAS,CA0EjC;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,oBAAoB,EAC3B,cAAc,EAAE,aAAa,CAAC,mBAAmB,CAAC,GACjD,sBAAsB,GAAG,SAAS,CA6CpC"}
@@ -83,6 +83,76 @@ function isButtonLikeInputFacts(facts) {
83
83
  return (normalizeText(facts.kind) === 'input' &&
84
84
  ['button', 'submit', 'reset'].includes(normalizeText(facts.inputType)));
85
85
  }
86
+ function isSelectionItemLikeFacts(facts) {
87
+ const kind = normalizeText(facts.kind);
88
+ const role = normalizeText(facts.role);
89
+ return (kind === 'option' ||
90
+ role === 'option' ||
91
+ role === 'menuitem' ||
92
+ facts.structure?.family === 'structured-grid');
93
+ }
94
+ function isBinaryToggleControlFacts(facts) {
95
+ const kind = normalizeText(facts.kind);
96
+ const role = normalizeText(facts.role);
97
+ const inputType = normalizeText(facts.inputType);
98
+ return (kind === 'checkbox' ||
99
+ kind === 'radio' ||
100
+ role === 'checkbox' ||
101
+ role === 'radio' ||
102
+ role === 'switch' ||
103
+ kind === 'tab' ||
104
+ role === 'tab' ||
105
+ inputType === 'checkbox' ||
106
+ inputType === 'radio');
107
+ }
108
+ function isDisclosureLikeTriggerFacts(facts, controlFamily) {
109
+ const kind = normalizeText(facts.kind);
110
+ const role = normalizeText(facts.role);
111
+ const inputType = normalizeText(facts.inputType);
112
+ const popupBacked = Boolean(facts.controlsSurfaceSelector) ||
113
+ facts.states?.expanded !== undefined;
114
+ if (!popupBacked) {
115
+ return false;
116
+ }
117
+ if (isSelectionItemLikeFacts(facts) || controlFamily === 'text-input') {
118
+ return false;
119
+ }
120
+ if (controlFamily === 'select' || controlFamily === 'datepicker') {
121
+ return true;
122
+ }
123
+ return (kind === 'button' ||
124
+ role === 'button' ||
125
+ kind === 'link' ||
126
+ role === 'link' ||
127
+ kind === 'combobox' ||
128
+ role === 'combobox' ||
129
+ kind === 'select' ||
130
+ isButtonLikeInputFacts(facts) ||
131
+ inputType === 'button' ||
132
+ isPopupBackedTextEntry(facts));
133
+ }
134
+ function isToggleLikeControlFacts(facts, controlFamily) {
135
+ const states = facts.states;
136
+ if (!states) {
137
+ return false;
138
+ }
139
+ if (states.checked !== undefined || isBinaryToggleControlFacts(facts)) {
140
+ return true;
141
+ }
142
+ if (states.selected !== undefined) {
143
+ if (isSelectionItemLikeFacts(facts) ||
144
+ controlFamily === 'select' ||
145
+ controlFamily === 'datepicker' ||
146
+ isDisclosureLikeTriggerFacts(facts, controlFamily)) {
147
+ return false;
148
+ }
149
+ return true;
150
+ }
151
+ if (states.pressed !== undefined) {
152
+ return !isDisclosureLikeTriggerFacts(facts, controlFamily);
153
+ }
154
+ return false;
155
+ }
86
156
  export function isLikelyDateLikeLabel(value) {
87
157
  const trimmed = (value ?? '').trim();
88
158
  if (!trimmed) {
@@ -280,17 +350,15 @@ export function inferAcceptancePolicyFromFacts(facts, allowedActions) {
280
350
  if (facts.structure?.family === 'structured-grid') {
281
351
  return facts.structure.variant === 'date-cell' ? 'date-selection' : 'selection';
282
352
  }
283
- if (states?.checked !== undefined ||
284
- states?.selected !== undefined ||
285
- states?.pressed !== undefined) {
286
- return 'toggle';
287
- }
288
353
  if (controlFamily === 'text-input') {
289
354
  return 'value-change';
290
355
  }
291
- if (states?.expanded !== undefined) {
356
+ if (isDisclosureLikeTriggerFacts(facts, controlFamily)) {
292
357
  return 'disclosure';
293
358
  }
359
+ if (isToggleLikeControlFacts(facts, controlFamily)) {
360
+ return 'toggle';
361
+ }
294
362
  if (controlFamily === 'datepicker') {
295
363
  return 'date-selection';
296
364
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA2QA,iBAAe,IAAI,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsKhE;AAED,OAAO,EAAE,IAAI,EAAE,CAAC;AAEhB,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAWzF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA4QA,iBAAe,IAAI,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4KhE;AAED,OAAO,EAAE,IAAI,EAAE,CAAC;AAEhB,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAWzF"}
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
5
5
  loadEnv();
6
6
  import { preflightAgentpayGateway } from './agentpay-gateway.js';
7
7
  import { browseCommand, browseCommandName } from './command-name.js';
8
+ import { assertSemanticObserveRuntimeSupport } from './commands/semantic-observe-lexical.js';
8
9
  import { loadSession } from './session.js';
9
10
  import { outputError, outputJSON, fatal, info } from './output.js';
10
11
  import { applyDemoProxyBootstrap } from './solver/config.js';
@@ -223,6 +224,12 @@ async function main(argv = process.argv) {
223
224
  if (!KNOWN_COMMANDS.has(command)) {
224
225
  outputError(`Unknown command: ${command}\n\n${usageText()}`);
225
226
  }
227
+ try {
228
+ assertSemanticObserveRuntimeSupport();
229
+ }
230
+ catch (err) {
231
+ outputError(err instanceof Error ? err.message : String(err));
232
+ }
226
233
  if (command !== 'init') {
227
234
  try {
228
235
  await preflightAgentpayGateway();
@@ -11,4 +11,7 @@ export declare function cleanupManagedBrowserPids(options?: {
11
11
  export declare function listManagedBrowserPids(options?: {
12
12
  processTable?: string;
13
13
  }): number[];
14
+ export declare function isManagedBrowserPid(pid: number, options?: {
15
+ processTable?: string;
16
+ }): boolean;
14
17
  //# sourceMappingURL=owned-process.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"owned-process.d.ts","sourceRoot":"","sources":["../src/owned-process.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,yBAAyB,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,GAAG,aAAa,CAAC;AACjG,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAW/C;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAOvF;AAiCD,wBAAsB,yBAAyB,CAC7C,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;CAAO,GAC/C,OAAO,CAAC,2BAA2B,CAAC,CAqBtC;AAED,wBAAgB,sBAAsB,CACpC,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,MAAM,CAAC;CAClB,GACL,MAAM,EAAE,CAuBV"}
1
+ {"version":3,"file":"owned-process.d.ts","sourceRoot":"","sources":["../src/owned-process.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,yBAAyB,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,GAAG,aAAa,CAAC;AACjG,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAW/C;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAOvF;AAiCD,wBAAsB,yBAAyB,CAC7C,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;CAAO,GAC/C,OAAO,CAAC,2BAA2B,CAAC,CAqBtC;AAED,wBAAgB,sBAAsB,CACpC,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,MAAM,CAAC;CAClB,GACL,MAAM,EAAE,CAuBV;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,MAAM,CAAC;CAClB,GACL,OAAO,CAET"}
@@ -93,6 +93,9 @@ export function listManagedBrowserPids(options = {}) {
93
93
  }
94
94
  return Array.from(pids);
95
95
  }
96
+ export function isManagedBrowserPid(pid, options = {}) {
97
+ return listManagedBrowserPids(options).includes(pid);
98
+ }
96
99
  async function waitForPidExit(pid, attempts, intervalMs) {
97
100
  for (let attempt = 0; attempt < attempts; attempt++) {
98
101
  if (!isPidAlive(pid)) {
@@ -10,4 +10,5 @@ export type SolverSession = {
10
10
  disconnect: () => Promise<void>;
11
11
  };
12
12
  export declare function launchSolver(profile: ProfileInfo, opts?: LaunchOptions): Promise<SolverSession>;
13
+ export declare function chromeArgsForEnvironment(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): string[];
13
14
  //# sourceMappingURL=browser-launcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser-launcher.d.ts","sourceRoot":"","sources":["../../src/solver/browser-launcher.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAsB,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAajF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC,CAAC;AAiBF,wBAAsB,YAAY,CAChC,OAAO,EAAE,WAAW,EACpB,IAAI,CAAC,EAAE,aAAa,GACnB,OAAO,CAAC,aAAa,CAAC,CAkExB"}
1
+ {"version":3,"file":"browser-launcher.d.ts","sourceRoot":"","sources":["../../src/solver/browser-launcher.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAsB,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAgBjF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC,CAAC;AAuBF,wBAAsB,YAAY,CAChC,OAAO,EAAE,WAAW,EACpB,IAAI,CAAC,EAAE,aAAa,GACnB,OAAO,CAAC,aAAa,CAAC,CAmExB;AA8TD,wBAAgB,wBAAwB,CACtC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,EAAE,CAGV"}
@@ -6,11 +6,20 @@ import puppeteerExtra from 'puppeteer-extra';
6
6
  import StealthPlugin from 'puppeteer-extra-plugin-stealth';
7
7
  import { buildTurnstileApiShimSource, isTurnstileApiRequest } from './turnstile-challenge.js';
8
8
  const enhancedPuppeteer = puppeteerExtra;
9
- enhancedPuppeteer.use(StealthPlugin());
9
+ const solverStealthPlugin = StealthPlugin();
10
+ // Some iframe-backed checkout flows break when this evasion runs on a connected real Chrome.
11
+ solverStealthPlugin.enabledEvasions.delete('iframe.contentWindow');
12
+ enhancedPuppeteer.use(solverStealthPlugin);
10
13
  const AUTO_CDP_PORT = 0;
11
14
  const CDP_DISCOVERY_TIMEOUT_MS = 30_000;
12
15
  const CDP_DISCOVERY_INTERVAL_MS = 500;
16
+ const BROWSER_DISCONNECT_TIMEOUT_MS = 1_000;
13
17
  const INTERCEPTED_PAGES = new WeakSet();
18
+ const LINUX_CI_CHROME_ARGS = [
19
+ '--no-sandbox',
20
+ '--disable-setuid-sandbox',
21
+ '--disable-dev-shm-usage',
22
+ ];
14
23
  const CHROME_PATHS = [
15
24
  '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
16
25
  '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
@@ -30,6 +39,7 @@ export async function launchSolver(profile, opts) {
30
39
  const cdpUrl = await discoverCdpUrl({
31
40
  requestedPort: opts?.cdpPort,
32
41
  userDataDir: profile.userDataDir,
42
+ chromeProcess,
33
43
  });
34
44
  if (!cdpUrl) {
35
45
  throw buildCdpDiscoveryError(chromeProcess, opts?.cdpPort, profile.userDataDir);
@@ -63,7 +73,7 @@ export async function launchSolver(profile, opts) {
63
73
  profile,
64
74
  close: async () => {
65
75
  try {
66
- await browser.disconnect();
76
+ await disconnectBrowserWithinTimeout(browser, BROWSER_DISCONNECT_TIMEOUT_MS);
67
77
  }
68
78
  finally {
69
79
  terminateProcessGroup(chromeProcess.pid);
@@ -96,10 +106,25 @@ async function discoverCdpUrl(options) {
96
106
  return discoveredViaActivePort;
97
107
  }
98
108
  }
109
+ if (typeof options.chromeProcess?.exitCode === 'number' || options.chromeProcess?.signalCode) {
110
+ break;
111
+ }
99
112
  await sleep(CDP_DISCOVERY_INTERVAL_MS);
100
113
  }
101
114
  return null;
102
115
  }
116
+ async function disconnectBrowserWithinTimeout(browser, timeoutMs) {
117
+ let timeoutId;
118
+ await Promise.race([
119
+ browser.disconnect().catch(() => { }),
120
+ new Promise((resolve) => {
121
+ timeoutId = setTimeout(resolve, timeoutMs);
122
+ }),
123
+ ]);
124
+ if (timeoutId) {
125
+ clearTimeout(timeoutId);
126
+ }
127
+ }
103
128
  async function discoverCdpUrlOnPort(port) {
104
129
  try {
105
130
  const res = await fetch(`http://127.0.0.1:${port}/json/version`);
@@ -290,6 +315,7 @@ function spawnChromeProcess(executablePath, profile, fp, cdpPort, windowSize, he
290
315
  '--no-first-run',
291
316
  '--no-default-browser-check',
292
317
  ];
318
+ args.push(...chromeArgsForEnvironment());
293
319
  if (headless) {
294
320
  args.push('--headless=new');
295
321
  }
@@ -311,6 +337,10 @@ function spawnChromeProcess(executablePath, profile, fp, cdpPort, windowSize, he
311
337
  }
312
338
  return proc;
313
339
  }
340
+ export function chromeArgsForEnvironment(platform = process.platform, env = process.env) {
341
+ const isLinuxCi = platform === 'linux' && (env.CI === 'true' || env.GITHUB_ACTIONS === 'true');
342
+ return isLinuxCi ? [...LINUX_CI_CHROME_ARGS] : [];
343
+ }
314
344
  function terminateProcessGroup(pid) {
315
345
  try {
316
346
  process.kill(-pid, 'SIGTERM');
@@ -1 +1 @@
1
- {"version":3,"file":"captcha-runtime.d.ts","sourceRoot":"","sources":["../../src/solver/captcha-runtime.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAMF,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,yBAAyB,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CA6HnC"}
1
+ {"version":3,"file":"captcha-runtime.d.ts","sourceRoot":"","sources":["../../src/solver/captcha-runtime.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAMF,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,yBAAyB,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CAgInC"}