@mercuryo-ai/agentbrowse 0.2.56 → 0.2.60

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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -58
  3. package/dist/command-name.js +1 -1
  4. package/dist/commands/act.d.ts.map +1 -1
  5. package/dist/commands/act.js +13 -5
  6. package/dist/commands/action-executor-helpers.d.ts.map +1 -1
  7. package/dist/commands/action-executor-helpers.js +10 -8
  8. package/dist/commands/click-activation-policy.d.ts.map +1 -1
  9. package/dist/commands/click-activation-policy.js +6 -2
  10. package/dist/commands/launch.d.ts +0 -1
  11. package/dist/commands/launch.d.ts.map +1 -1
  12. package/dist/commands/launch.js +2 -8
  13. package/dist/commands/observe-inventory.d.ts +5 -1
  14. package/dist/commands/observe-inventory.d.ts.map +1 -1
  15. package/dist/commands/observe-inventory.js +316 -5
  16. package/dist/commands/observe-persistence.d.ts.map +1 -1
  17. package/dist/commands/observe-persistence.js +2 -0
  18. package/dist/commands/observe-projection.d.ts +3 -2
  19. package/dist/commands/observe-projection.d.ts.map +1 -1
  20. package/dist/commands/observe-projection.js +1 -0
  21. package/dist/commands/observe-protected.d.ts +3 -1
  22. package/dist/commands/observe-protected.d.ts.map +1 -1
  23. package/dist/commands/observe-protected.js +23 -1
  24. package/dist/commands/observe-semantics.d.ts.map +1 -1
  25. package/dist/commands/observe-semantics.js +70 -0
  26. package/dist/commands/observe.d.ts +1 -0
  27. package/dist/commands/observe.d.ts.map +1 -1
  28. package/dist/commands/observe.js +4 -2
  29. package/dist/control-semantics.d.ts.map +1 -1
  30. package/dist/control-semantics.js +5 -0
  31. package/dist/date-value-normalization.d.ts +16 -0
  32. package/dist/date-value-normalization.d.ts.map +1 -0
  33. package/dist/date-value-normalization.js +117 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +5 -24
  36. package/dist/library.d.ts +3 -0
  37. package/dist/library.d.ts.map +1 -1
  38. package/dist/library.js +2 -0
  39. package/dist/protected-fill.d.ts +3 -2
  40. package/dist/protected-fill.d.ts.map +1 -1
  41. package/dist/runtime-protected-state.d.ts.map +1 -1
  42. package/dist/runtime-protected-state.js +8 -1
  43. package/dist/runtime-state.d.ts +11 -0
  44. package/dist/runtime-state.d.ts.map +1 -1
  45. package/dist/secrets/form-matcher.d.ts +1 -2
  46. package/dist/secrets/form-matcher.d.ts.map +1 -1
  47. package/dist/secrets/form-matcher.js +125 -119
  48. package/dist/secrets/matching-helpers.d.ts +13 -0
  49. package/dist/secrets/matching-helpers.d.ts.map +1 -0
  50. package/dist/secrets/matching-helpers.js +147 -0
  51. package/dist/secrets/observed-field-resolution.d.ts +43 -0
  52. package/dist/secrets/observed-field-resolution.d.ts.map +1 -0
  53. package/dist/secrets/observed-field-resolution.js +223 -0
  54. package/dist/secrets/protected-field-semantics.d.ts.map +1 -1
  55. package/dist/secrets/protected-field-semantics.js +3 -2
  56. package/dist/secrets/protected-fill.d.ts +3 -1
  57. package/dist/secrets/protected-fill.d.ts.map +1 -1
  58. package/dist/secrets/protected-fill.js +31 -0
  59. package/dist/secrets/protected-value-adapters.d.ts.map +1 -1
  60. package/dist/secrets/protected-value-adapters.js +14 -22
  61. package/dist/secrets/types.d.ts +3 -0
  62. package/dist/secrets/types.d.ts.map +1 -1
  63. package/docs/README.md +15 -2
  64. package/docs/api-reference.md +13 -3
  65. package/docs/assistive-runtime.md +63 -7
  66. package/docs/configuration.md +16 -8
  67. package/docs/getting-started.md +17 -8
  68. package/docs/integration-checklist.md +8 -7
  69. package/docs/protected-fill.md +40 -7
  70. package/docs/testing.md +4 -3
  71. package/docs/troubleshooting.md +88 -34
  72. package/examples/README.md +9 -2
  73. package/package.json +8 -3
@@ -3,8 +3,8 @@ import { tryCreateAgentbrowseAssistiveLlmClient } from '../assistive-runtime.js'
3
3
  import { IDENTITY_FIELD_KEYS, LOGIN_FIELD_KEYS, PAYMENT_CARD_FIELD_KEYS } from './types.js';
4
4
  import { normalizeProtectedBindingValueHint, protectedBindingKey, protectedBindingValueHintSchema, } from './protected-bindings.js';
5
5
  import { inferProtectedFieldMeaningFromTarget } from './protected-field-semantics.js';
6
+ import { directFieldSignalValuesOf, frameKeyOf, normalizeText, selectorRootOf, selectorSignalFragments, signalValuesOf, } from './matching-helpers.js';
6
7
  const PROMPT_VALUE_MAX_CHARS = 180;
7
- const PROMPT_SIGNAL_MAX_CHARS = 220;
8
8
  const PROMPT_SIGNAL_MAX_VALUES = 8;
9
9
  const PAYMENT_CARD_CORE_FIELD_KEYS = new Set([
10
10
  'pan',
@@ -27,7 +27,8 @@ const EXPLICIT_PAYMENT_CARD_NAME_SIGNAL_RE = /\b(cardholder|card holder|name on
27
27
  const PAYMENT_CARD_PAN_SIGNAL_RE = /\b(card number|card no|cc-number|cc number)\b/i;
28
28
  const PAYMENT_CARD_CVV_SIGNAL_RE = /\b(security code|cvv|cvc)\b/i;
29
29
  const STRONG_PAYMENT_CARD_EXP_SIGNAL_RE = /\b(mm\s*\/\s*yy|mmyy|card exp(?:iry)?|card expiration|cc-exp)\b/i;
30
- const GENERIC_SHELL_SELECTOR_ROOTS = new Set(['#__next', '#root', '#app', '#app-root']);
30
+ const LOGIN_AUTH_CONTEXT_SIGNAL_RE = /\b(sign in|sign-in|log in|login|account(?: access| sign in| login| email| password)|member login|use password)\b/i;
31
+ const LOGIN_NON_AUTH_CONTEXT_SIGNAL_RE = /\b(reset|forgot|recover|recovery|create password|confirm password|new password|verification|security code|one[- ]?time|otp|passkey|magic link)\b/i;
31
32
  const protectedFormPlanSchema = z.object({
32
33
  confidence: z.enum(['high', 'medium', 'low']),
33
34
  bindings: z
@@ -38,9 +39,6 @@ const protectedFormPlanSchema = z.object({
38
39
  }))
39
40
  .max(24),
40
41
  });
41
- function normalizeText(value) {
42
- return value?.replace(/\s+/g, ' ').trim().toLowerCase() ?? '';
43
- }
44
42
  function compactPromptValue(value, maxChars = PROMPT_VALUE_MAX_CHARS) {
45
43
  const normalized = value?.replace(/\s+/g, ' ').trim();
46
44
  if (!normalized) {
@@ -62,83 +60,6 @@ function isTargetEligibleForProtectedFill(target) {
62
60
  function allowsProtectedAvailability(availability) {
63
61
  return availability?.state === undefined || availability.state === 'available';
64
62
  }
65
- function selectorSignalFragments(selector) {
66
- const fragments = [];
67
- for (const match of selector.matchAll(/\[name="([^"]+)"\]/g)) {
68
- fragments.push(match[1] ?? '');
69
- }
70
- for (const match of selector.matchAll(/#([a-zA-Z0-9_-]+)/g)) {
71
- fragments.push(match[1] ?? '');
72
- }
73
- return fragments;
74
- }
75
- function selectorRootOf(target) {
76
- const selectors = [
77
- target.context?.item?.selector,
78
- target.context?.container?.selector,
79
- target.context?.landmark?.selector,
80
- ].filter((value) => typeof value === 'string' && value.length > 0);
81
- for (const selector of selectors) {
82
- for (const match of selector.matchAll(/#[A-Za-z0-9_-]+/g)) {
83
- const candidate = match[0]?.trim().toLowerCase();
84
- if (!candidate || GENERIC_SHELL_SELECTOR_ROOTS.has(candidate)) {
85
- continue;
86
- }
87
- return match[0];
88
- }
89
- }
90
- return null;
91
- }
92
- function signalValuesOf(target) {
93
- const values = new Set();
94
- const push = (value) => {
95
- const normalized = normalizeText(value);
96
- if (normalized) {
97
- values.add(normalized.length > PROMPT_SIGNAL_MAX_CHARS
98
- ? `${normalized.slice(0, PROMPT_SIGNAL_MAX_CHARS - 1)}…`
99
- : normalized);
100
- }
101
- };
102
- push(target.label);
103
- push(target.displayLabel);
104
- push(target.kind);
105
- push(target.semantics?.role);
106
- push(target.semantics?.name);
107
- push(target.context?.hintText);
108
- push(target.context?.group?.label);
109
- push(target.context?.group?.text);
110
- push(target.context?.container?.label);
111
- push(target.context?.container?.text);
112
- push(target.context?.landmark?.label);
113
- push(target.context?.landmark?.text);
114
- for (const candidate of target.locatorCandidates) {
115
- push(candidate.name);
116
- push(candidate.value);
117
- if (candidate.strategy === 'css' || candidate.strategy === 'xpath') {
118
- for (const fragment of selectorSignalFragments(candidate.value)) {
119
- push(fragment);
120
- }
121
- }
122
- }
123
- return [...values];
124
- }
125
- function directFieldSignalValuesOf(target) {
126
- const values = new Set();
127
- const push = (value) => {
128
- const normalized = normalizeText(value);
129
- if (normalized) {
130
- values.add(normalized);
131
- }
132
- };
133
- push(target.label);
134
- push(target.displayLabel);
135
- push(target.placeholder);
136
- push(target.inputName);
137
- push(target.inputType);
138
- push(target.autocomplete);
139
- push(target.semantics?.name);
140
- return [...values];
141
- }
142
63
  function targetHasInferredProtectedMeaning(target, kind, fieldKeys) {
143
64
  return inferProtectedFieldMeaningFromTarget(target).some((hint) => {
144
65
  if (hint.kind !== kind) {
@@ -455,7 +376,48 @@ function deterministicLoginFieldKeysForTarget(target) {
455
376
  .filter((hint) => hint.kind === 'login')
456
377
  .map((hint) => hint.fieldKey);
457
378
  }
458
- function deterministicLoginPlan(plannerGroup, fullGroup) {
379
+ function loginContextSignalValues(group) {
380
+ const values = new Set();
381
+ const push = (value) => {
382
+ const normalized = normalizeText(value);
383
+ if (normalized) {
384
+ values.add(normalized);
385
+ }
386
+ };
387
+ for (const target of group.targets) {
388
+ push(target.label);
389
+ push(target.displayLabel);
390
+ push(target.placeholder);
391
+ push(target.inputName);
392
+ push(target.context?.hintText);
393
+ push(target.context?.item?.label);
394
+ push(target.context?.item?.text);
395
+ push(target.context?.group?.label);
396
+ push(target.context?.group?.text);
397
+ push(target.context?.container?.label);
398
+ push(target.context?.container?.text);
399
+ push(target.context?.landmark?.label);
400
+ push(target.context?.landmark?.text);
401
+ }
402
+ return [...values];
403
+ }
404
+ function hasStrongLoginAuthContext(group) {
405
+ const signals = loginContextSignalValues(group);
406
+ if (signals.some((signal) => LOGIN_NON_AUTH_CONTEXT_SIGNAL_RE.test(signal))) {
407
+ return false;
408
+ }
409
+ return signals.some((signal) => LOGIN_AUTH_CONTEXT_SIGNAL_RE.test(signal));
410
+ }
411
+ function loginFieldBindingForTarget(fieldKey, target) {
412
+ return {
413
+ fieldKey,
414
+ targetRef: target.ref,
415
+ label: target.displayLabel ?? target.label,
416
+ required: target.validation?.required,
417
+ valueHint: 'direct',
418
+ };
419
+ }
420
+ function deterministicLoginPlan(plannerGroup, fullGroup, catalog) {
459
421
  const fieldTargets = new Map();
460
422
  for (const target of plannerGroup.targets) {
461
423
  for (const fieldKey of deterministicLoginFieldKeysForTarget(target)) {
@@ -466,32 +428,58 @@ function deterministicLoginPlan(plannerGroup, fullGroup) {
466
428
  }
467
429
  const usernameRefs = fieldTargets.get('username');
468
430
  const passwordRefs = fieldTargets.get('password');
469
- if (!usernameRefs || usernameRefs.size !== 1 || !passwordRefs || passwordRefs.size !== 1) {
431
+ if (usernameRefs?.size === 1 && passwordRefs?.size === 1) {
432
+ const targetByRef = new Map(fullGroup.targets.map((target) => [target.ref, target]));
433
+ const fields = [
434
+ {
435
+ fieldKey: 'username',
436
+ targetRef: [...usernameRefs][0],
437
+ label: targetByRef.get([...usernameRefs][0])?.displayLabel ??
438
+ targetByRef.get([...usernameRefs][0])?.label,
439
+ required: targetByRef.get([...usernameRefs][0])?.validation?.required,
440
+ valueHint: 'direct',
441
+ },
442
+ {
443
+ fieldKey: 'password',
444
+ targetRef: [...passwordRefs][0],
445
+ label: targetByRef.get([...passwordRefs][0])?.displayLabel ??
446
+ targetByRef.get([...passwordRefs][0])?.label,
447
+ required: targetByRef.get([...passwordRefs][0])?.validation?.required,
448
+ valueHint: 'direct',
449
+ },
450
+ ];
451
+ return {
452
+ confidence: 'high',
453
+ fields,
454
+ loginStep: 'full',
455
+ };
456
+ }
457
+ if (!catalog ||
458
+ !hasApplicableStoredSecretKind(catalog, 'login') ||
459
+ fullGroup.targets.length !== 1 ||
460
+ plannerGroup.targets.length !== 1 ||
461
+ !hasStrongLoginAuthContext(fullGroup)) {
470
462
  return null;
471
463
  }
472
- const targetByRef = new Map(fullGroup.targets.map((target) => [target.ref, target]));
473
- const fields = [
474
- {
475
- fieldKey: 'username',
476
- targetRef: [...usernameRefs][0],
477
- label: targetByRef.get([...usernameRefs][0])?.displayLabel ??
478
- targetByRef.get([...usernameRefs][0])?.label,
479
- required: targetByRef.get([...usernameRefs][0])?.validation?.required,
480
- valueHint: 'direct',
481
- },
482
- {
483
- fieldKey: 'password',
484
- targetRef: [...passwordRefs][0],
485
- label: targetByRef.get([...passwordRefs][0])?.displayLabel ??
486
- targetByRef.get([...passwordRefs][0])?.label,
487
- required: targetByRef.get([...passwordRefs][0])?.validation?.required,
488
- valueHint: 'direct',
489
- },
490
- ];
491
- return {
492
- confidence: 'high',
493
- fields,
494
- };
464
+ const [onlyTarget] = plannerGroup.targets;
465
+ if (!onlyTarget) {
466
+ return null;
467
+ }
468
+ if (usernameRefs?.size === 1 && !passwordRefs) {
469
+ return {
470
+ confidence: 'medium',
471
+ fields: [loginFieldBindingForTarget('username', onlyTarget)],
472
+ loginStep: 'identifier',
473
+ };
474
+ }
475
+ if (!usernameRefs && passwordRefs?.size === 1) {
476
+ return {
477
+ confidence: 'medium',
478
+ fields: [loginFieldBindingForTarget('password', onlyTarget)],
479
+ loginStep: 'password',
480
+ };
481
+ }
482
+ return null;
495
483
  }
496
484
  function loginPlannerGroup(group) {
497
485
  const relevantTargets = group.targets.filter((target) => targetLooksLikeLoginUsernameTarget(target) || targetLooksLikeLoginPasswordTarget(target));
@@ -499,7 +487,7 @@ function loginPlannerGroup(group) {
499
487
  return null;
500
488
  }
501
489
  const hasPasswordAnchor = relevantTargets.some((target) => targetLooksLikeLoginPasswordTarget(target));
502
- if (!hasPasswordAnchor) {
490
+ if (!hasPasswordAnchor && !(relevantTargets.length === 1 && group.targets.length === 1)) {
503
491
  return null;
504
492
  }
505
493
  return {
@@ -577,6 +565,9 @@ function formGroupKeyOf(target) {
577
565
  if (selectorRoot) {
578
566
  return `selector-root:${selectorRoot}`;
579
567
  }
568
+ if (target.pageFormSelector) {
569
+ return `page-form:${target.pageFormSelector}`;
570
+ }
580
571
  if (target.surfaceRef) {
581
572
  return `surface:${target.surfaceRef}`;
582
573
  }
@@ -733,13 +724,19 @@ function sanitizeBindings(kind, group, bindings) {
733
724
  return left.targetRef.localeCompare(right.targetRef);
734
725
  });
735
726
  }
736
- function isCredibleKindMatch(kind, fieldBindings) {
727
+ function isCredibleKindMatch(kind, fieldBindings, loginStep) {
737
728
  if (fieldBindings.length === 0) {
738
729
  return false;
739
730
  }
740
731
  const fieldKeys = new Set(fieldBindings.map((field) => field.fieldKey));
741
732
  switch (kind) {
742
733
  case 'login':
734
+ if (loginStep === 'identifier') {
735
+ return fieldKeys.size === 1 && fieldKeys.has('username');
736
+ }
737
+ if (loginStep === 'password') {
738
+ return fieldKeys.size === 1 && fieldKeys.has('password');
739
+ }
743
740
  return fieldKeys.has('username') && fieldKeys.has('password');
744
741
  case 'payment_card':
745
742
  return fieldKeys.has('pan') && fieldKeys.has('exp_month') && fieldKeys.has('exp_year');
@@ -761,12 +758,6 @@ function isCredibleKindMatch(kind, fieldBindings) {
761
758
  }
762
759
  }
763
760
  }
764
- function frameKeyOf(target) {
765
- if (!target.framePath || target.framePath.length === 0) {
766
- return null;
767
- }
768
- return target.framePath.join('>');
769
- }
770
761
  function targetLooksLikeIframeEnrollmentField(target) {
771
762
  const autocomplete = normalizeText(target.autocomplete);
772
763
  const signals = signalValuesOf(target);
@@ -818,6 +809,16 @@ function formScopeRef(fields, targetByRef) {
818
809
  }
819
810
  return surfaceRefs.size === 1 ? [...surfaceRefs][0] : undefined;
820
811
  }
812
+ function formPageFormSelector(fields, targetByRef) {
813
+ const selectors = new Set();
814
+ for (const field of fields) {
815
+ const pageFormSelector = targetByRef.get(field.targetRef)?.pageFormSelector?.trim();
816
+ if (pageFormSelector) {
817
+ selectors.add(pageFormSelector);
818
+ }
819
+ }
820
+ return selectors.size === 1 ? [...selectors][0] : undefined;
821
+ }
821
822
  function buildStoredSecretCandidates(catalog, kind, confidence, matchedFieldKeys) {
822
823
  return catalog.storedSecrets
823
824
  .filter((secret) => secret.kind === kind && secret.fieldKeys.some((fieldKey) => matchedFieldKeys.has(fieldKey)))
@@ -842,12 +843,15 @@ function purposesByPriority(catalog) {
842
843
  }
843
844
  return ORDERED_PROTECTED_KINDS.filter((kind) => hasApplicableStoredSecretKind(catalog, kind));
844
845
  }
845
- async function planBindingsForGroup(getClient, kind, plannerGroup, fullGroup) {
846
+ async function planBindingsForGroup(getClient, kind, plannerGroup, fullGroup, catalog) {
846
847
  if (kind === 'login') {
847
- const deterministicPlan = deterministicLoginPlan(plannerGroup, fullGroup);
848
+ const deterministicPlan = deterministicLoginPlan(plannerGroup, fullGroup, catalog);
848
849
  if (deterministicPlan) {
849
850
  return deterministicPlan;
850
851
  }
852
+ if (plannerGroup.targets.length === 1 && fullGroup.targets.length === 1) {
853
+ return null;
854
+ }
851
855
  }
852
856
  if (kind === 'payment_card') {
853
857
  const deterministicPlan = deterministicPaymentCardPlan(plannerGroup, fullGroup);
@@ -913,11 +917,11 @@ export async function matchStoredSecretsToObservedTargets(pageRef, targets, cata
913
917
  const forms = [];
914
918
  for (const group of groups) {
915
919
  for (const { kind, plannerGroup } of plannerGroupsForGroup(group, catalog)) {
916
- const planned = await planBindingsForGroup(catalog || kind === 'payment_card' ? getClient : null, kind, plannerGroup, group).catch(() => null);
920
+ const planned = await planBindingsForGroup(catalog || kind === 'payment_card' ? getClient : null, kind, plannerGroup, group, catalog).catch(() => null);
917
921
  if (!planned || planned.confidence === 'low') {
918
922
  continue;
919
923
  }
920
- if (!isCredibleKindMatch(kind, planned.fields)) {
924
+ if (!isCredibleKindMatch(kind, planned.fields, planned.loginStep)) {
921
925
  continue;
922
926
  }
923
927
  const matchedFieldKeys = new Set(planned.fields.map((field) => field.fieldKey));
@@ -927,7 +931,9 @@ export async function matchStoredSecretsToObservedTargets(pageRef, targets, cata
927
931
  forms.push({
928
932
  pageRef,
929
933
  scopeRef: formScopeRef(planned.fields, targetByRef),
934
+ pageFormSelector: formPageFormSelector(planned.fields, targetByRef),
930
935
  purpose: kind,
936
+ ...(kind === 'login' ? { loginStep: planned.loginStep ?? 'full' } : {}),
931
937
  fields: planned.fields,
932
938
  storedSecretCandidates,
933
939
  observedAt: nextObservedAt,
@@ -0,0 +1,13 @@
1
+ import type { TargetDescriptor } from '../runtime-state.js';
2
+ export declare function normalizeText(value: string | undefined): string;
3
+ export declare function normalizeFieldKey(value: string | undefined): string;
4
+ export declare function canonicalFieldKeyOf(value: string | undefined): string;
5
+ export declare function fieldKeyEvidenceTerms(value: string | undefined): string[];
6
+ export declare function selectorSignalFragments(selector: string): string[];
7
+ export declare function selectorRootOf(target: Pick<TargetDescriptor, 'context'>): string | null;
8
+ export declare function signalValuesOf(target: TargetDescriptor, options?: {
9
+ maxChars?: number;
10
+ }): string[];
11
+ export declare function directFieldSignalValuesOf(target: TargetDescriptor): string[];
12
+ export declare function frameKeyOf(target: Pick<TargetDescriptor, 'framePath'>): string | null;
13
+ //# sourceMappingURL=matching-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matching-helpers.d.ts","sourceRoot":"","sources":["../../src/secrets/matching-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAmC5D,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAE/D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAInE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAOrE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAuBzE;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CASlE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,CAkBvF;AAqBD,wBAAgB,cAAc,CAC5B,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,MAAM,EAAE,CA4BV;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAY5E;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,GAAG,MAAM,GAAG,IAAI,CAMrF"}
@@ -0,0 +1,147 @@
1
+ const TARGET_SIGNAL_MAX_CHARS = 220;
2
+ const GENERIC_SHELL_SELECTOR_ROOTS = new Set(['#__next', '#root', '#app', '#app-root']);
3
+ const CANONICAL_FIELD_KEY_VARIANTS = {
4
+ email: ['email', 'e_mail', 'email_address', 'account_email', 'contact_email'],
5
+ first_name: ['first_name', 'given_name', 'forename'],
6
+ last_name: ['last_name', 'family_name', 'surname'],
7
+ full_name: ['full_name'],
8
+ date_of_birth: ['date_of_birth', 'birth_date', 'dob', 'birthday'],
9
+ phone: ['phone', 'phone_number', 'mobile'],
10
+ country: ['country', 'country_region'],
11
+ nationality: ['nationality', 'citizenship'],
12
+ };
13
+ const CANONICAL_FIELD_KEY_LABELS = {
14
+ email: ['email', 'email address', 'account email', 'contact email'],
15
+ first_name: ['first name', 'given name', 'forename'],
16
+ last_name: ['last name', 'family name', 'surname'],
17
+ full_name: ['full name'],
18
+ date_of_birth: ['date of birth', 'birth date', 'dob', 'birthday'],
19
+ phone: ['phone', 'phone number', 'mobile'],
20
+ country: ['country', 'country region'],
21
+ nationality: ['nationality', 'citizenship'],
22
+ };
23
+ const FIELD_KEY_CANONICAL_MAP = new Map();
24
+ for (const [canonicalFieldKey, variants] of Object.entries(CANONICAL_FIELD_KEY_VARIANTS)) {
25
+ FIELD_KEY_CANONICAL_MAP.set(canonicalFieldKey, canonicalFieldKey);
26
+ for (const variant of variants) {
27
+ FIELD_KEY_CANONICAL_MAP.set(variant, canonicalFieldKey);
28
+ }
29
+ }
30
+ export function normalizeText(value) {
31
+ return value?.replace(/\s+/g, ' ').trim().toLowerCase() ?? '';
32
+ }
33
+ export function normalizeFieldKey(value) {
34
+ return normalizeText(value)
35
+ .replace(/[^a-z0-9]+/g, '_')
36
+ .replace(/^_+|_+$/g, '');
37
+ }
38
+ export function canonicalFieldKeyOf(value) {
39
+ const normalized = normalizeFieldKey(value);
40
+ if (!normalized) {
41
+ return '';
42
+ }
43
+ return FIELD_KEY_CANONICAL_MAP.get(normalized) ?? normalized;
44
+ }
45
+ export function fieldKeyEvidenceTerms(value) {
46
+ const terms = new Set();
47
+ const normalized = normalizeFieldKey(value);
48
+ const canonical = canonicalFieldKeyOf(value);
49
+ const push = (candidate) => {
50
+ const next = normalizeText(candidate);
51
+ if (next) {
52
+ terms.add(next);
53
+ }
54
+ };
55
+ if (normalized) {
56
+ push(normalized.replace(/_/g, ' '));
57
+ }
58
+ if (canonical) {
59
+ push(canonical.replace(/_/g, ' '));
60
+ }
61
+ for (const label of CANONICAL_FIELD_KEY_LABELS[canonical] ?? []) {
62
+ push(label);
63
+ }
64
+ return [...terms];
65
+ }
66
+ export function selectorSignalFragments(selector) {
67
+ const fragments = [];
68
+ for (const match of selector.matchAll(/\[name="([^"]+)"\]/g)) {
69
+ fragments.push(match[1] ?? '');
70
+ }
71
+ for (const match of selector.matchAll(/#([a-zA-Z0-9_-]+)/g)) {
72
+ fragments.push(match[1] ?? '');
73
+ }
74
+ return fragments;
75
+ }
76
+ export function selectorRootOf(target) {
77
+ const selectors = [
78
+ target.context?.item?.selector,
79
+ target.context?.container?.selector,
80
+ target.context?.landmark?.selector,
81
+ ].filter((value) => typeof value === 'string' && value.length > 0);
82
+ for (const selector of selectors) {
83
+ for (const match of selector.matchAll(/#[A-Za-z0-9_-]+/g)) {
84
+ const candidate = match[0]?.trim().toLowerCase();
85
+ if (!candidate || GENERIC_SHELL_SELECTOR_ROOTS.has(candidate)) {
86
+ continue;
87
+ }
88
+ return match[0];
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+ function pushSignalValue(values, value, options = {}) {
94
+ const normalized = normalizeText(value);
95
+ if (!normalized) {
96
+ return;
97
+ }
98
+ const maxChars = options.maxChars;
99
+ if (maxChars && normalized.length > maxChars) {
100
+ values.add(`${normalized.slice(0, maxChars - 1)}…`);
101
+ return;
102
+ }
103
+ values.add(normalized);
104
+ }
105
+ export function signalValuesOf(target, options = {}) {
106
+ const values = new Set();
107
+ const maxChars = options.maxChars ?? TARGET_SIGNAL_MAX_CHARS;
108
+ pushSignalValue(values, target.label, { maxChars });
109
+ pushSignalValue(values, target.displayLabel, { maxChars });
110
+ pushSignalValue(values, target.kind, { maxChars });
111
+ pushSignalValue(values, target.semantics?.role, { maxChars });
112
+ pushSignalValue(values, target.semantics?.name, { maxChars });
113
+ pushSignalValue(values, target.context?.hintText, { maxChars });
114
+ pushSignalValue(values, target.context?.group?.label, { maxChars });
115
+ pushSignalValue(values, target.context?.group?.text, { maxChars });
116
+ pushSignalValue(values, target.context?.container?.label, { maxChars });
117
+ pushSignalValue(values, target.context?.container?.text, { maxChars });
118
+ pushSignalValue(values, target.context?.landmark?.label, { maxChars });
119
+ pushSignalValue(values, target.context?.landmark?.text, { maxChars });
120
+ for (const candidate of target.locatorCandidates) {
121
+ pushSignalValue(values, candidate.name, { maxChars });
122
+ pushSignalValue(values, candidate.value, { maxChars });
123
+ if (candidate.strategy === 'css' || candidate.strategy === 'xpath') {
124
+ for (const fragment of selectorSignalFragments(candidate.value)) {
125
+ pushSignalValue(values, fragment, { maxChars });
126
+ }
127
+ }
128
+ }
129
+ return [...values];
130
+ }
131
+ export function directFieldSignalValuesOf(target) {
132
+ const values = new Set();
133
+ pushSignalValue(values, target.label);
134
+ pushSignalValue(values, target.displayLabel);
135
+ pushSignalValue(values, target.placeholder);
136
+ pushSignalValue(values, target.inputName);
137
+ pushSignalValue(values, target.inputType);
138
+ pushSignalValue(values, target.autocomplete);
139
+ pushSignalValue(values, target.semantics?.name);
140
+ return [...values];
141
+ }
142
+ export function frameKeyOf(target) {
143
+ if (!target.framePath || target.framePath.length === 0) {
144
+ return null;
145
+ }
146
+ return target.framePath.join('>');
147
+ }
@@ -0,0 +1,43 @@
1
+ import type { TargetDescriptor } from '../runtime-state.js';
2
+ export type ObservedFieldValueType = 'text' | 'secret' | 'date' | 'number' | 'email' | 'url';
3
+ export type ObservedFieldValueSource = 'profile_facts' | 'session_open_value';
4
+ export interface ObservedFieldCandidate {
5
+ candidateRef: string;
6
+ fieldKey: string;
7
+ value: unknown;
8
+ source: ObservedFieldValueSource;
9
+ type?: ObservedFieldValueType;
10
+ label?: string;
11
+ semanticTags?: string[];
12
+ applicability?: {
13
+ target: 'global' | 'host';
14
+ value?: string;
15
+ };
16
+ pageFormSelector?: string;
17
+ selectorRoot?: string;
18
+ }
19
+ export interface ResolveObservedFieldOptions {
20
+ host?: string;
21
+ protectedTargetRefs?: ReadonlySet<string>;
22
+ minimumScore?: number;
23
+ clearWinnerDelta?: number;
24
+ }
25
+ export type ResolveObservedFieldResult = {
26
+ status: 'matched';
27
+ targetRef: string;
28
+ fieldKey: string;
29
+ value: string | number;
30
+ source: ObservedFieldValueSource;
31
+ confidence: 'high' | 'medium';
32
+ candidateRef: string;
33
+ } | {
34
+ status: 'no_match';
35
+ targetRef: string;
36
+ reason: 'protected_target' | 'no_candidate' | 'scope_ineligible' | 'incompatible_shape' | 'low_confidence';
37
+ } | {
38
+ status: 'ambiguous';
39
+ targetRef: string;
40
+ candidates: string[];
41
+ };
42
+ export declare function resolveObservedField(target: TargetDescriptor, candidates: ReadonlyArray<ObservedFieldCandidate>, options?: ResolveObservedFieldOptions): ResolveObservedFieldResult;
43
+ //# sourceMappingURL=observed-field-resolution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observed-field-resolution.d.ts","sourceRoot":"","sources":["../../src/secrets/observed-field-resolution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAU5D,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;AAC7F,MAAM,MAAM,wBAAwB,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAE9E,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,wBAAwB,CAAC;IACjC,IAAI,CAAC,EAAE,sBAAsB,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE;QACd,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mBAAmB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,0BAA0B,GAClC;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,MAAM,EAAE,wBAAwB,CAAC;IACjC,UAAU,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;CACtB,GACD;IACE,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EACF,kBAAkB,GAClB,cAAc,GACd,kBAAkB,GAClB,oBAAoB,GACpB,gBAAgB,CAAC;CACtB,GACD;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AA2ON,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,aAAa,CAAC,sBAAsB,CAAC,EACjD,OAAO,GAAE,2BAAgC,GACxC,0BAA0B,CAsE5B"}