@qontinui/ui-bridge 0.3.0 → 0.3.1

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 (127) hide show
  1. package/dist/ai/index.d.mts +312 -155
  2. package/dist/ai/index.d.ts +312 -155
  3. package/dist/ai/index.js +2363 -67
  4. package/dist/ai/index.js.map +1 -1
  5. package/dist/ai/index.mjs +2328 -68
  6. package/dist/ai/index.mjs.map +1 -1
  7. package/dist/annotations/index.d.mts +218 -0
  8. package/dist/annotations/index.d.ts +218 -0
  9. package/dist/annotations/index.js +246 -0
  10. package/dist/annotations/index.js.map +1 -0
  11. package/dist/annotations/index.mjs +241 -0
  12. package/dist/annotations/index.mjs.map +1 -0
  13. package/dist/assertions-BSR3afVr.d.ts +161 -0
  14. package/dist/assertions-CTw1hfOx.d.mts +161 -0
  15. package/dist/babel-plugin/index.js +23 -34
  16. package/dist/babel-plugin/index.js.map +1 -1
  17. package/dist/babel-plugin/index.mjs +23 -34
  18. package/dist/babel-plugin/index.mjs.map +1 -1
  19. package/dist/browser-capture-Bms60T6f.d.mts +47 -0
  20. package/dist/browser-capture-CsTU29mb.d.ts +47 -0
  21. package/dist/control/index.d.mts +26 -7
  22. package/dist/control/index.d.ts +26 -7
  23. package/dist/control/index.js +276 -48
  24. package/dist/control/index.js.map +1 -1
  25. package/dist/control/index.mjs +276 -48
  26. package/dist/control/index.mjs.map +1 -1
  27. package/dist/core/index.d.mts +2 -2
  28. package/dist/core/index.d.ts +2 -2
  29. package/dist/core/index.js.map +1 -1
  30. package/dist/core/index.mjs.map +1 -1
  31. package/dist/debug/index.d.mts +5 -3
  32. package/dist/debug/index.d.ts +5 -3
  33. package/dist/debug/index.js +925 -1
  34. package/dist/debug/index.js.map +1 -1
  35. package/dist/debug/index.mjs +924 -2
  36. package/dist/debug/index.mjs.map +1 -1
  37. package/dist/index.d.mts +12 -7
  38. package/dist/index.d.ts +12 -7
  39. package/dist/index.js +4720 -173
  40. package/dist/index.js.map +1 -1
  41. package/dist/index.mjs +4656 -174
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/{metrics-DTA2bwG7.d.mts → metrics-DuA2qIIz.d.mts} +2 -2
  44. package/dist/{metrics-BfiT_rhZ.d.ts → metrics-KFAAKNEB.d.ts} +2 -2
  45. package/dist/native/control/index.js +2 -7
  46. package/dist/native/control/index.js.map +1 -1
  47. package/dist/native/control/index.mjs +2 -7
  48. package/dist/native/control/index.mjs.map +1 -1
  49. package/dist/native/core/index.js.map +1 -1
  50. package/dist/native/core/index.mjs.map +1 -1
  51. package/dist/native/debug/index.js +23 -66
  52. package/dist/native/debug/index.js.map +1 -1
  53. package/dist/native/debug/index.mjs +23 -66
  54. package/dist/native/debug/index.mjs.map +1 -1
  55. package/dist/native/index.js +89 -131
  56. package/dist/native/index.js.map +1 -1
  57. package/dist/native/index.mjs +89 -131
  58. package/dist/native/index.mjs.map +1 -1
  59. package/dist/native/react/index.js +28 -52
  60. package/dist/native/react/index.js.map +1 -1
  61. package/dist/native/react/index.mjs +28 -52
  62. package/dist/native/react/index.mjs.map +1 -1
  63. package/dist/native/server/index.js +38 -13
  64. package/dist/native/server/index.js.map +1 -1
  65. package/dist/native/server/index.mjs +38 -13
  66. package/dist/native/server/index.mjs.map +1 -1
  67. package/dist/react/index.d.mts +107 -8
  68. package/dist/react/index.d.ts +107 -8
  69. package/dist/react/index.js +2194 -84
  70. package/dist/react/index.js.map +1 -1
  71. package/dist/react/index.mjs +2194 -85
  72. package/dist/react/index.mjs.map +1 -1
  73. package/dist/{registry-BKLEm-yk.d.ts → registry-C6dDtn1v.d.ts} +27 -2
  74. package/dist/{registry-BmZgyCz8.d.mts → registry-POtcxnal.d.mts} +27 -2
  75. package/dist/render-log/index.d.mts +1 -1
  76. package/dist/render-log/index.d.ts +1 -1
  77. package/dist/server/express.d.mts +5 -4
  78. package/dist/server/express.d.ts +5 -4
  79. package/dist/server/express.js +104 -2
  80. package/dist/server/express.js.map +1 -1
  81. package/dist/server/express.mjs +104 -2
  82. package/dist/server/express.mjs.map +1 -1
  83. package/dist/server/handlers.d.mts +36 -5
  84. package/dist/server/handlers.d.ts +36 -5
  85. package/dist/server/handlers.js +3129 -224
  86. package/dist/server/handlers.js.map +1 -1
  87. package/dist/server/handlers.mjs +3129 -224
  88. package/dist/server/handlers.mjs.map +1 -1
  89. package/dist/server/index.d.mts +7 -5
  90. package/dist/server/index.d.ts +7 -5
  91. package/dist/server/index.js +3215 -183
  92. package/dist/server/index.js.map +1 -1
  93. package/dist/server/index.mjs +3215 -183
  94. package/dist/server/index.mjs.map +1 -1
  95. package/dist/server/nextjs.d.mts +6 -4
  96. package/dist/server/nextjs.d.ts +6 -4
  97. package/dist/server/nextjs.js +106 -3
  98. package/dist/server/nextjs.js.map +1 -1
  99. package/dist/server/nextjs.mjs +106 -3
  100. package/dist/server/nextjs.mjs.map +1 -1
  101. package/dist/server/standalone.d.mts +6 -5
  102. package/dist/server/standalone.d.ts +6 -5
  103. package/dist/server/standalone.js +131 -5
  104. package/dist/server/standalone.js.map +1 -1
  105. package/dist/server/standalone.mjs +131 -5
  106. package/dist/server/standalone.mjs.map +1 -1
  107. package/dist/specs/index.d.mts +365 -0
  108. package/dist/specs/index.d.ts +365 -0
  109. package/dist/specs/index.js +2809 -0
  110. package/dist/specs/index.js.map +1 -0
  111. package/dist/specs/index.mjs +2786 -0
  112. package/dist/specs/index.mjs.map +1 -0
  113. package/dist/{standalone-BURj8J3G.d.ts → standalone-B6GLIEmR.d.ts} +6 -2
  114. package/dist/{standalone-Dwmel29d.d.mts → standalone-CjdYqj3P.d.mts} +6 -2
  115. package/dist/{types-CHnlwiTK.d.ts → types-B2EfvEaq.d.ts} +83 -3
  116. package/dist/{types-B7J7noLK.d.mts → types-C7gVYRnF.d.ts} +72 -2
  117. package/dist/{types-BkNRILUa.d.ts → types-CJGrBEhC.d.mts} +72 -2
  118. package/dist/types-CebMQj76.d.ts +1275 -0
  119. package/dist/types-D_ypYl3T.d.mts +1275 -0
  120. package/dist/types-UBtp7R0u.d.mts +132 -0
  121. package/dist/types-UBtp7R0u.d.ts +132 -0
  122. package/dist/{types-CEQLnFMv.d.mts → types-gO696T_t.d.mts} +83 -3
  123. package/dist/{types-jKVgTI6_.d.mts → types-suaYwWWg.d.mts} +173 -2
  124. package/dist/{types-jKVgTI6_.d.ts → types-suaYwWWg.d.ts} +173 -2
  125. package/package.json +18 -2
  126. package/dist/types-B5Q0GVo0.d.mts +0 -646
  127. package/dist/types-DfPqwU-i.d.ts +0 -646
@@ -421,17 +421,72 @@ function generateDescription(input) {
421
421
  }
422
422
  parts.push(`"${name}"`);
423
423
  }
424
- const typeWords = ELEMENT_ACTION_WORDS[input.elementType || ""] || [input.elementType || "element"];
424
+ const typeWords = ELEMENT_ACTION_WORDS[input.elementType || ""] || [
425
+ input.elementType || "element"
426
+ ];
425
427
  parts.push(typeWords[0]);
426
428
  if (input.inputType && input.inputType !== "text") {
427
429
  parts.push(`(${input.inputType})`);
428
430
  }
429
431
  return parts.join(" ");
430
432
  }
433
+ var CONTENT_TYPES = /* @__PURE__ */ new Set([
434
+ "heading",
435
+ "paragraph",
436
+ "list-item",
437
+ "table-cell",
438
+ "table-header",
439
+ "label",
440
+ "caption",
441
+ "blockquote",
442
+ "code-block",
443
+ "badge",
444
+ "status-message",
445
+ "metric-value",
446
+ "description-text",
447
+ "nav-text",
448
+ "content-generic"
449
+ ]);
431
450
  function generatePurpose(input) {
432
451
  const text = (input.textContent || input.ariaLabel || input.title || "").toLowerCase();
433
452
  const type = input.elementType?.toLowerCase() || "";
434
453
  const inputType = input.inputType?.toLowerCase() || "";
454
+ if (CONTENT_TYPES.has(type)) {
455
+ switch (type) {
456
+ case "heading":
457
+ return "Section heading";
458
+ case "paragraph":
459
+ return "Body text content";
460
+ case "list-item":
461
+ return "List item";
462
+ case "table-cell":
463
+ return "Table data cell";
464
+ case "table-header":
465
+ return "Table column header";
466
+ case "label":
467
+ return "Field label or definition term";
468
+ case "caption":
469
+ return "Figure or table caption";
470
+ case "blockquote":
471
+ return "Quoted content";
472
+ case "code-block":
473
+ return "Code or preformatted text";
474
+ case "badge":
475
+ return "Status badge or tag";
476
+ case "status-message":
477
+ return "Dynamic status indicator";
478
+ case "metric-value":
479
+ return "Metric or statistic value";
480
+ case "description-text":
481
+ return "Description or definition";
482
+ case "nav-text":
483
+ return "Navigation label";
484
+ case "content-generic":
485
+ return "Text content";
486
+ default:
487
+ return "Static content";
488
+ }
489
+ }
435
490
  if (type === "button" || inputType === "submit") {
436
491
  if (text.match(/submit|send|save|confirm|ok|done|finish|apply/)) {
437
492
  return "Submits the form";
@@ -496,6 +551,10 @@ function generateSuggestedActions(input) {
496
551
  const inputType = input.inputType?.toLowerCase() || "";
497
552
  const text = (input.textContent || input.ariaLabel || "").toLowerCase();
498
553
  const actions = [];
554
+ if (CONTENT_TYPES.has(type)) {
555
+ actions.push("read text content", "verify text matches expected");
556
+ return actions;
557
+ }
499
558
  switch (type) {
500
559
  case "button":
501
560
  actions.push(`click "${text || "this button"}"`);
@@ -543,6 +602,241 @@ function areSynonyms(word1, word2) {
543
602
  return synonyms1.includes(w2) || synonyms2.includes(w1);
544
603
  }
545
604
 
605
+ // src/annotations/types.ts
606
+ var ANNOTATION_CONFIG_VERSION = "1.0.0";
607
+
608
+ // src/annotations/store.ts
609
+ var AnnotationStore = class {
610
+ constructor() {
611
+ this.store = /* @__PURE__ */ new Map();
612
+ this.listeners = /* @__PURE__ */ new Set();
613
+ }
614
+ /**
615
+ * Get an annotation by element ID.
616
+ */
617
+ get(elementId) {
618
+ return this.store.get(elementId);
619
+ }
620
+ /**
621
+ * Get all annotations as a record.
622
+ */
623
+ getAll() {
624
+ const result = {};
625
+ for (const [id, annotation] of this.store) {
626
+ result[id] = annotation;
627
+ }
628
+ return result;
629
+ }
630
+ /**
631
+ * Set an annotation for an element. Auto-sets `updatedAt`.
632
+ */
633
+ set(elementId, annotation) {
634
+ const updated = {
635
+ ...annotation,
636
+ updatedAt: Date.now()
637
+ };
638
+ this.store.set(elementId, updated);
639
+ this.emit({
640
+ type: "annotation:set",
641
+ elementId,
642
+ annotation: updated,
643
+ timestamp: Date.now()
644
+ });
645
+ }
646
+ /**
647
+ * Delete an annotation by element ID.
648
+ *
649
+ * @returns true if the annotation existed and was deleted
650
+ */
651
+ delete(elementId) {
652
+ const existed = this.store.delete(elementId);
653
+ if (existed) {
654
+ this.emit({
655
+ type: "annotation:deleted",
656
+ elementId,
657
+ timestamp: Date.now()
658
+ });
659
+ }
660
+ return existed;
661
+ }
662
+ /**
663
+ * Check if an annotation exists for an element.
664
+ */
665
+ has(elementId) {
666
+ return this.store.has(elementId);
667
+ }
668
+ /**
669
+ * Get the number of stored annotations.
670
+ */
671
+ get count() {
672
+ return this.store.size;
673
+ }
674
+ /**
675
+ * Clear all annotations.
676
+ */
677
+ clear() {
678
+ this.store.clear();
679
+ this.emit({
680
+ type: "annotation:cleared",
681
+ timestamp: Date.now()
682
+ });
683
+ }
684
+ /**
685
+ * Import annotations from a config object.
686
+ *
687
+ * Merges with existing annotations (new values overwrite per element ID).
688
+ *
689
+ * @returns Number of annotations imported
690
+ *
691
+ * @example
692
+ * ```ts
693
+ * const config: AnnotationConfig = {
694
+ * version: '1.0.0',
695
+ * annotations: {
696
+ * 'btn-1': { description: 'Submit button', tags: ['form'] },
697
+ * 'input-1': { description: 'Name field' },
698
+ * },
699
+ * };
700
+ * const count = store.importConfig(config); // 2
701
+ * ```
702
+ */
703
+ importConfig(config) {
704
+ let count = 0;
705
+ for (const [id, annotation] of Object.entries(config.annotations)) {
706
+ this.store.set(id, {
707
+ ...annotation,
708
+ updatedAt: annotation.updatedAt ?? Date.now()
709
+ });
710
+ count++;
711
+ }
712
+ this.emit({
713
+ type: "annotation:imported",
714
+ count,
715
+ timestamp: Date.now()
716
+ });
717
+ return count;
718
+ }
719
+ /**
720
+ * Export all annotations as a config object.
721
+ *
722
+ * The returned object can be serialized to JSON and saved to a file,
723
+ * then later re-imported with {@link importConfig}.
724
+ *
725
+ * @param metadata - Optional metadata to include (appName, description, etc.)
726
+ * @returns AnnotationConfig with all current annotations
727
+ *
728
+ * @example
729
+ * ```ts
730
+ * const config = store.exportConfig({ appName: 'MyApp' });
731
+ * // config.version === '1.0.0'
732
+ * // config.annotations === { 'btn-1': { ... }, 'input-1': { ... } }
733
+ * // config.metadata === { appName: 'MyApp', exportedAt: 1706900000000 }
734
+ *
735
+ * // Save to file
736
+ * fs.writeFileSync('annotations.json', JSON.stringify(config, null, 2));
737
+ * ```
738
+ */
739
+ exportConfig(metadata) {
740
+ return {
741
+ version: ANNOTATION_CONFIG_VERSION,
742
+ annotations: this.getAll(),
743
+ metadata: {
744
+ ...metadata,
745
+ exportedAt: Date.now()
746
+ }
747
+ };
748
+ }
749
+ /**
750
+ * Compute annotation coverage against a set of known element IDs.
751
+ *
752
+ * Compares the store's annotations against the provided list of element IDs
753
+ * to determine what percentage of elements have been annotated.
754
+ *
755
+ * @param allElementIds - Array of all known element IDs in the UI
756
+ * @returns Coverage statistics including percentages and lists of annotated/unannotated IDs
757
+ *
758
+ * @example
759
+ * ```ts
760
+ * store.set('btn-1', { description: 'Submit' });
761
+ * store.set('input-1', { description: 'Name' });
762
+ *
763
+ * const coverage = store.getCoverage(['btn-1', 'input-1', 'input-2', 'link-1']);
764
+ * // coverage.totalElements === 4
765
+ * // coverage.annotatedElements === 2
766
+ * // coverage.coveragePercent === 50
767
+ * // coverage.annotatedIds === ['btn-1', 'input-1']
768
+ * // coverage.unannotatedIds === ['input-2', 'link-1']
769
+ * ```
770
+ */
771
+ getCoverage(allElementIds) {
772
+ const annotatedIds = [];
773
+ const unannotatedIds = [];
774
+ for (const id of allElementIds) {
775
+ if (this.store.has(id)) {
776
+ annotatedIds.push(id);
777
+ } else {
778
+ unannotatedIds.push(id);
779
+ }
780
+ }
781
+ const total = allElementIds.length;
782
+ return {
783
+ totalElements: total,
784
+ annotatedElements: annotatedIds.length,
785
+ coveragePercent: total > 0 ? annotatedIds.length / total * 100 : 0,
786
+ annotatedIds,
787
+ unannotatedIds,
788
+ timestamp: Date.now()
789
+ };
790
+ }
791
+ /**
792
+ * Subscribe to annotation events.
793
+ *
794
+ * The listener is called whenever annotations are set, deleted, imported,
795
+ * or cleared. Returns an unsubscribe function to stop listening.
796
+ *
797
+ * @param listener - Callback function receiving {@link AnnotationEvent} objects
798
+ * @returns Unsubscribe function - call it to remove the listener
799
+ *
800
+ * @example
801
+ * ```ts
802
+ * const unsubscribe = store.on((event) => {
803
+ * if (event.type === 'annotation:set') {
804
+ * console.log(`Element ${event.elementId} annotated:`, event.annotation);
805
+ * }
806
+ * });
807
+ *
808
+ * store.set('btn-1', { description: 'Submit' });
809
+ * // Logs: "Element btn-1 annotated: { description: 'Submit', updatedAt: ... }"
810
+ *
811
+ * unsubscribe(); // Stop listening
812
+ * ```
813
+ */
814
+ on(listener) {
815
+ this.listeners.add(listener);
816
+ return () => {
817
+ this.listeners.delete(listener);
818
+ };
819
+ }
820
+ /**
821
+ * Emit an event to all listeners.
822
+ */
823
+ emit(event) {
824
+ for (const listener of this.listeners) {
825
+ try {
826
+ listener(event);
827
+ } catch {
828
+ }
829
+ }
830
+ }
831
+ };
832
+ var globalStore = null;
833
+ function getGlobalAnnotationStore() {
834
+ if (!globalStore) {
835
+ globalStore = new AnnotationStore();
836
+ }
837
+ return globalStore;
838
+ }
839
+
546
840
  // src/ai/search-engine.ts
547
841
  var DEFAULT_SEARCH_CONFIG = {
548
842
  fuzzyThreshold: 0.7,
@@ -585,17 +879,40 @@ var SearchEngine = class {
585
879
  if ("getState" in element && typeof element.getState === "function") {
586
880
  state = getState ? getState(element) : element.getState();
587
881
  textContent = state.textContent || void 0;
588
- tagName = element.element.tagName.toLowerCase();
589
- role = element.element.getAttribute("role") || void 0;
590
- ariaLabel = element.element.getAttribute("aria-label") || void 0;
591
- placeholder = element.element.getAttribute("placeholder") || void 0;
592
- title = element.element.getAttribute("title") || void 0;
593
- if (element.element.id) {
594
- const labelEl = document.querySelector(`label[for="${element.element.id}"]`);
595
- labelText = labelEl?.textContent?.trim() || void 0;
596
- }
597
- if (element.element instanceof HTMLInputElement || element.element instanceof HTMLTextAreaElement || element.element instanceof HTMLSelectElement) {
598
- value = element.element.value || void 0;
882
+ try {
883
+ tagName = element.element.tagName.toLowerCase();
884
+ } catch {
885
+ tagName = element.type || "unknown";
886
+ }
887
+ try {
888
+ role = element.element.getAttribute("role") || void 0;
889
+ ariaLabel = element.element.getAttribute("aria-label") || void 0;
890
+ placeholder = element.element.getAttribute("placeholder") || void 0;
891
+ title = element.element.getAttribute("title") || void 0;
892
+ } catch {
893
+ }
894
+ if (!ariaLabel && element.label) {
895
+ ariaLabel = element.label;
896
+ }
897
+ try {
898
+ if (element.element.id) {
899
+ const labelEl = document.querySelector(`label[for="${element.element.id}"]`);
900
+ labelText = labelEl?.textContent?.trim() || void 0;
901
+ }
902
+ } catch {
903
+ }
904
+ if (!labelText && element.label) {
905
+ labelText = element.label;
906
+ }
907
+ if (!textContent && element.label) {
908
+ textContent = element.label;
909
+ }
910
+ try {
911
+ if (element.element instanceof HTMLInputElement || element.element instanceof HTMLTextAreaElement || element.element instanceof HTMLSelectElement) {
912
+ value = element.element.value || void 0;
913
+ }
914
+ } catch {
915
+ value = state.value || void 0;
599
916
  }
600
917
  } else {
601
918
  const discovered = element;
@@ -604,8 +921,11 @@ var SearchEngine = class {
604
921
  tagName = discovered.tagName;
605
922
  role = discovered.role || void 0;
606
923
  ariaLabel = discovered.accessibleName || void 0;
924
+ if (!labelText && element.label) {
925
+ labelText = element.label;
926
+ }
607
927
  }
608
- const aliases = generateAliases({
928
+ let aliases = generateAliases({
609
929
  textContent,
610
930
  ariaLabel,
611
931
  placeholder,
@@ -615,7 +935,14 @@ var SearchEngine = class {
615
935
  labelText,
616
936
  value
617
937
  });
618
- const description = generateDescription({
938
+ if ("aliases" in element && Array.isArray(element.aliases) && element.aliases.length > 0) {
939
+ const aliasSet = /* @__PURE__ */ new Set([
940
+ ...aliases,
941
+ ...element.aliases.map((a) => a.toLowerCase())
942
+ ]);
943
+ aliases = [...aliasSet];
944
+ }
945
+ let description = generateDescription({
619
946
  textContent,
620
947
  ariaLabel,
621
948
  placeholder,
@@ -624,6 +951,22 @@ var SearchEngine = class {
624
951
  id: element.id,
625
952
  labelText
626
953
  });
954
+ if (!description && "description" in element && element.description) {
955
+ description = element.description;
956
+ }
957
+ const annotation = getGlobalAnnotationStore().get(element.id);
958
+ if (annotation) {
959
+ if (annotation.description) {
960
+ description = annotation.description;
961
+ }
962
+ if (annotation.tags && annotation.tags.length > 0) {
963
+ const tagSet = /* @__PURE__ */ new Set([...aliases, ...annotation.tags.map((t) => t.toLowerCase())]);
964
+ aliases = [...tagSet];
965
+ }
966
+ if (annotation.notes) {
967
+ aliases.push(annotation.notes.toLowerCase());
968
+ }
969
+ }
627
970
  return {
628
971
  id: element.id,
629
972
  element,
@@ -726,7 +1069,12 @@ var SearchEngine = class {
726
1069
  threshold: criteria.fuzzyThreshold ?? this.config.fuzzyThreshold
727
1070
  };
728
1071
  if (criteria.text) {
729
- const textScore = this.scoreTextMatch(searchable, criteria.text, criteria.fuzzy !== false, fuzzyConfig.threshold);
1072
+ const textScore = this.scoreTextMatch(
1073
+ searchable,
1074
+ criteria.text,
1075
+ criteria.fuzzy !== false,
1076
+ fuzzyConfig.threshold
1077
+ );
730
1078
  scores.text = textScore.score;
731
1079
  if (textScore.score > 0) {
732
1080
  matchReasons.push(...textScore.reasons);
@@ -734,8 +1082,37 @@ var SearchEngine = class {
734
1082
  weightedScore += textScore.score * this.config.textWeight;
735
1083
  totalWeight += this.config.textWeight;
736
1084
  }
1085
+ if (criteria.textContent && !criteria.text) {
1086
+ const alternatives = criteria.textContent.includes("|") ? criteria.textContent.split("|").map((s) => s.trim()).filter(Boolean) : [criteria.textContent];
1087
+ let bestScore = 0;
1088
+ let bestReasons = [];
1089
+ for (const alt of alternatives) {
1090
+ const exactScore = this.scoreTextMatch(
1091
+ searchable,
1092
+ alt,
1093
+ criteria.fuzzy !== false,
1094
+ fuzzyConfig.threshold
1095
+ );
1096
+ const containsScore = this.scoreContainsMatch(searchable, alt, criteria.fuzzy !== false);
1097
+ const altBest = Math.max(exactScore.score, containsScore.score);
1098
+ if (altBest > bestScore) {
1099
+ bestScore = altBest;
1100
+ bestReasons = exactScore.score >= containsScore.score ? exactScore.reasons : containsScore.reasons;
1101
+ }
1102
+ }
1103
+ scores.text = bestScore;
1104
+ if (bestScore > 0) {
1105
+ matchReasons.push(...bestReasons);
1106
+ }
1107
+ weightedScore += bestScore * this.config.textWeight;
1108
+ totalWeight += this.config.textWeight;
1109
+ }
737
1110
  if (criteria.textContains) {
738
- const containsScore = this.scoreContainsMatch(searchable, criteria.textContains, criteria.fuzzy !== false);
1111
+ const containsScore = this.scoreContainsMatch(
1112
+ searchable,
1113
+ criteria.textContains,
1114
+ criteria.fuzzy !== false
1115
+ );
739
1116
  scores.text = Math.max(scores.text || 0, containsScore.score);
740
1117
  if (containsScore.score > 0 && containsScore.reasons.length > 0) {
741
1118
  matchReasons.push(...containsScore.reasons);
@@ -784,7 +1161,11 @@ var SearchEngine = class {
784
1161
  totalWeight += this.config.spatialWeight;
785
1162
  }
786
1163
  if (criteria.placeholder && searchable.placeholder) {
787
- const placeholderResult = fuzzyMatch(searchable.placeholder, criteria.placeholder, fuzzyConfig);
1164
+ const placeholderResult = fuzzyMatch(
1165
+ searchable.placeholder,
1166
+ criteria.placeholder,
1167
+ fuzzyConfig
1168
+ );
788
1169
  if (placeholderResult.isMatch) {
789
1170
  matchReasons.push(`placeholder matches`);
790
1171
  weightedScore += placeholderResult.similarity * this.config.textWeight;
@@ -829,11 +1210,9 @@ var SearchEngine = class {
829
1210
  scoreTextMatch(searchable, text, fuzzy, threshold) {
830
1211
  const reasons = [];
831
1212
  let maxScore = 0;
832
- const textsToMatch = [
833
- searchable.textContent,
834
- searchable.labelText,
835
- searchable.value
836
- ].filter(Boolean);
1213
+ const textsToMatch = [searchable.textContent, searchable.labelText, searchable.value].filter(
1214
+ Boolean
1215
+ );
837
1216
  for (const targetText of textsToMatch) {
838
1217
  if (targetText.toLowerCase() === text.toLowerCase()) {
839
1218
  maxScore = Math.max(maxScore, 1);
@@ -929,7 +1308,9 @@ var SearchEngine = class {
929
1308
  heading: ["h1", "h2", "h3", "h4", "h5", "h6"]
930
1309
  };
931
1310
  const inferredRoles = tagRoleMap[normalizedRole] || [];
932
- if (inferredRoles.some((r) => searchable.tagName === r || searchable.type.toLowerCase() === normalizedRole)) {
1311
+ if (inferredRoles.some(
1312
+ (r) => searchable.tagName === r || searchable.type.toLowerCase() === normalizedRole
1313
+ )) {
933
1314
  return { score: 0.8, reasons: [`inferred role: ${role}`] };
934
1315
  }
935
1316
  return { score: 0, reasons };
@@ -1116,11 +1497,14 @@ function generatePageSummary(elements, pageContext, config = {}) {
1116
1497
  if (finalConfig.includeElementCounts) {
1117
1498
  const counts = countElementTypes(elements);
1118
1499
  const countParts = [];
1119
- if (counts.button > 0) countParts.push(`${counts.button} button${counts.button > 1 ? "s" : ""}`);
1500
+ if (counts.button > 0)
1501
+ countParts.push(`${counts.button} button${counts.button > 1 ? "s" : ""}`);
1120
1502
  if (counts.input > 0) countParts.push(`${counts.input} input${counts.input > 1 ? "s" : ""}`);
1121
1503
  if (counts.link > 0) countParts.push(`${counts.link} link${counts.link > 1 ? "s" : ""}`);
1122
- if (counts.select > 0) countParts.push(`${counts.select} dropdown${counts.select > 1 ? "s" : ""}`);
1123
- if (counts.checkbox > 0) countParts.push(`${counts.checkbox} checkbox${counts.checkbox > 1 ? "es" : ""}`);
1504
+ if (counts.select > 0)
1505
+ countParts.push(`${counts.select} dropdown${counts.select > 1 ? "s" : ""}`);
1506
+ if (counts.checkbox > 0)
1507
+ countParts.push(`${counts.checkbox} checkbox${counts.checkbox > 1 ? "es" : ""}`);
1124
1508
  if (countParts.length > 0) {
1125
1509
  lines.push(`Contains: ${countParts.join(", ")}`);
1126
1510
  }
@@ -1163,7 +1547,9 @@ function generateFormSummary(form, verbosity) {
1163
1547
  if (verbosity === "brief") {
1164
1548
  const fieldCount = form.fields.length;
1165
1549
  const filledCount = form.fields.filter((f) => f.value).length;
1166
- lines.push(` ${filledCount}/${fieldCount} fields filled, ${form.isValid ? "valid" : "has errors"}`);
1550
+ lines.push(
1551
+ ` ${filledCount}/${fieldCount} fields filled, ${form.isValid ? "valid" : "has errors"}`
1552
+ );
1167
1553
  } else {
1168
1554
  for (const field of form.fields) {
1169
1555
  let fieldLine = ` - ${field.label || field.id}`;
@@ -1198,7 +1584,9 @@ function generateDiffSummary(appeared, disappeared, modified) {
1198
1584
  if (modified.length > 0) {
1199
1585
  lines.push("Changed:");
1200
1586
  for (const mod of modified.slice(0, 5)) {
1201
- lines.push(` - ${mod.description}: ${mod.property} changed from "${mod.from}" to "${mod.to}"`);
1587
+ lines.push(
1588
+ ` - ${mod.description}: ${mod.property} changed from "${mod.from}" to "${mod.to}"`
1589
+ );
1202
1590
  }
1203
1591
  if (modified.length > 5) {
1204
1592
  lines.push(` ... and ${modified.length - 5} more changes`);
@@ -1629,7 +2017,9 @@ function parseNLInstruction(instruction) {
1629
2017
  }
1630
2018
  }
1631
2019
  if (pattern.action === "assert") {
1632
- const assertMatch = trimmed.match(/(visible|hidden|enabled|disabled|checked|unchecked|focused|contains|has)/i);
2020
+ const assertMatch = trimmed.match(
2021
+ /(visible|hidden|enabled|disabled|checked|unchecked|focused|contains|has)/i
2022
+ );
1633
2023
  if (assertMatch) {
1634
2024
  parsed.assertionType = ASSERTION_TYPE_MAP[assertMatch[1].toLowerCase()];
1635
2025
  }
@@ -2390,7 +2780,9 @@ var NLActionExecutor = class {
2390
2780
  switch (errorCode) {
2391
2781
  case "PARSE_ERROR":
2392
2782
  suggestions.push('Try using a simpler phrase like "click Submit button"');
2393
- suggestions.push('Ensure the instruction follows patterns like "click X" or "type Y into X"');
2783
+ suggestions.push(
2784
+ 'Ensure the instruction follows patterns like "click X" or "type Y into X"'
2785
+ );
2394
2786
  break;
2395
2787
  case "ELEMENT_NOT_FOUND":
2396
2788
  if (alternatives.length > 0) {
@@ -2458,9 +2850,15 @@ var AssertionExecutor = class {
2458
2850
  async assert(request) {
2459
2851
  const startTime = performance.now();
2460
2852
  const timeout = request.timeout ?? this.config.defaultTimeout;
2461
- const element = await this.findElement(request.target, request.fuzzy !== false);
2853
+ const searchResult = this.findElementDetailed(request.target, request.fuzzy !== false);
2854
+ const element = searchResult?.element ?? null;
2855
+ const searchDetails = searchResult ? {
2856
+ confidence: searchResult.confidence,
2857
+ matchReasons: searchResult.matchReasons,
2858
+ candidateCount: this.elements.length
2859
+ } : void 0;
2462
2860
  if (!element && request.type !== "notExists") {
2463
- return this.createResult(
2861
+ const result2 = this.createResult(
2464
2862
  false,
2465
2863
  typeof request.target === "string" ? request.target : JSON.stringify(request.target),
2466
2864
  "element not found",
@@ -2470,8 +2868,16 @@ var AssertionExecutor = class {
2470
2868
  this.config.includeSuggestions ? "Check if the element exists and is properly labeled" : void 0,
2471
2869
  startTime
2472
2870
  );
2871
+ if (searchDetails) {
2872
+ result2.searchDetails = searchDetails;
2873
+ }
2874
+ return result2;
2473
2875
  }
2474
- return this.executeAssertion(request, element, timeout, startTime);
2876
+ const result = await this.executeAssertion(request, element, timeout, startTime);
2877
+ if (searchDetails) {
2878
+ result.searchDetails = searchDetails;
2879
+ }
2880
+ return result;
2475
2881
  }
2476
2882
  /**
2477
2883
  * Execute multiple assertions
@@ -2576,16 +2982,26 @@ var AssertionExecutor = class {
2576
2982
  return this.assert({ target, type: "count", expected: expectedCount, timeout });
2577
2983
  }
2578
2984
  /**
2579
- * Find element by target (string or criteria)
2985
+ * Find element by target with full search metadata.
2986
+ * Returns the SearchResult (including confidence, matchReasons, scores)
2987
+ * or null if no match above the fuzzy threshold.
2580
2988
  */
2581
- async findElement(target, fuzzy = true) {
2989
+ findElementDetailed(target, fuzzy = true) {
2582
2990
  const criteria = typeof target === "string" ? { text: target, fuzzy } : { ...target, fuzzy };
2583
- const searchResult = this.searchEngine.findBest(criteria);
2991
+ const searchResult = this.searchEngine.findBest(criteria, this.elements);
2584
2992
  if (searchResult && searchResult.confidence >= this.config.fuzzyThreshold) {
2585
- return searchResult.element;
2993
+ return searchResult;
2586
2994
  }
2587
2995
  return null;
2588
2996
  }
2997
+ /**
2998
+ * Find element by target (string or criteria).
2999
+ * Public for use by condition evaluation in SpecExecutor.
3000
+ */
3001
+ async findElement(target, fuzzy = true) {
3002
+ const result = this.findElementDetailed(target, fuzzy);
3003
+ return result?.element ?? null;
3004
+ }
2589
3005
  /**
2590
3006
  * Execute the actual assertion
2591
3007
  */
@@ -2594,19 +3010,55 @@ var AssertionExecutor = class {
2594
3010
  const elementDescription = element?.description || targetStr;
2595
3011
  switch (request.type) {
2596
3012
  case "visible":
2597
- return this.assertVisibility(element, true, elementDescription, request.message, startTime);
3013
+ return this.assertVisibility(
3014
+ element,
3015
+ true,
3016
+ elementDescription,
3017
+ request.message,
3018
+ startTime
3019
+ );
2598
3020
  case "hidden":
2599
- return this.assertVisibility(element, false, elementDescription, request.message, startTime);
3021
+ return this.assertVisibility(
3022
+ element,
3023
+ false,
3024
+ elementDescription,
3025
+ request.message,
3026
+ startTime
3027
+ );
2600
3028
  case "enabled":
2601
- return this.assertEnabledState(element, true, elementDescription, request.message, startTime);
3029
+ return this.assertEnabledState(
3030
+ element,
3031
+ true,
3032
+ elementDescription,
3033
+ request.message,
3034
+ startTime
3035
+ );
2602
3036
  case "disabled":
2603
- return this.assertEnabledState(element, false, elementDescription, request.message, startTime);
3037
+ return this.assertEnabledState(
3038
+ element,
3039
+ false,
3040
+ elementDescription,
3041
+ request.message,
3042
+ startTime
3043
+ );
2604
3044
  case "focused":
2605
3045
  return this.assertFocused(element, elementDescription, request.message, startTime);
2606
3046
  case "checked":
2607
- return this.assertCheckedState(element, true, elementDescription, request.message, startTime);
3047
+ return this.assertCheckedState(
3048
+ element,
3049
+ true,
3050
+ elementDescription,
3051
+ request.message,
3052
+ startTime
3053
+ );
2608
3054
  case "unchecked":
2609
- return this.assertCheckedState(element, false, elementDescription, request.message, startTime);
3055
+ return this.assertCheckedState(
3056
+ element,
3057
+ false,
3058
+ elementDescription,
3059
+ request.message,
3060
+ startTime
3061
+ );
2610
3062
  case "hasText":
2611
3063
  return this.assertTextMatch(
2612
3064
  element,
@@ -2941,7 +3393,8 @@ var DEFAULT_SNAPSHOT_CONFIG = {
2941
3393
  detectModals: true,
2942
3394
  inferPageType: true,
2943
3395
  generateDescriptions: true,
2944
- maxElements: 500
3396
+ maxElements: 500,
3397
+ useAnnotations: true
2945
3398
  };
2946
3399
  var SemanticSnapshotManager = class {
2947
3400
  constructor(config = {}) {
@@ -3014,26 +3467,52 @@ var SemanticSnapshotManager = class {
3014
3467
  * Convert a single element to AI element
3015
3468
  */
3016
3469
  convertElement(element) {
3470
+ const isContent = element.category === "content";
3017
3471
  const aliases = generateAliases({
3018
3472
  textContent: element.state.textContent,
3019
3473
  elementType: element.type,
3020
3474
  id: element.id,
3021
3475
  labelText: element.label
3022
3476
  });
3023
- const description = this.config.generateDescriptions ? generateDescription({
3024
- textContent: element.state.textContent,
3025
- elementType: element.type,
3026
- id: element.id,
3027
- labelText: element.label
3028
- }) : element.label || element.id;
3029
- const purpose = generatePurpose({
3477
+ let description;
3478
+ if (isContent && element.contentMetadata) {
3479
+ description = this.generateContentDescription(element);
3480
+ } else if (this.config.generateDescriptions) {
3481
+ description = generateDescription({
3482
+ textContent: element.state.textContent,
3483
+ elementType: element.type,
3484
+ id: element.id,
3485
+ labelText: element.label
3486
+ });
3487
+ } else {
3488
+ description = element.label || element.id;
3489
+ }
3490
+ const purpose = isContent ? generatePurpose({ textContent: element.state.textContent, elementType: element.type }) : generatePurpose({ textContent: element.state.textContent, elementType: element.type });
3491
+ const suggestedActions = isContent ? generateSuggestedActions({
3030
3492
  textContent: element.state.textContent,
3031
3493
  elementType: element.type
3032
- });
3033
- const suggestedActions = generateSuggestedActions({
3494
+ }) : generateSuggestedActions({
3034
3495
  textContent: element.state.textContent,
3035
3496
  elementType: element.type
3036
3497
  });
3498
+ let finalDescription = description;
3499
+ let finalPurpose = purpose;
3500
+ let finalAliases = aliases;
3501
+ if (this.config.useAnnotations) {
3502
+ const annotation = getGlobalAnnotationStore().get(element.id);
3503
+ if (annotation) {
3504
+ if (annotation.description) {
3505
+ finalDescription = annotation.description;
3506
+ }
3507
+ if (annotation.purpose) {
3508
+ finalPurpose = annotation.purpose;
3509
+ }
3510
+ if (annotation.tags && annotation.tags.length > 0) {
3511
+ const tagSet = /* @__PURE__ */ new Set([...finalAliases, ...annotation.tags.map((t) => t.toLowerCase())]);
3512
+ finalAliases = [...tagSet];
3513
+ }
3514
+ }
3515
+ }
3037
3516
  return {
3038
3517
  id: element.id,
3039
3518
  type: element.type,
@@ -3044,13 +3523,56 @@ var SemanticSnapshotManager = class {
3044
3523
  actions: element.actions,
3045
3524
  state: element.state,
3046
3525
  registered: true,
3047
- description,
3048
- aliases,
3049
- purpose,
3526
+ description: finalDescription,
3527
+ aliases: finalAliases,
3528
+ purpose: finalPurpose,
3050
3529
  suggestedActions,
3051
- semanticType: this.inferSemanticType(element)
3530
+ semanticType: this.inferSemanticType(element),
3531
+ category: element.category,
3532
+ contentMetadata: element.contentMetadata
3052
3533
  };
3053
3534
  }
3535
+ /**
3536
+ * Generate a content-specific description
3537
+ */
3538
+ generateContentDescription(element) {
3539
+ const meta = element.contentMetadata;
3540
+ const text = element.state.textContent?.trim() || "";
3541
+ const truncatedText = text.length > 60 ? text.substring(0, 57) + "..." : text;
3542
+ if (!meta) return `"${truncatedText}"`;
3543
+ switch (meta.contentRole) {
3544
+ case "heading":
3545
+ return `Level ${meta.headingLevel || "?"} heading: '${truncatedText}'`;
3546
+ case "table-cell":
3547
+ return `Table cell${meta.structuralContext ? ` (${meta.structuralContext})` : ""}: '${truncatedText}'`;
3548
+ case "table-header":
3549
+ return `Table header${meta.structuralContext ? ` (${meta.structuralContext})` : ""}: '${truncatedText}'`;
3550
+ case "status":
3551
+ return `Status message: '${truncatedText}'`;
3552
+ case "badge":
3553
+ return `Badge: '${truncatedText}'`;
3554
+ case "metric":
3555
+ return `Metric value: '${truncatedText}'`;
3556
+ case "body-text":
3557
+ return `Text: '${truncatedText}'`;
3558
+ case "list-item":
3559
+ return `List item: '${truncatedText}'`;
3560
+ case "quote":
3561
+ return `Blockquote: '${truncatedText}'`;
3562
+ case "code":
3563
+ return `Code block: '${truncatedText}'`;
3564
+ case "caption":
3565
+ return `Caption: '${truncatedText}'`;
3566
+ case "label":
3567
+ return `Label: '${truncatedText}'`;
3568
+ case "description":
3569
+ return `Description: '${truncatedText}'`;
3570
+ case "navigation":
3571
+ return `Navigation text: '${truncatedText}'`;
3572
+ default:
3573
+ return `Content: '${truncatedText}'`;
3574
+ }
3575
+ }
3054
3576
  /**
3055
3577
  * Build full page context
3056
3578
  */
@@ -3154,9 +3676,7 @@ var SemanticSnapshotManager = class {
3154
3676
  */
3155
3677
  detectModals(elements) {
3156
3678
  const modals = [];
3157
- const dialogElements = elements.filter(
3158
- (el) => el.type === "dialog" && el.state.visible
3159
- );
3679
+ const dialogElements = elements.filter((el) => el.type === "dialog" && el.state.visible);
3160
3680
  for (const dialog of dialogElements) {
3161
3681
  const closeButton = elements.find(
3162
3682
  (el) => el.type === "button" && el.state.visible && (el.semanticType === "cancel-button" || el.state.textContent?.toLowerCase().match(/close|cancel|x|dismiss/))
@@ -3207,9 +3727,7 @@ var SemanticSnapshotManager = class {
3207
3727
  * Infer form purpose from fields
3208
3728
  */
3209
3729
  inferFormPurpose(fields) {
3210
- const labels = fields.map(
3211
- (f) => (f.accessibleName || f.label || "").toLowerCase()
3212
- );
3730
+ const labels = fields.map((f) => (f.accessibleName || f.label || "").toLowerCase());
3213
3731
  const allLabels = labels.join(" ");
3214
3732
  if (allLabels.includes("email") && allLabels.includes("password")) {
3215
3733
  if (allLabels.includes("confirm") || allLabels.includes("name")) {
@@ -3263,6 +3781,13 @@ var SemanticSnapshotManager = class {
3263
3781
  * Infer semantic type
3264
3782
  */
3265
3783
  inferSemanticType(element) {
3784
+ if (element.category === "content" && element.contentMetadata) {
3785
+ const role = element.contentMetadata.contentRole;
3786
+ if (role === "heading" && element.contentMetadata.headingLevel) {
3787
+ return `heading-${element.contentMetadata.headingLevel}`;
3788
+ }
3789
+ return role;
3790
+ }
3266
3791
  const text = (element.state.textContent || element.label || "").toLowerCase();
3267
3792
  const type = element.type.toLowerCase();
3268
3793
  if (type === "button") {
@@ -3344,6 +3869,7 @@ function computeDiff(fromSnapshot, toSnapshot, config = {}) {
3344
3869
  const probableTrigger = detectTrigger(appeared, disappeared, limitedModifications);
3345
3870
  const suggestedActions = finalConfig.generateSuggestions ? generateSuggestedActionsFromDiff(appeared, disappeared, limitedModifications, probableTrigger) : void 0;
3346
3871
  const pageChanges = detectPageChanges(fromSnapshot, toSnapshot);
3872
+ const contentChanges = detectContentChanges(fromElements, toElements);
3347
3873
  const summary = generateDiffSummary(
3348
3874
  appeared.map((e) => e.description),
3349
3875
  disappeared.map((e) => e.description),
@@ -3358,6 +3884,7 @@ function computeDiff(fromSnapshot, toSnapshot, config = {}) {
3358
3884
  disappeared,
3359
3885
  modified: limitedModifications
3360
3886
  },
3887
+ contentChanges: contentChanges || void 0,
3361
3888
  probableTrigger,
3362
3889
  suggestedActions,
3363
3890
  pageChanges,
@@ -3492,9 +4019,7 @@ function generateSuggestedActionsFromDiff(appeared, disappeared, modified, trigg
3492
4019
  suggestions.push("Fix the validation errors before submitting");
3493
4020
  }
3494
4021
  if (trigger === "Modal opened") {
3495
- const modal = appeared.find(
3496
- (e) => e.type === "dialog" || e.semanticType?.includes("dialog")
3497
- );
4022
+ const modal = appeared.find((e) => e.type === "dialog" || e.semanticType?.includes("dialog"));
3498
4023
  if (modal) {
3499
4024
  suggestions.push(`Interact with the "${modal.description}" dialog`);
3500
4025
  }
@@ -3557,76 +4082,1869 @@ var SemanticDiffManager = class {
3557
4082
  return this.lastSnapshot;
3558
4083
  }
3559
4084
  };
3560
-
3561
- // src/server/handlers.ts
3562
- function success(data) {
3563
- return {
3564
- success: true,
3565
- data,
3566
- timestamp: Date.now()
3567
- };
4085
+ var METRIC_CONTENT_TYPES = /* @__PURE__ */ new Set(["metric-value"]);
4086
+ var STATUS_CONTENT_TYPES = /* @__PURE__ */ new Set(["status-message", "badge"]);
4087
+ var HEADING_CONTENT_TYPES = /* @__PURE__ */ new Set(["heading"]);
4088
+ function isContentElement(element) {
4089
+ return element.category === "content" || element.contentMetadata !== void 0;
3568
4090
  }
3569
- function error(message, code) {
4091
+ function getContentType(element) {
4092
+ if (element.contentMetadata?.contentRole) {
4093
+ return element.contentMetadata.contentRole;
4094
+ }
4095
+ return element.type;
4096
+ }
4097
+ function detectContentChanges(fromElements, toElements) {
4098
+ const textChanges = [];
4099
+ const metricChanges = [];
4100
+ const statusChanges = [];
4101
+ for (const [id, toElement] of toElements) {
4102
+ const fromElement = fromElements.get(id);
4103
+ if (fromElement) {
4104
+ if (isContentElement(toElement) || isContentElement(fromElement)) {
4105
+ const fromText = (fromElement.state.textContent || "").trim();
4106
+ const toText = (toElement.state.textContent || "").trim();
4107
+ if (fromText !== toText) {
4108
+ const contentType = getContentType(toElement);
4109
+ const label = toElement.description || toElement.accessibleName || id;
4110
+ if (METRIC_CONTENT_TYPES.has(contentType) || contentType === "metric") {
4111
+ const parsed = parseMetricChange(fromText, toText, id, label);
4112
+ if (parsed) {
4113
+ metricChanges.push(parsed);
4114
+ }
4115
+ } else if (STATUS_CONTENT_TYPES.has(contentType) || contentType === "status") {
4116
+ statusChanges.push({
4117
+ elementId: id,
4118
+ label,
4119
+ oldStatus: fromText,
4120
+ newStatus: toText,
4121
+ direction: classifyStatusDirection(fromText, toText)
4122
+ });
4123
+ } else {
4124
+ textChanges.push({
4125
+ elementId: id,
4126
+ contentType,
4127
+ oldText: fromText,
4128
+ newText: toText,
4129
+ changeType: "modified"
4130
+ });
4131
+ }
4132
+ }
4133
+ }
4134
+ } else {
4135
+ if (isContentElement(toElement)) {
4136
+ const toText = (toElement.state.textContent || "").trim();
4137
+ if (toText) {
4138
+ textChanges.push({
4139
+ elementId: id,
4140
+ contentType: getContentType(toElement),
4141
+ oldText: "",
4142
+ newText: toText,
4143
+ changeType: "added"
4144
+ });
4145
+ }
4146
+ }
4147
+ }
4148
+ }
4149
+ for (const [id, fromElement] of fromElements) {
4150
+ if (!toElements.has(id) && isContentElement(fromElement)) {
4151
+ const fromText = (fromElement.state.textContent || "").trim();
4152
+ if (fromText) {
4153
+ textChanges.push({
4154
+ elementId: id,
4155
+ contentType: getContentType(fromElement),
4156
+ oldText: fromText,
4157
+ newText: "",
4158
+ changeType: "removed"
4159
+ });
4160
+ }
4161
+ }
4162
+ }
4163
+ if (textChanges.length === 0 && metricChanges.length === 0 && statusChanges.length === 0) {
4164
+ return null;
4165
+ }
3570
4166
  return {
3571
- success: false,
3572
- error: message,
3573
- code,
3574
- timestamp: Date.now()
4167
+ textChanges,
4168
+ metricChanges,
4169
+ statusChanges,
4170
+ summary: generateContentChangeSummary(textChanges, metricChanges, statusChanges)
3575
4171
  };
3576
4172
  }
3577
- function getRecoverySuggestions(errorCode) {
3578
- switch (errorCode) {
3579
- case "ELEMENT_NOT_FOUND":
3580
- return [
3581
- { suggestion: "Wait for the page to fully load", command: "wait for page to load", confidence: 0.7, retryable: true },
3582
- { suggestion: "Use a different description for the element", confidence: 0.8, retryable: false },
3583
- { suggestion: "Scroll the page to reveal the element", command: "scroll down", confidence: 0.6, retryable: true }
3584
- ];
3585
- case "ELEMENT_NOT_VISIBLE":
3586
- return [
3587
- { suggestion: "Scroll to make the element visible", command: "scroll to element", confidence: 0.9, retryable: true },
3588
- { suggestion: "Wait for any loading overlays to disappear", confidence: 0.7, retryable: true },
3589
- { suggestion: "Close any blocking modals or popups", command: "click close button", confidence: 0.8, retryable: true }
3590
- ];
3591
- case "ELEMENT_NOT_ENABLED":
3592
- return [
3593
- { suggestion: "Fill in required fields first", confidence: 0.8, retryable: false },
3594
- { suggestion: "Complete prerequisite steps in the form", confidence: 0.7, retryable: false },
3595
- { suggestion: "Wait for the element to become enabled", command: "wait for element to be enabled", confidence: 0.6, retryable: true }
3596
- ];
3597
- case "ELEMENT_NOT_INTERACTABLE":
3598
- return [
3599
- { suggestion: "Close any modal or popup blocking the element", command: "click close button", confidence: 0.9, retryable: true },
3600
- { suggestion: "Wait for animations to complete", confidence: 0.7, retryable: true },
3601
- { suggestion: "Scroll the element into the viewport", command: "scroll to element", confidence: 0.8, retryable: true }
3602
- ];
3603
- case "ACTION_TIMEOUT":
3604
- return [
3605
- { suggestion: "Increase the timeout duration", confidence: 0.8, retryable: true },
3606
- { suggestion: "Check if the condition can ever be met", confidence: 0.7, retryable: false },
3607
- { suggestion: "Verify the page is responding", command: "check page status", confidence: 0.6, retryable: true }
3608
- ];
3609
- case "LOW_CONFIDENCE":
3610
- return [
3611
- { suggestion: "Use the exact text shown on the element", confidence: 0.9, retryable: false },
3612
- { suggestion: "Try a different description that more closely matches the element", confidence: 0.8, retryable: false },
3613
- { suggestion: "Lower the confidence threshold if the match is correct", confidence: 0.7, retryable: true }
3614
- ];
3615
- case "AMBIGUOUS_MATCH":
3616
- return [
3617
- { suggestion: "Be more specific about which element you mean", confidence: 0.9, retryable: false },
3618
- { suggestion: "Include the section or form name in the description", confidence: 0.8, retryable: false },
3619
- { suggestion: "Use the element ID directly", confidence: 0.7, retryable: false }
3620
- ];
3621
- default:
3622
- return [
3623
- { suggestion: "Try a different approach or check the page state", confidence: 0.5, retryable: false }
3624
- ];
4173
+ function parseNumericValue(text) {
4174
+ const trimmed = text.trim();
4175
+ if (!trimmed) return null;
4176
+ let working = trimmed;
4177
+ let negate = false;
4178
+ if (working.startsWith("(") && working.endsWith(")")) {
4179
+ working = working.slice(1, -1).trim();
4180
+ negate = true;
4181
+ }
4182
+ if (working.startsWith("-")) {
4183
+ negate = !negate;
4184
+ working = working.slice(1).trim();
4185
+ }
4186
+ if (working.startsWith("+")) {
4187
+ working = working.slice(1).trim();
4188
+ }
4189
+ working = working.replace(/^[£€¥₹$]/, "").trim();
4190
+ const isPercent = working.endsWith("%");
4191
+ if (isPercent) {
4192
+ working = working.slice(0, -1).trim();
4193
+ }
4194
+ working = working.replace(/\s*(ms|s|m|h|d|hrs?|mins?|secs?|days?)$/i, "").trim();
4195
+ working = working.replace(/,/g, "");
4196
+ const num = Number(working);
4197
+ if (isNaN(num) || !isFinite(num) || working === "") {
4198
+ return null;
3625
4199
  }
4200
+ return negate ? -num : num;
3626
4201
  }
3627
- function createFailureDetails(errorCode, message, options = {}) {
3628
- const retryableErrors = [
3629
- "ELEMENT_NOT_VISIBLE",
4202
+ function parseMetricChange(fromText, toText, elementId, label) {
4203
+ const fromNum = parseNumericValue(fromText);
4204
+ const toNum = parseNumericValue(toText);
4205
+ let numericDelta;
4206
+ let percentChange;
4207
+ let significant = false;
4208
+ if (fromNum !== null && toNum !== null) {
4209
+ numericDelta = toNum - fromNum;
4210
+ if (fromNum !== 0) {
4211
+ percentChange = (toNum - fromNum) / Math.abs(fromNum) * 100;
4212
+ }
4213
+ if (percentChange !== void 0 && Math.abs(percentChange) > 10) {
4214
+ significant = true;
4215
+ }
4216
+ if (fromNum > 0 && toNum < 0) significant = true;
4217
+ if (fromNum < 0 && toNum > 0) significant = true;
4218
+ if (fromNum === 0 && toNum !== 0) significant = true;
4219
+ if (fromNum !== 0 && toNum === 0) significant = true;
4220
+ } else {
4221
+ significant = fromText !== toText;
4222
+ }
4223
+ return {
4224
+ elementId,
4225
+ label,
4226
+ oldValue: fromText,
4227
+ newValue: toText,
4228
+ numericDelta,
4229
+ percentChange: percentChange !== void 0 ? Math.round(percentChange * 100) / 100 : void 0,
4230
+ significant
4231
+ };
4232
+ }
4233
+ var STATUS_PROGRESSIONS = [
4234
+ [
4235
+ "failed",
4236
+ "error",
4237
+ "pending",
4238
+ "queued",
4239
+ "running",
4240
+ "in progress",
4241
+ "completed",
4242
+ "success",
4243
+ "done"
4244
+ ],
4245
+ ["disconnected", "connecting", "connected"],
4246
+ ["unhealthy", "degraded", "healthy"],
4247
+ ["offline", "online"],
4248
+ ["inactive", "active"],
4249
+ ["disabled", "enabled"],
4250
+ ["down", "up"],
4251
+ ["stopped", "starting", "started", "running"],
4252
+ ["closed", "open"],
4253
+ ["blocked", "unblocked"],
4254
+ ["rejected", "pending", "approved"],
4255
+ ["critical", "warning", "info", "ok"],
4256
+ ["red", "yellow", "green"]
4257
+ ];
4258
+ function classifyStatusDirection(oldStatus, newStatus) {
4259
+ const oldLower = oldStatus.toLowerCase().trim();
4260
+ const newLower = newStatus.toLowerCase().trim();
4261
+ for (const progression of STATUS_PROGRESSIONS) {
4262
+ let oldIndex = -1;
4263
+ let newIndex = -1;
4264
+ for (let i = 0; i < progression.length; i++) {
4265
+ if (oldLower.includes(progression[i])) oldIndex = i;
4266
+ if (newLower.includes(progression[i])) newIndex = i;
4267
+ }
4268
+ if (oldIndex >= 0 && newIndex >= 0 && oldIndex !== newIndex) {
4269
+ return newIndex > oldIndex ? "improved" : "degraded";
4270
+ }
4271
+ }
4272
+ return "neutral";
4273
+ }
4274
+ function generateContentChangeSummary(textChanges, metricChanges, statusChanges) {
4275
+ const parts = [];
4276
+ const modified = textChanges.filter((t) => t.changeType === "modified").length;
4277
+ const added = textChanges.filter((t) => t.changeType === "added").length;
4278
+ const removed = textChanges.filter((t) => t.changeType === "removed").length;
4279
+ const headingChanges = textChanges.filter(
4280
+ (t) => HEADING_CONTENT_TYPES.has(t.contentType) || t.contentType === "heading"
4281
+ );
4282
+ if (headingChanges.length > 0) {
4283
+ parts.push(`${headingChanges.length} heading${headingChanges.length > 1 ? "s" : ""} changed`);
4284
+ }
4285
+ if (metricChanges.length > 0) {
4286
+ const significantMetrics = metricChanges.filter((m) => m.significant);
4287
+ if (significantMetrics.length > 0) {
4288
+ parts.push(
4289
+ `${significantMetrics.length} metric${significantMetrics.length > 1 ? "s" : ""} changed significantly`
4290
+ );
4291
+ } else {
4292
+ parts.push(`${metricChanges.length} metric${metricChanges.length > 1 ? "s" : ""} changed`);
4293
+ }
4294
+ }
4295
+ if (statusChanges.length > 0) {
4296
+ const degraded = statusChanges.filter((s) => s.direction === "degraded");
4297
+ const improved = statusChanges.filter((s) => s.direction === "improved");
4298
+ if (degraded.length > 0) {
4299
+ parts.push(`${degraded.length} status${degraded.length > 1 ? "es" : ""} degraded`);
4300
+ }
4301
+ if (improved.length > 0) {
4302
+ parts.push(`${improved.length} status${improved.length > 1 ? "es" : ""} improved`);
4303
+ }
4304
+ const neutral = statusChanges.length - degraded.length - improved.length;
4305
+ if (neutral > 0 && degraded.length === 0 && improved.length === 0) {
4306
+ parts.push(`${neutral} status${neutral > 1 ? "es" : ""} changed`);
4307
+ }
4308
+ }
4309
+ const otherModified = modified - headingChanges.filter((h) => h.changeType === "modified").length;
4310
+ if (otherModified > 0) {
4311
+ parts.push(`${otherModified} text${otherModified > 1 ? " values" : " value"} modified`);
4312
+ }
4313
+ if (added > 0) {
4314
+ parts.push(`${added} content${added > 1 ? " elements" : " element"} added`);
4315
+ }
4316
+ if (removed > 0) {
4317
+ parts.push(`${removed} content${removed > 1 ? " elements" : " element"} removed`);
4318
+ }
4319
+ if (parts.length === 0) {
4320
+ return "No content changes";
4321
+ }
4322
+ return parts.join(", ");
4323
+ }
4324
+
4325
+ // src/ai/data-extraction.ts
4326
+ var DEFAULT_DATA_EXTRACTION_CONFIG = {
4327
+ minConfidence: 0.3,
4328
+ normalizeWhitespace: true
4329
+ };
4330
+ function classifyDataType(value) {
4331
+ const trimmed = value.trim();
4332
+ if (!trimmed) return { type: "unknown", confidence: 0 };
4333
+ if (/^(true|false|yes|no|on|off)$/i.test(trimmed)) {
4334
+ return { type: "boolean", confidence: 0.95 };
4335
+ }
4336
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
4337
+ return { type: "email", confidence: 0.95 };
4338
+ }
4339
+ if (/^https?:\/\/\S+/.test(trimmed)) {
4340
+ return { type: "url", confidence: 0.95 };
4341
+ }
4342
+ if (/^[+]?[\d\s\-().]{7,20}$/.test(trimmed) && /\d{3,}/.test(trimmed)) {
4343
+ return { type: "phone", confidence: 0.7 };
4344
+ }
4345
+ if (/^[£$€¥₹][\s]?[\d,.]+$/.test(trimmed) || /^[\d,.]+[\s]?[£$€¥₹]$/.test(trimmed)) {
4346
+ return { type: "currency", confidence: 0.9 };
4347
+ }
4348
+ if (/^[\d,.]+\s?%$/.test(trimmed)) {
4349
+ return { type: "percentage", confidence: 0.95 };
4350
+ }
4351
+ if (/^\d{4}-\d{2}-\d{2}/.test(trimmed) || /^\d{1,2}\/\d{1,2}\/\d{2,4}$/.test(trimmed) || /^\d{1,2}\.\d{1,2}\.\d{2,4}$/.test(trimmed) || /^\w{3,9}\s+\d{1,2},?\s+\d{4}$/.test(trimmed)) {
4352
+ return { type: "date", confidence: 0.85 };
4353
+ }
4354
+ if (/^-?[\d,]+\.?\d*$/.test(trimmed) && trimmed !== "") {
4355
+ return { type: "number", confidence: 0.9 };
4356
+ }
4357
+ return { type: "text", confidence: 0.5 };
4358
+ }
4359
+ function normalizeValue(value, dataType) {
4360
+ const trimmed = value.trim();
4361
+ switch (dataType) {
4362
+ case "number":
4363
+ case "currency":
4364
+ case "percentage": {
4365
+ const numeric = trimmed.replace(/[^0-9.-]/g, "");
4366
+ const parsed = parseFloat(numeric);
4367
+ return isNaN(parsed) ? trimmed.toLowerCase() : parsed.toString();
4368
+ }
4369
+ case "date": {
4370
+ const d = new Date(trimmed);
4371
+ return isNaN(d.getTime()) ? trimmed.toLowerCase() : d.toISOString().split("T")[0];
4372
+ }
4373
+ case "boolean":
4374
+ return /^(true|yes|on)$/i.test(trimmed) ? "true" : "false";
4375
+ case "email":
4376
+ return trimmed.toLowerCase();
4377
+ case "url":
4378
+ return trimmed.replace(/\/+$/, "").toLowerCase();
4379
+ case "phone":
4380
+ return trimmed.replace(/[^\d+]/g, "");
4381
+ default:
4382
+ return trimmed.toLowerCase().replace(/\s+/g, " ");
4383
+ }
4384
+ }
4385
+ function extractElementValue(element) {
4386
+ const state = element.state;
4387
+ if (state?.value !== void 0 && state.value !== "") {
4388
+ return String(state.value);
4389
+ }
4390
+ if (state?.textContent !== void 0 && state.textContent !== "") {
4391
+ return String(state.textContent);
4392
+ }
4393
+ return "";
4394
+ }
4395
+ function extractLabel(element) {
4396
+ return element.accessibleName || element.labelText || element.label || element.description || element.id;
4397
+ }
4398
+ function extractPageData(elements, config = DEFAULT_DATA_EXTRACTION_CONFIG) {
4399
+ const values = {};
4400
+ let extractedCount = 0;
4401
+ for (const element of elements) {
4402
+ const rawValue = extractElementValue(element);
4403
+ if (!rawValue) continue;
4404
+ const label = extractLabel(element);
4405
+ const { type: dataType, confidence } = classifyDataType(rawValue);
4406
+ if (confidence < config.minConfidence) continue;
4407
+ const normalizedValue = normalizeValue(rawValue, dataType);
4408
+ values[label] = {
4409
+ elementId: element.id,
4410
+ label,
4411
+ rawValue: config.normalizeWhitespace ? rawValue.replace(/\s+/g, " ").trim() : rawValue,
4412
+ normalizedValue,
4413
+ dataType,
4414
+ confidence
4415
+ };
4416
+ extractedCount++;
4417
+ }
4418
+ return {
4419
+ values,
4420
+ scannedCount: elements.length,
4421
+ extractedCount
4422
+ };
4423
+ }
4424
+
4425
+ // src/ai/region-segmentation.ts
4426
+ var DEFAULT_REGION_SEGMENTATION_CONFIG = {
4427
+ minRegionElements: 1,
4428
+ headerFraction: 0.12,
4429
+ footerFraction: 0.9,
4430
+ sidebarFraction: 0.2
4431
+ };
4432
+ function toBounded(el) {
4433
+ const rect = el.state?.rect;
4434
+ if (!rect) return null;
4435
+ return {
4436
+ element: el,
4437
+ x: rect.x ?? 0,
4438
+ y: rect.y ?? 0,
4439
+ width: rect.width ?? 0,
4440
+ height: rect.height ?? 0
4441
+ };
4442
+ }
4443
+ function classifyRegionType(el, relativeY, relativeX, config = DEFAULT_REGION_SEGMENTATION_CONFIG) {
4444
+ const role = (el.role || "").toLowerCase();
4445
+ const semanticType = (el.semanticType || "").toLowerCase();
4446
+ const tag = (el.tagName || "").toLowerCase();
4447
+ if (role === "navigation" || role === "nav" || tag === "nav") {
4448
+ return { type: "navigation", confidence: 0.95 };
4449
+ }
4450
+ if (role === "banner" || tag === "header") {
4451
+ return { type: "header", confidence: 0.95 };
4452
+ }
4453
+ if (role === "contentinfo" || tag === "footer") {
4454
+ return { type: "footer", confidence: 0.95 };
4455
+ }
4456
+ if (role === "main" || tag === "main") {
4457
+ return { type: "main-content", confidence: 0.95 };
4458
+ }
4459
+ if (role === "complementary" || tag === "aside") {
4460
+ return { type: "sidebar", confidence: 0.9 };
4461
+ }
4462
+ if (role === "form" || tag === "form") {
4463
+ return { type: "form", confidence: 0.9 };
4464
+ }
4465
+ if (role === "table" || tag === "table") {
4466
+ return { type: "table", confidence: 0.9 };
4467
+ }
4468
+ if (role === "dialog" || role === "alertdialog") {
4469
+ return { type: "modal", confidence: 0.95 };
4470
+ }
4471
+ if (role === "toolbar") {
4472
+ return { type: "toolbar", confidence: 0.9 };
4473
+ }
4474
+ if (semanticType.includes("card")) {
4475
+ return { type: "card", confidence: 0.8 };
4476
+ }
4477
+ if (relativeY < config.headerFraction) {
4478
+ return { type: "header", confidence: 0.6 };
4479
+ }
4480
+ if (relativeY > config.footerFraction) {
4481
+ return { type: "footer", confidence: 0.6 };
4482
+ }
4483
+ if (relativeX < config.sidebarFraction) {
4484
+ return { type: "sidebar", confidence: 0.5 };
4485
+ }
4486
+ return { type: "main-content", confidence: 0.3 };
4487
+ }
4488
+ function segmentPageRegions(elements, config = DEFAULT_REGION_SEGMENTATION_CONFIG) {
4489
+ const bounded = elements.map(toBounded).filter((b) => b !== null);
4490
+ if (bounded.length === 0) {
4491
+ return { regions: [], assignedCount: 0, unassignedIds: elements.map((e) => e.id) };
4492
+ }
4493
+ let maxX = 0;
4494
+ let maxY = 0;
4495
+ for (const b of bounded) {
4496
+ maxX = Math.max(maxX, b.x + b.width);
4497
+ maxY = Math.max(maxY, b.y + b.height);
4498
+ }
4499
+ if (maxX === 0) maxX = 1;
4500
+ if (maxY === 0) maxY = 1;
4501
+ const regionGroups = /* @__PURE__ */ new Map();
4502
+ const unassignedIds = [];
4503
+ for (const b of bounded) {
4504
+ const relativeX = b.x / maxX;
4505
+ const relativeY = b.y / maxY;
4506
+ const { type, confidence } = classifyRegionType(b.element, relativeY, relativeX, config);
4507
+ if (!regionGroups.has(type)) {
4508
+ regionGroups.set(type, { elements: [], confidences: [] });
4509
+ }
4510
+ regionGroups.get(type).elements.push(b);
4511
+ regionGroups.get(type).confidences.push(confidence);
4512
+ }
4513
+ const regions = [];
4514
+ let assignedCount = 0;
4515
+ for (const [type, group] of regionGroups) {
4516
+ if (group.elements.length < config.minRegionElements) {
4517
+ for (const b of group.elements) unassignedIds.push(b.element.id);
4518
+ continue;
4519
+ }
4520
+ let minX = Infinity, minY = Infinity, maxRX = 0, maxRY = 0;
4521
+ const elementIds = [];
4522
+ for (const b of group.elements) {
4523
+ minX = Math.min(minX, b.x);
4524
+ minY = Math.min(minY, b.y);
4525
+ maxRX = Math.max(maxRX, b.x + b.width);
4526
+ maxRY = Math.max(maxRY, b.y + b.height);
4527
+ elementIds.push(b.element.id);
4528
+ }
4529
+ const avgConfidence = group.confidences.reduce((a, b) => a + b, 0) / group.confidences.length;
4530
+ regions.push({
4531
+ type,
4532
+ bounds: { x: minX, y: minY, width: maxRX - minX, height: maxRY - minY },
4533
+ elementIds,
4534
+ label: type.replace("-", " ").replace(/\b\w/g, (c) => c.toUpperCase()),
4535
+ confidence: Math.round(avgConfidence * 100) / 100
4536
+ });
4537
+ assignedCount += elementIds.length;
4538
+ }
4539
+ return { regions, assignedCount, unassignedIds };
4540
+ }
4541
+
4542
+ // src/ai/table-extraction.ts
4543
+ var DEFAULT_TABLE_EXTRACTION_CONFIG = {
4544
+ minTableColumns: 2,
4545
+ minTableRows: 2,
4546
+ minListItems: 2,
4547
+ columnTolerance: 20,
4548
+ rowTolerance: 10
4549
+ };
4550
+ function getElementBounds(el) {
4551
+ const rect = el.state?.rect;
4552
+ if (!rect || rect.width === 0) return null;
4553
+ const text = el.state?.textContent ?? el.state?.value ?? "";
4554
+ if (!text) return null;
4555
+ return {
4556
+ element: el,
4557
+ x: rect.x ?? 0,
4558
+ y: rect.y ?? 0,
4559
+ width: rect.width ?? 0,
4560
+ height: rect.height ?? 0,
4561
+ text: text.trim()
4562
+ };
4563
+ }
4564
+ function clusterPositions(values, tolerance) {
4565
+ if (values.length === 0) return [];
4566
+ const sorted = [...values].sort((a, b) => a - b);
4567
+ const clusters = [sorted[0]];
4568
+ for (let i = 1; i < sorted.length; i++) {
4569
+ if (sorted[i] - clusters[clusters.length - 1] > tolerance) {
4570
+ clusters.push(sorted[i]);
4571
+ }
4572
+ }
4573
+ return clusters;
4574
+ }
4575
+ function assignToCluster(value, clusters, tolerance) {
4576
+ let best = 0;
4577
+ let bestDist = Math.abs(value - clusters[0]);
4578
+ for (let i = 1; i < clusters.length; i++) {
4579
+ const dist = Math.abs(value - clusters[i]);
4580
+ if (dist < bestDist) {
4581
+ bestDist = dist;
4582
+ best = i;
4583
+ }
4584
+ }
4585
+ return bestDist <= tolerance ? best : -1;
4586
+ }
4587
+ function detectTable(elements, config = DEFAULT_TABLE_EXTRACTION_CONFIG) {
4588
+ const withBounds = elements.map(getElementBounds).filter((b) => b !== null);
4589
+ if (withBounds.length < config.minTableColumns * config.minTableRows) return null;
4590
+ const xPositions = withBounds.map((b) => b.x);
4591
+ const yPositions = withBounds.map((b) => b.y);
4592
+ const columnClusters = clusterPositions(xPositions, config.columnTolerance);
4593
+ const rowClusters = clusterPositions(yPositions, config.rowTolerance);
4594
+ if (columnClusters.length < config.minTableColumns || rowClusters.length < config.minTableRows) {
4595
+ return null;
4596
+ }
4597
+ const grid = Array.from(
4598
+ { length: rowClusters.length },
4599
+ () => Array(columnClusters.length).fill(null)
4600
+ );
4601
+ for (const b of withBounds) {
4602
+ const col = assignToCluster(b.x, columnClusters, config.columnTolerance);
4603
+ const row = assignToCluster(b.y, rowClusters, config.rowTolerance);
4604
+ if (col >= 0 && row >= 0 && grid[row][col] === null) {
4605
+ grid[row][col] = b.text;
4606
+ }
4607
+ }
4608
+ const headers = grid[0].map((h) => h ?? "");
4609
+ const columns = headers.map((header, index) => {
4610
+ const bodyCells = grid.slice(1).map((r) => r[index]).filter((c) => c !== null);
4611
+ const types = bodyCells.map((c) => classifyDataType(c).type);
4612
+ const mostCommon = mode(types) ?? "text";
4613
+ return { header, index, dataType: mostCommon };
4614
+ });
4615
+ const rows = grid.slice(1).map((row) => row.map((cell) => cell ?? ""));
4616
+ return {
4617
+ label: headers[0] || "Table",
4618
+ columns,
4619
+ rows
4620
+ };
4621
+ }
4622
+ function detectList(elements, config = DEFAULT_TABLE_EXTRACTION_CONFIG) {
4623
+ const withBounds = elements.map(getElementBounds).filter((b) => b !== null);
4624
+ if (withBounds.length < config.minListItems) return null;
4625
+ const sorted = [...withBounds].sort((a, b) => a.y - b.y);
4626
+ const yPositions = sorted.map((b) => b.y);
4627
+ const rowClusters = clusterPositions(yPositions, config.rowTolerance);
4628
+ if (rowClusters.length < config.minListItems) return null;
4629
+ const rowGroups = /* @__PURE__ */ new Map();
4630
+ for (const b of sorted) {
4631
+ const row = assignToCluster(b.y, rowClusters, config.rowTolerance);
4632
+ if (row >= 0) {
4633
+ if (!rowGroups.has(row)) rowGroups.set(row, []);
4634
+ rowGroups.get(row).push(b);
4635
+ }
4636
+ }
4637
+ const items = [];
4638
+ const fieldLabels = [];
4639
+ let fieldLabelsInitialized = false;
4640
+ for (const [, rowElements] of [...rowGroups.entries()].sort(([a], [b]) => a - b)) {
4641
+ const sortedRow = [...rowElements].sort((a, b) => a.x - b.x);
4642
+ const item = {};
4643
+ for (let i = 0; i < sortedRow.length; i++) {
4644
+ const label = `field_${i}`;
4645
+ if (!fieldLabelsInitialized) fieldLabels.push(label);
4646
+ item[label] = sortedRow[i].text;
4647
+ }
4648
+ fieldLabelsInitialized = true;
4649
+ items.push(item);
4650
+ }
4651
+ if (items.length < config.minListItems) return null;
4652
+ const fields = fieldLabels.map((label) => {
4653
+ const values = items.map((item) => item[label]).filter(Boolean);
4654
+ const types = values.map((v) => classifyDataType(v).type);
4655
+ return { label, dataType: mode(types) ?? "text" };
4656
+ });
4657
+ return {
4658
+ label: "List",
4659
+ fields,
4660
+ items
4661
+ };
4662
+ }
4663
+ function extractStructuredData(elements, config = DEFAULT_TABLE_EXTRACTION_CONFIG) {
4664
+ const tables = [];
4665
+ const lists = [];
4666
+ const table = detectTable(elements, config);
4667
+ if (table) {
4668
+ tables.push(table);
4669
+ }
4670
+ const listCandidates = elements.filter((el) => {
4671
+ const role = el.role || el.type;
4672
+ return ["listitem", "row", "option", "link", "button"].includes(role);
4673
+ });
4674
+ if (listCandidates.length >= config.minListItems) {
4675
+ const list = detectList(listCandidates, config);
4676
+ if (list) {
4677
+ lists.push(list);
4678
+ }
4679
+ }
4680
+ return { tables, lists };
4681
+ }
4682
+ function mode(arr) {
4683
+ if (arr.length === 0) return void 0;
4684
+ const counts = /* @__PURE__ */ new Map();
4685
+ let best = arr[0];
4686
+ let bestCount = 0;
4687
+ for (const v of arr) {
4688
+ const c = (counts.get(v) ?? 0) + 1;
4689
+ counts.set(v, c);
4690
+ if (c > bestCount) {
4691
+ bestCount = c;
4692
+ best = v;
4693
+ }
4694
+ }
4695
+ return best;
4696
+ }
4697
+
4698
+ // src/ai/format-analysis.ts
4699
+ var DEFAULT_FORMAT_ANALYSIS_CONFIG = {
4700
+ lenientFormatting: true
4701
+ };
4702
+ function detectFormatPattern(value, dataType) {
4703
+ const trimmed = value.trim();
4704
+ switch (dataType) {
4705
+ case "currency": {
4706
+ const hasLeadingSymbol = /^[£$€¥₹]/.test(trimmed);
4707
+ const hasTrailingSymbol = /[£$€¥₹]$/.test(trimmed);
4708
+ const usesCommaThousands = /\d{1,3}(,\d{3})+/.test(trimmed);
4709
+ const usesPeriodThousands = /\d{1,3}(\.\d{3})+,/.test(trimmed);
4710
+ let pattern = hasLeadingSymbol ? "$" : "";
4711
+ if (usesCommaThousands) pattern += "#,###";
4712
+ else if (usesPeriodThousands) pattern += "#.###";
4713
+ else pattern += "#";
4714
+ if (/\.\d{2}$/.test(trimmed)) pattern += ".##";
4715
+ else if (/,\d{2}$/.test(trimmed)) pattern += ",##";
4716
+ if (hasTrailingSymbol) pattern += "$";
4717
+ return pattern;
4718
+ }
4719
+ case "date": {
4720
+ if (/^\d{4}-\d{2}-\d{2}/.test(trimmed)) return "YYYY-MM-DD";
4721
+ if (/^\d{2}\/\d{2}\/\d{4}$/.test(trimmed)) return "MM/DD/YYYY";
4722
+ if (/^\d{2}\.\d{2}\.\d{4}$/.test(trimmed)) return "DD.MM.YYYY";
4723
+ if (/^\d{1,2}\/\d{1,2}\/\d{2}$/.test(trimmed)) return "M/D/YY";
4724
+ if (/^\w{3,9}\s+\d{1,2},?\s+\d{4}$/.test(trimmed)) return "Month DD, YYYY";
4725
+ return "date";
4726
+ }
4727
+ case "percentage":
4728
+ return /\s%$/.test(trimmed) ? "#.## %" : "#.##%";
4729
+ case "number": {
4730
+ const hasCommas = /,/.test(trimmed);
4731
+ const decimalPlaces = trimmed.includes(".") ? trimmed.split(".")[1]?.length || 0 : 0;
4732
+ return (hasCommas ? "#,###" : "#") + (decimalPlaces > 0 ? "." + "#".repeat(decimalPlaces) : "");
4733
+ }
4734
+ case "phone": {
4735
+ if (/^\(\d{3}\)\s?\d{3}-\d{4}$/.test(trimmed)) return "(###) ###-####";
4736
+ if (/^\d{3}-\d{3}-\d{4}$/.test(trimmed)) return "###-###-####";
4737
+ if (/^\+\d/.test(trimmed)) return "+# ###...";
4738
+ return "phone";
4739
+ }
4740
+ default:
4741
+ return dataType;
4742
+ }
4743
+ }
4744
+ function analyzeFormat(elementId, label, rawValue) {
4745
+ const { type: dataType } = classifyDataType(rawValue);
4746
+ const pattern = detectFormatPattern(rawValue, dataType);
4747
+ return {
4748
+ elementId,
4749
+ label,
4750
+ dataType,
4751
+ pattern,
4752
+ example: rawValue.trim()
4753
+ };
4754
+ }
4755
+ function analyzePageFormats(elements) {
4756
+ const descriptors = [];
4757
+ for (const el of elements) {
4758
+ const rawValue = el.state?.value ?? el.state?.textContent ?? "";
4759
+ if (!rawValue) continue;
4760
+ const label = el.accessibleName || el.labelText || el.label || el.description || el.id;
4761
+ descriptors.push(analyzeFormat(el.id, label, rawValue));
4762
+ }
4763
+ return descriptors;
4764
+ }
4765
+ function compareFormats(sourceFormats, targetFormats, config = DEFAULT_FORMAT_ANALYSIS_CONFIG) {
4766
+ const mismatches = [];
4767
+ const targetByLabel = /* @__PURE__ */ new Map();
4768
+ for (const t of targetFormats) {
4769
+ targetByLabel.set(t.label.toLowerCase(), t);
4770
+ }
4771
+ for (const source of sourceFormats) {
4772
+ const target = targetByLabel.get(source.label.toLowerCase());
4773
+ if (!target) continue;
4774
+ if (source.dataType !== target.dataType) {
4775
+ mismatches.push({
4776
+ label: source.label,
4777
+ sourceFormat: source,
4778
+ targetFormat: target,
4779
+ severity: "error",
4780
+ description: `Data type mismatch: source is ${source.dataType}, target is ${target.dataType}`
4781
+ });
4782
+ continue;
4783
+ }
4784
+ if (source.pattern !== target.pattern) {
4785
+ const severity = config.lenientFormatting ? "warning" : "error";
4786
+ mismatches.push({
4787
+ label: source.label,
4788
+ sourceFormat: source,
4789
+ targetFormat: target,
4790
+ severity,
4791
+ description: `Format differs: source uses "${source.pattern}", target uses "${target.pattern}"`
4792
+ });
4793
+ }
4794
+ }
4795
+ return mismatches;
4796
+ }
4797
+
4798
+ // src/ai/cross-app-diff.ts
4799
+ var DEFAULT_CROSS_APP_DIFF_CONFIG = {
4800
+ matchThreshold: 0.5,
4801
+ accessibleNameWeight: 1,
4802
+ textWeight: 0.95,
4803
+ rolePositionWeight: 0.7
4804
+ };
4805
+ function getElementText(el) {
4806
+ return el.accessibleName || el.labelText || el.label || el.state?.textContent || el.description || "";
4807
+ }
4808
+ function getRole(el) {
4809
+ return (el.role || el.type || "").toLowerCase();
4810
+ }
4811
+ function getCenter(el) {
4812
+ const rect = el.state?.rect;
4813
+ if (!rect) return null;
4814
+ return {
4815
+ x: rect.x + rect.width / 2,
4816
+ y: rect.y + rect.height / 2
4817
+ };
4818
+ }
4819
+ function computeMatchScore(source, target, config) {
4820
+ let bestScore = 0;
4821
+ let bestStrategy = "none";
4822
+ const srcName = (source.accessibleName || "").trim();
4823
+ const tgtName = (target.accessibleName || "").trim();
4824
+ if (srcName && tgtName && srcName.toLowerCase() === tgtName.toLowerCase()) {
4825
+ return { score: config.accessibleNameWeight, strategy: "accessible-name-exact" };
4826
+ }
4827
+ const srcText = getElementText(source);
4828
+ const tgtText = getElementText(target);
4829
+ if (srcText && tgtText && srcText.toLowerCase() === tgtText.toLowerCase()) {
4830
+ const score = config.textWeight;
4831
+ if (score > bestScore) {
4832
+ bestScore = score;
4833
+ bestStrategy = "text-exact";
4834
+ }
4835
+ }
4836
+ if (srcText && tgtText) {
4837
+ const srcNorm = normalizeString(srcText);
4838
+ const tgtNorm = normalizeString(tgtText);
4839
+ const similarity = jaroWinklerSimilarity(srcNorm, tgtNorm);
4840
+ const score = similarity * 0.85;
4841
+ if (score > bestScore) {
4842
+ bestScore = score;
4843
+ bestStrategy = "text-fuzzy";
4844
+ }
4845
+ }
4846
+ const srcRole = getRole(source);
4847
+ const tgtRole = getRole(target);
4848
+ if (srcRole && srcRole === tgtRole) {
4849
+ const srcCenter = getCenter(source);
4850
+ const tgtCenter = getCenter(target);
4851
+ if (srcCenter && tgtCenter) {
4852
+ const dx = Math.abs(srcCenter.x - tgtCenter.x) / 1920;
4853
+ const dy = Math.abs(srcCenter.y - tgtCenter.y) / 1080;
4854
+ const posSimilarity = 1 - Math.min(1, Math.sqrt(dx * dx + dy * dy));
4855
+ const score = config.rolePositionWeight * posSimilarity;
4856
+ if (score > bestScore) {
4857
+ bestScore = score;
4858
+ bestStrategy = "role-position";
4859
+ }
4860
+ }
4861
+ }
4862
+ const srcVal = source.state?.value ?? source.state?.textContent ?? "";
4863
+ const tgtVal = target.state?.value ?? target.state?.textContent ?? "";
4864
+ if (srcVal && tgtVal) {
4865
+ const srcType = classifyDataType(srcVal).type;
4866
+ const tgtType = classifyDataType(tgtVal).type;
4867
+ const srcNorm = normalizeValue(srcVal, srcType);
4868
+ const tgtNorm = normalizeValue(tgtVal, tgtType);
4869
+ if (srcNorm === tgtNorm && srcNorm !== "") {
4870
+ const score = 0.6;
4871
+ if (score > bestScore) {
4872
+ bestScore = score;
4873
+ bestStrategy = "data-overlap";
4874
+ }
4875
+ }
4876
+ }
4877
+ return { score: bestScore, strategy: bestStrategy };
4878
+ }
4879
+ function matchElements(sourceElements, targetElements, config = DEFAULT_CROSS_APP_DIFF_CONFIG) {
4880
+ const candidates = [];
4881
+ for (let si = 0; si < sourceElements.length; si++) {
4882
+ for (let ti = 0; ti < targetElements.length; ti++) {
4883
+ const { score, strategy } = computeMatchScore(sourceElements[si], targetElements[ti], config);
4884
+ if (score >= config.matchThreshold) {
4885
+ candidates.push({ sourceIdx: si, targetIdx: ti, score, strategy });
4886
+ }
4887
+ }
4888
+ }
4889
+ candidates.sort((a, b) => b.score - a.score);
4890
+ const usedSource = /* @__PURE__ */ new Set();
4891
+ const usedTarget = /* @__PURE__ */ new Set();
4892
+ const pairs = [];
4893
+ for (const c of candidates) {
4894
+ if (usedSource.has(c.sourceIdx) || usedTarget.has(c.targetIdx)) continue;
4895
+ usedSource.add(c.sourceIdx);
4896
+ usedTarget.add(c.targetIdx);
4897
+ const src = sourceElements[c.sourceIdx];
4898
+ const tgt = targetElements[c.targetIdx];
4899
+ pairs.push({
4900
+ sourceId: src.id,
4901
+ targetId: tgt.id,
4902
+ sourceLabel: getElementText(src) || src.id,
4903
+ targetLabel: getElementText(tgt) || tgt.id,
4904
+ confidence: Math.round(c.score * 100) / 100,
4905
+ matchStrategy: c.strategy
4906
+ });
4907
+ }
4908
+ return pairs;
4909
+ }
4910
+ function computeCrossAppDiff(sourceElements, targetElements, config = DEFAULT_CROSS_APP_DIFF_CONFIG) {
4911
+ const matchedPairs = matchElements(sourceElements, targetElements, config);
4912
+ const matchedSourceIds = new Set(matchedPairs.map((p) => p.sourceId));
4913
+ const matchedTargetIds = new Set(matchedPairs.map((p) => p.targetId));
4914
+ const unmatchedSourceIds = sourceElements.filter((e) => !matchedSourceIds.has(e.id)).map((e) => e.id);
4915
+ const unmatchedTargetIds = targetElements.filter((e) => !matchedTargetIds.has(e.id)).map((e) => e.id);
4916
+ const sourceData = extractPageData(sourceElements);
4917
+ const targetData = extractPageData(targetElements);
4918
+ const dataComparisons = [];
4919
+ for (const pair of matchedPairs) {
4920
+ const srcEntry = Object.values(sourceData.values).find((v) => v.elementId === pair.sourceId);
4921
+ const tgtEntry = Object.values(targetData.values).find((v) => v.elementId === pair.targetId);
4922
+ if (srcEntry && tgtEntry) {
4923
+ dataComparisons.push({
4924
+ label: pair.sourceLabel,
4925
+ sourceValue: srcEntry.rawValue,
4926
+ targetValue: tgtEntry.rawValue,
4927
+ valuesMatch: srcEntry.normalizedValue === tgtEntry.normalizedValue,
4928
+ formatsMatch: srcEntry.dataType === tgtEntry.dataType
4929
+ });
4930
+ }
4931
+ }
4932
+ const sourceFormats = analyzePageFormats(sourceElements);
4933
+ const targetFormats = analyzePageFormats(targetElements);
4934
+ const formatMismatches = compareFormats(sourceFormats, targetFormats);
4935
+ return {
4936
+ matchedPairs,
4937
+ unmatchedSourceIds,
4938
+ unmatchedTargetIds,
4939
+ dataComparisons,
4940
+ formatMismatches
4941
+ };
4942
+ }
4943
+
4944
+ // src/ai/action-parity.ts
4945
+ var DEFAULT_ACTION_PARITY_CONFIG = {
4946
+ ignoreActions: []
4947
+ };
4948
+ function getActions(el, ignoreActions) {
4949
+ const actions = el.actions || el.suggestedActions || [];
4950
+ const ignoreSet = new Set(ignoreActions.map((a) => a.toLowerCase()));
4951
+ return actions.map(
4952
+ (a) => typeof a === "string" ? a : a.action || a.name || ""
4953
+ ).filter((a) => a && !ignoreSet.has(a.toLowerCase()));
4954
+ }
4955
+ function analyzeActionParity(matchedPairs, sourceElements, targetElements, config = DEFAULT_ACTION_PARITY_CONFIG) {
4956
+ const sourceById = new Map(sourceElements.map((e) => [e.id, e]));
4957
+ const targetById = new Map(targetElements.map((e) => [e.id, e]));
4958
+ const results = [];
4959
+ for (const pair of matchedPairs) {
4960
+ const src = sourceById.get(pair.sourceId);
4961
+ const tgt = targetById.get(pair.targetId);
4962
+ if (!src || !tgt) continue;
4963
+ const sourceActions = getActions(src, config.ignoreActions);
4964
+ const targetActions = getActions(tgt, config.ignoreActions);
4965
+ const sourceSet = new Set(sourceActions.map((a) => a.toLowerCase()));
4966
+ const targetSet = new Set(targetActions.map((a) => a.toLowerCase()));
4967
+ const missingInTarget = sourceActions.filter((a) => !targetSet.has(a.toLowerCase()));
4968
+ const missingInSource = targetActions.filter((a) => !sourceSet.has(a.toLowerCase()));
4969
+ results.push({
4970
+ pair,
4971
+ sourceActions,
4972
+ targetActions,
4973
+ missingInTarget,
4974
+ missingInSource
4975
+ });
4976
+ }
4977
+ return results;
4978
+ }
4979
+
4980
+ // src/ai/navigation-map.ts
4981
+ var DEFAULT_NAVIGATION_MAP_CONFIG = {
4982
+ labelMatchThreshold: 0.8
4983
+ };
4984
+ function isNavigationElement(el) {
4985
+ const role = (el.role || "").toLowerCase();
4986
+ const type = (el.type || "").toLowerCase();
4987
+ const semanticType = (el.semanticType || "").toLowerCase();
4988
+ if (["link", "menuitem", "tab"].includes(role)) return true;
4989
+ if (["link", "menuitem"].includes(type)) return true;
4990
+ if (semanticType.includes("nav") || semanticType.includes("menu") || semanticType.includes("tab")) {
4991
+ return true;
4992
+ }
4993
+ const context = (el.parentContext || "").toLowerCase();
4994
+ if (context.includes("nav") || context.includes("menu") || context.includes("sidebar")) {
4995
+ if (role === "button" || type === "button" || role === "link" || type === "link") {
4996
+ return true;
4997
+ }
4998
+ }
4999
+ return false;
5000
+ }
5001
+ function getNavLabel(el) {
5002
+ return el.accessibleName || el.labelText || el.label || el.description || el.id;
5003
+ }
5004
+ function getHref(el) {
5005
+ const state = el.state;
5006
+ return state?.href || void 0;
5007
+ }
5008
+ function hrefsMatch(a, b) {
5009
+ if (!a || !b) return false;
5010
+ const normalize = (h) => h.replace(/^https?:\/\//, "").replace(/localhost:\d+/, "").replace(/\/+$/, "").toLowerCase();
5011
+ return normalize(a) === normalize(b);
5012
+ }
5013
+ function buildNavigationMap(sourceElements, targetElements, config = DEFAULT_NAVIGATION_MAP_CONFIG) {
5014
+ const sourceNav = sourceElements.filter(isNavigationElement);
5015
+ const targetNav = targetElements.filter(isNavigationElement);
5016
+ const pairs = [];
5017
+ const matchedTargetIds = /* @__PURE__ */ new Set();
5018
+ for (const src of sourceNav) {
5019
+ const srcLabel = getNavLabel(src);
5020
+ const srcNorm = normalizeString(srcLabel);
5021
+ let bestTarget = null;
5022
+ let bestScore = 0;
5023
+ for (const tgt of targetNav) {
5024
+ if (matchedTargetIds.has(tgt.id)) continue;
5025
+ const tgtLabel = getNavLabel(tgt);
5026
+ const tgtNorm = normalizeString(tgtLabel);
5027
+ if (srcNorm === tgtNorm) {
5028
+ bestTarget = tgt;
5029
+ bestScore = 1;
5030
+ break;
5031
+ }
5032
+ const similarity = jaroWinklerSimilarity(srcNorm, tgtNorm);
5033
+ if (similarity > bestScore && similarity >= config.labelMatchThreshold) {
5034
+ bestScore = similarity;
5035
+ bestTarget = tgt;
5036
+ }
5037
+ }
5038
+ if (bestTarget) {
5039
+ matchedTargetIds.add(bestTarget.id);
5040
+ const srcHref = getHref(src);
5041
+ const tgtHref = getHref(bestTarget);
5042
+ pairs.push({
5043
+ sourceId: src.id,
5044
+ targetId: bestTarget.id,
5045
+ label: srcLabel,
5046
+ sourceHref: srcHref,
5047
+ targetHref: tgtHref,
5048
+ destinationMatch: hrefsMatch(srcHref, tgtHref)
5049
+ });
5050
+ }
5051
+ }
5052
+ const sourceOnly = sourceNav.filter((s) => !pairs.some((p) => p.sourceId === s.id)).map((s) => s.id);
5053
+ const targetOnly = targetNav.filter((t) => !matchedTargetIds.has(t.id)).map((t) => t.id);
5054
+ return { pairs, sourceOnly, targetOnly };
5055
+ }
5056
+
5057
+ // src/ai/component-comparison.ts
5058
+ var DEFAULT_COMPONENT_COMPARISON_CONFIG = {
5059
+ nameMatchThreshold: 0.75
5060
+ };
5061
+ function computeComponentMatchScore(source, target) {
5062
+ if (source.name.toLowerCase() === target.name.toLowerCase()) return 1;
5063
+ let score = 0;
5064
+ if (source.type === target.type) {
5065
+ score += 0.3;
5066
+ }
5067
+ const nameSimilarity = jaroWinklerSimilarity(
5068
+ normalizeString(source.name),
5069
+ normalizeString(target.name)
5070
+ );
5071
+ score += nameSimilarity * 0.7;
5072
+ return score;
5073
+ }
5074
+ function compareComponents(sourceComponents, targetComponents, config = DEFAULT_COMPONENT_COMPARISON_CONFIG) {
5075
+ const candidates = [];
5076
+ for (let si = 0; si < sourceComponents.length; si++) {
5077
+ for (let ti = 0; ti < targetComponents.length; ti++) {
5078
+ const score = computeComponentMatchScore(sourceComponents[si], targetComponents[ti]);
5079
+ if (score >= config.nameMatchThreshold) {
5080
+ candidates.push({ sourceIdx: si, targetIdx: ti, score });
5081
+ }
5082
+ }
5083
+ }
5084
+ candidates.sort((a, b) => b.score - a.score);
5085
+ const usedSource = /* @__PURE__ */ new Set();
5086
+ const usedTarget = /* @__PURE__ */ new Set();
5087
+ const matches = [];
5088
+ for (const c of candidates) {
5089
+ if (usedSource.has(c.sourceIdx) || usedTarget.has(c.targetIdx)) continue;
5090
+ usedSource.add(c.sourceIdx);
5091
+ usedTarget.add(c.targetIdx);
5092
+ const src = sourceComponents[c.sourceIdx];
5093
+ const tgt = targetComponents[c.targetIdx];
5094
+ const srcKeys = new Set(src.stateKeys);
5095
+ const tgtKeys = new Set(tgt.stateKeys);
5096
+ const missingKeys = src.stateKeys.filter((k) => !tgtKeys.has(k));
5097
+ const extraKeys = tgt.stateKeys.filter((k) => !srcKeys.has(k));
5098
+ const srcActions = new Set(src.actions.map((a) => a.toLowerCase()));
5099
+ const tgtActions = new Set(tgt.actions.map((a) => a.toLowerCase()));
5100
+ const missingActions = src.actions.filter((a) => !tgtActions.has(a.toLowerCase()));
5101
+ const extraActions = tgt.actions.filter((a) => !srcActions.has(a.toLowerCase()));
5102
+ matches.push({
5103
+ source: src,
5104
+ target: tgt,
5105
+ confidence: Math.round(c.score * 100) / 100,
5106
+ stateKeyDiff: { missing: missingKeys, extra: extraKeys },
5107
+ actionDiff: { missing: missingActions, extra: extraActions }
5108
+ });
5109
+ }
5110
+ const sourceOnly = sourceComponents.filter((_, i) => !usedSource.has(i));
5111
+ const targetOnly = targetComponents.filter((_, i) => !usedTarget.has(i));
5112
+ return { matches, sourceOnly, targetOnly };
5113
+ }
5114
+
5115
+ // src/ai/layout-comparison.ts
5116
+ var DEFAULT_LAYOUT_COMPARISON_CONFIG = {
5117
+ gridTolerance: 20
5118
+ };
5119
+ function getRect(el) {
5120
+ const rect = el.state?.rect;
5121
+ if (!rect || !rect.width) return null;
5122
+ return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
5123
+ }
5124
+ function clusterValues(values, tolerance) {
5125
+ if (values.length === 0) return [];
5126
+ const sorted = [...values].sort((a, b) => a - b);
5127
+ const clusters = [sorted[0]];
5128
+ for (let i = 1; i < sorted.length; i++) {
5129
+ if (sorted[i] - clusters[clusters.length - 1] > tolerance) {
5130
+ clusters.push(sorted[i]);
5131
+ }
5132
+ }
5133
+ return clusters;
5134
+ }
5135
+ function detectGridStructure(elements, config = DEFAULT_LAYOUT_COMPARISON_CONFIG) {
5136
+ const rects = elements.map(getRect).filter((r) => r !== null);
5137
+ const xPositions = rects.map((r) => r.x);
5138
+ const yPositions = rects.map((r) => r.y);
5139
+ const columns = clusterValues(xPositions, config.gridTolerance);
5140
+ const rows = clusterValues(yPositions, config.gridTolerance);
5141
+ return {
5142
+ columns,
5143
+ rows,
5144
+ columnCount: columns.length,
5145
+ rowCount: rows.length
5146
+ };
5147
+ }
5148
+ function computeMaxDepth(elements) {
5149
+ let maxDepth = 0;
5150
+ for (const el of elements) {
5151
+ const context = el.parentContext || "";
5152
+ const depth = context ? context.split(">").length : 1;
5153
+ maxDepth = Math.max(maxDepth, depth);
5154
+ }
5155
+ return maxDepth;
5156
+ }
5157
+ function compareLayouts(sourceElements, targetElements, sourceRegions, targetRegions, config = DEFAULT_LAYOUT_COMPARISON_CONFIG) {
5158
+ const sourceGrid = detectGridStructure(sourceElements, config);
5159
+ const targetGrid = detectGridStructure(targetElements, config);
5160
+ const gridDiff = {
5161
+ sourceGrid,
5162
+ targetGrid,
5163
+ columnDiff: sourceGrid.columnCount - targetGrid.columnCount,
5164
+ rowDiff: sourceGrid.rowCount - targetGrid.rowCount
5165
+ };
5166
+ const sourceDepth = computeMaxDepth(sourceElements);
5167
+ const targetDepth = computeMaxDepth(targetElements);
5168
+ const hierarchyDiff = {
5169
+ sourceDepth,
5170
+ targetDepth,
5171
+ depthDiff: sourceDepth - targetDepth
5172
+ };
5173
+ const sourceRegionCount = sourceRegions?.regions.length || 1;
5174
+ const targetRegionCount = targetRegions?.regions.length || 1;
5175
+ const sourceDensity = sourceElements.length / sourceRegionCount;
5176
+ const targetDensity = targetElements.length / targetRegionCount;
5177
+ const density = {
5178
+ sourceDensity: Math.round(sourceDensity * 100) / 100,
5179
+ targetDensity: Math.round(targetDensity * 100) / 100,
5180
+ ratio: targetDensity > 0 ? Math.round(sourceDensity / targetDensity * 100) / 100 : 0
5181
+ };
5182
+ const gridSimilarity = sourceGrid.columnCount === 0 && targetGrid.columnCount === 0 ? 1 : 1 - Math.abs(gridDiff.columnDiff) / Math.max(sourceGrid.columnCount, targetGrid.columnCount, 1);
5183
+ const hierarchySimilarity = sourceDepth === 0 && targetDepth === 0 ? 1 : 1 - Math.abs(hierarchyDiff.depthDiff) / Math.max(sourceDepth, targetDepth, 1);
5184
+ const densitySimilarity = density.ratio > 0 ? Math.min(density.ratio, 1 / density.ratio) : 0;
5185
+ const similarity = Math.round((gridSimilarity * 0.4 + hierarchySimilarity * 0.3 + densitySimilarity * 0.3) * 100) / 100;
5186
+ return {
5187
+ gridDiff,
5188
+ hierarchyDiff,
5189
+ density,
5190
+ similarity
5191
+ };
5192
+ }
5193
+
5194
+ // src/ai/content-comparison.ts
5195
+ var DEFAULT_CONTENT_COMPARISON_CONFIG = {
5196
+ labelMatchThreshold: 0.8,
5197
+ headingMatchThreshold: 0.75,
5198
+ maxCellDifferences: 50
5199
+ };
5200
+ function getElementText2(el) {
5201
+ return (el.accessibleName || el.labelText || el.label || el.state?.textContent || el.description || "").trim();
5202
+ }
5203
+ function getContentRole(el) {
5204
+ if (el.contentMetadata?.contentRole) {
5205
+ return el.contentMetadata.contentRole;
5206
+ }
5207
+ const t = (el.type || "").toLowerCase();
5208
+ if (t === "heading" || t.startsWith("h") && /^h[1-6]$/.test(t)) return "heading";
5209
+ if (t === "metric-value" || t === "metric") return "metric";
5210
+ if (t === "status-message" || t === "status") return "status";
5211
+ if (t === "label") return "label";
5212
+ if (t === "badge") return "badge";
5213
+ if (t === "table-cell") return "table-cell";
5214
+ if (t === "table-header") return "table-header";
5215
+ if (t === "caption") return "caption";
5216
+ return null;
5217
+ }
5218
+ function getHeadingLevel(el) {
5219
+ if (el.contentMetadata?.headingLevel) {
5220
+ return el.contentMetadata.headingLevel;
5221
+ }
5222
+ const tag = (el.tagName || el.type || "").toLowerCase();
5223
+ const match = /^h([1-6])$/.exec(tag);
5224
+ if (match) return parseInt(match[1], 10);
5225
+ return void 0;
5226
+ }
5227
+ function isContentElement2(el) {
5228
+ if (el.category === "content") return true;
5229
+ if (el.contentMetadata) return true;
5230
+ const role = getContentRole(el);
5231
+ return role !== null;
5232
+ }
5233
+ function normalizeText(text) {
5234
+ return normalizeString(text, { caseSensitive: false, ignoreWhitespace: true });
5235
+ }
5236
+ function parseMetricText(el) {
5237
+ const text = getElementText2(el);
5238
+ const colonMatch = text.match(/^(.+?):\s*(.+)$/);
5239
+ if (colonMatch) {
5240
+ return { label: colonMatch[1].trim(), value: colonMatch[2].trim() };
5241
+ }
5242
+ const dashMatch = text.match(/^(.+?)\s*[-]\s*(.+)$/);
5243
+ if (dashMatch) {
5244
+ return { label: dashMatch[1].trim(), value: dashMatch[2].trim() };
5245
+ }
5246
+ const elLabel = el.accessibleName || el.labelText || el.label || el.id;
5247
+ return { label: elLabel, value: text };
5248
+ }
5249
+ function filterHeadings(elements) {
5250
+ return elements.filter((el) => getContentRole(el) === "heading");
5251
+ }
5252
+ function filterMetrics(elements) {
5253
+ return elements.filter((el) => getContentRole(el) === "metric");
5254
+ }
5255
+ function filterStatuses(elements) {
5256
+ return elements.filter((el) => {
5257
+ const role = getContentRole(el);
5258
+ return role === "status" || role === "badge";
5259
+ });
5260
+ }
5261
+ function filterLabels(elements) {
5262
+ return elements.filter((el) => {
5263
+ const role = getContentRole(el);
5264
+ return role === "label" || role === "caption";
5265
+ });
5266
+ }
5267
+ function matchTexts(sourceTexts, targetTexts, threshold) {
5268
+ const candidates = [];
5269
+ for (let si = 0; si < sourceTexts.length; si++) {
5270
+ const sNorm = normalizeText(sourceTexts[si]);
5271
+ if (!sNorm) continue;
5272
+ for (let ti = 0; ti < targetTexts.length; ti++) {
5273
+ const tNorm = normalizeText(targetTexts[ti]);
5274
+ if (!tNorm) continue;
5275
+ const score = sNorm === tNorm ? 1 : jaroWinklerSimilarity(sNorm, tNorm);
5276
+ if (score >= threshold) {
5277
+ candidates.push({ sourceIdx: si, targetIdx: ti, score });
5278
+ }
5279
+ }
5280
+ }
5281
+ candidates.sort((a, b) => b.score - a.score);
5282
+ const usedSource = /* @__PURE__ */ new Set();
5283
+ const usedTarget = /* @__PURE__ */ new Set();
5284
+ const matched = [];
5285
+ for (const c of candidates) {
5286
+ if (usedSource.has(c.sourceIdx) || usedTarget.has(c.targetIdx)) continue;
5287
+ usedSource.add(c.sourceIdx);
5288
+ usedTarget.add(c.targetIdx);
5289
+ matched.push(c);
5290
+ }
5291
+ const unmatchedSource = sourceTexts.map((_, i) => i).filter((i) => !usedSource.has(i));
5292
+ const unmatchedTarget = targetTexts.map((_, i) => i).filter((i) => !usedTarget.has(i));
5293
+ return { matched, unmatchedSource, unmatchedTarget };
5294
+ }
5295
+ function compareHeadings(sourceElements, targetElements, config) {
5296
+ const srcHeadings = filterHeadings(sourceElements);
5297
+ const tgtHeadings = filterHeadings(targetElements);
5298
+ const srcTexts = srcHeadings.map(getElementText2);
5299
+ const tgtTexts = tgtHeadings.map(getElementText2);
5300
+ const { matched, unmatchedSource, unmatchedTarget } = matchTexts(
5301
+ srcTexts,
5302
+ tgtTexts,
5303
+ config.headingMatchThreshold
5304
+ );
5305
+ const headingMatched = [];
5306
+ const headingChanged = [];
5307
+ for (const m of matched) {
5308
+ const srcText = srcTexts[m.sourceIdx];
5309
+ const tgtText = tgtTexts[m.targetIdx];
5310
+ const srcLevel = getHeadingLevel(srcHeadings[m.sourceIdx]);
5311
+ const tgtLevel = getHeadingLevel(tgtHeadings[m.targetIdx]);
5312
+ if (normalizeText(srcText) === normalizeText(tgtText)) {
5313
+ headingMatched.push({
5314
+ source: srcText,
5315
+ target: tgtText,
5316
+ level: srcLevel
5317
+ });
5318
+ } else {
5319
+ headingChanged.push({
5320
+ source: srcText,
5321
+ target: tgtText,
5322
+ level: srcLevel ?? tgtLevel
5323
+ });
5324
+ }
5325
+ }
5326
+ return {
5327
+ matched: headingMatched,
5328
+ sourceOnly: unmatchedSource.map((i) => srcTexts[i]),
5329
+ targetOnly: unmatchedTarget.map((i) => tgtTexts[i]),
5330
+ changed: headingChanged
5331
+ };
5332
+ }
5333
+ function compareMetrics(sourceElements, targetElements, config) {
5334
+ const srcMetrics = filterMetrics(sourceElements);
5335
+ const tgtMetrics = filterMetrics(targetElements);
5336
+ const srcParsed = srcMetrics.map(parseMetricText);
5337
+ const tgtParsed = tgtMetrics.map(parseMetricText);
5338
+ const srcLabels = srcParsed.map((p) => p.label);
5339
+ const tgtLabels = tgtParsed.map((p) => p.label);
5340
+ const { matched, unmatchedSource, unmatchedTarget } = matchTexts(
5341
+ srcLabels,
5342
+ tgtLabels,
5343
+ config.labelMatchThreshold
5344
+ );
5345
+ const metricMatched = [];
5346
+ const metricChanged = [];
5347
+ for (const m of matched) {
5348
+ const src = srcParsed[m.sourceIdx];
5349
+ const tgt = tgtParsed[m.targetIdx];
5350
+ if (normalizeText(src.value) === normalizeText(tgt.value)) {
5351
+ metricMatched.push({
5352
+ label: src.label,
5353
+ sourceValue: src.value,
5354
+ targetValue: tgt.value
5355
+ });
5356
+ } else {
5357
+ metricChanged.push({
5358
+ label: src.label,
5359
+ sourceValue: src.value,
5360
+ targetValue: tgt.value
5361
+ });
5362
+ }
5363
+ }
5364
+ return {
5365
+ matched: metricMatched,
5366
+ changed: metricChanged,
5367
+ sourceOnly: unmatchedSource.map((i) => srcParsed[i].label),
5368
+ targetOnly: unmatchedTarget.map((i) => tgtParsed[i].label)
5369
+ };
5370
+ }
5371
+ function compareStatuses(sourceElements, targetElements, config) {
5372
+ const srcStatuses = filterStatuses(sourceElements);
5373
+ const tgtStatuses = filterStatuses(targetElements);
5374
+ const srcParsed = srcStatuses.map(parseMetricText);
5375
+ const tgtParsed = tgtStatuses.map(parseMetricText);
5376
+ const srcLabels = srcParsed.map((p) => p.label);
5377
+ const tgtLabels = tgtParsed.map((p) => p.label);
5378
+ const { matched } = matchTexts(srcLabels, tgtLabels, config.labelMatchThreshold);
5379
+ const statusMatched = [];
5380
+ const statusChanged = [];
5381
+ for (const m of matched) {
5382
+ const src = srcParsed[m.sourceIdx];
5383
+ const tgt = tgtParsed[m.targetIdx];
5384
+ if (normalizeText(src.value) === normalizeText(tgt.value)) {
5385
+ statusMatched.push({
5386
+ label: src.label,
5387
+ sourceStatus: src.value,
5388
+ targetStatus: tgt.value
5389
+ });
5390
+ } else {
5391
+ statusChanged.push({
5392
+ label: src.label,
5393
+ sourceStatus: src.value,
5394
+ targetStatus: tgt.value
5395
+ });
5396
+ }
5397
+ }
5398
+ return {
5399
+ matched: statusMatched,
5400
+ changed: statusChanged
5401
+ };
5402
+ }
5403
+ function compareLabels(sourceElements, targetElements, config) {
5404
+ const srcLabels = filterLabels(sourceElements);
5405
+ const tgtLabels = filterLabels(targetElements);
5406
+ const srcTexts = srcLabels.map(getElementText2);
5407
+ const tgtTexts = tgtLabels.map(getElementText2);
5408
+ const { matched, unmatchedSource, unmatchedTarget } = matchTexts(
5409
+ srcTexts,
5410
+ tgtTexts,
5411
+ config.labelMatchThreshold
5412
+ );
5413
+ return {
5414
+ matched: matched.map((m) => srcTexts[m.sourceIdx]),
5415
+ sourceOnly: unmatchedSource.map((i) => srcTexts[i]),
5416
+ targetOnly: unmatchedTarget.map((i) => tgtTexts[i])
5417
+ };
5418
+ }
5419
+ function compareTables(sourceElements, targetElements, config) {
5420
+ const srcData = extractStructuredData(sourceElements);
5421
+ const tgtData = extractStructuredData(targetElements);
5422
+ const srcTables = srcData.tables;
5423
+ const tgtTables = tgtData.tables;
5424
+ if (srcTables.length === 0 || tgtTables.length === 0) {
5425
+ return [];
5426
+ }
5427
+ const srcTableLabels = srcTables.map((t) => t.label || "");
5428
+ const tgtTableLabels = tgtTables.map((t) => t.label || "");
5429
+ const { matched } = matchTexts(srcTableLabels, tgtTableLabels, config.labelMatchThreshold);
5430
+ const tablePairs = [];
5431
+ if (matched.length > 0) {
5432
+ for (const m of matched) {
5433
+ tablePairs.push({ srcIdx: m.sourceIdx, tgtIdx: m.targetIdx });
5434
+ }
5435
+ } else if (srcTables.length === 1 && tgtTables.length === 1) {
5436
+ tablePairs.push({ srcIdx: 0, tgtIdx: 0 });
5437
+ }
5438
+ const comparisons = [];
5439
+ for (const pair of tablePairs) {
5440
+ const srcTable = srcTables[pair.srcIdx];
5441
+ const tgtTable = tgtTables[pair.tgtIdx];
5442
+ const srcHeaders = srcTable.columns.map((c) => c.header);
5443
+ const tgtHeaders = tgtTable.columns.map((c) => c.header);
5444
+ const srcHeaderSet = new Set(srcHeaders.map(normalizeText));
5445
+ const tgtHeaderSet = new Set(tgtHeaders.map(normalizeText));
5446
+ const sourceOnlyColumns = srcHeaders.filter((h) => !tgtHeaderSet.has(normalizeText(h)));
5447
+ const targetOnlyColumns = tgtHeaders.filter((h) => !srcHeaderSet.has(normalizeText(h)));
5448
+ const columnsMatch = sourceOnlyColumns.length === 0 && targetOnlyColumns.length === 0;
5449
+ const cellDifferences = [];
5450
+ const commonHeaders = srcHeaders.filter((h) => tgtHeaderSet.has(normalizeText(h)));
5451
+ const minRows = Math.min(srcTable.rows.length, tgtTable.rows.length);
5452
+ for (let row = 0; row < minRows; row++) {
5453
+ if (cellDifferences.length >= config.maxCellDifferences) break;
5454
+ for (const header of commonHeaders) {
5455
+ const srcColIdx = srcHeaders.indexOf(header);
5456
+ const tgtColIdx = tgtHeaders.findIndex((h) => normalizeText(h) === normalizeText(header));
5457
+ if (srcColIdx < 0 || tgtColIdx < 0) continue;
5458
+ const srcValue = srcTable.rows[row]?.[srcColIdx] ?? "";
5459
+ const tgtValue = tgtTable.rows[row]?.[tgtColIdx] ?? "";
5460
+ if (normalizeText(srcValue) !== normalizeText(tgtValue)) {
5461
+ cellDifferences.push({
5462
+ row,
5463
+ column: header,
5464
+ sourceValue: srcValue,
5465
+ targetValue: tgtValue
5466
+ });
5467
+ }
5468
+ }
5469
+ }
5470
+ comparisons.push({
5471
+ sourceLabel: srcTable.label,
5472
+ targetLabel: tgtTable.label,
5473
+ columnsMatch,
5474
+ sourceOnlyColumns,
5475
+ targetOnlyColumns,
5476
+ sourceRowCount: srcTable.rows.length,
5477
+ targetRowCount: tgtTable.rows.length,
5478
+ cellDifferences
5479
+ });
5480
+ }
5481
+ return comparisons;
5482
+ }
5483
+ function compareHeadingHierarchy(sourceElements, targetElements) {
5484
+ const srcHeadings = filterHeadings(sourceElements);
5485
+ const tgtHeadings = filterHeadings(targetElements);
5486
+ const srcByLevel = /* @__PURE__ */ new Map();
5487
+ const tgtByLevel = /* @__PURE__ */ new Map();
5488
+ for (const el of srcHeadings) {
5489
+ const level = getHeadingLevel(el) ?? 0;
5490
+ srcByLevel.set(level, (srcByLevel.get(level) ?? 0) + 1);
5491
+ }
5492
+ for (const el of tgtHeadings) {
5493
+ const level = getHeadingLevel(el) ?? 0;
5494
+ tgtByLevel.set(level, (tgtByLevel.get(level) ?? 0) + 1);
5495
+ }
5496
+ const allLevels = /* @__PURE__ */ new Set([...srcByLevel.keys(), ...tgtByLevel.keys()]);
5497
+ const result = [];
5498
+ for (const level of [...allLevels].sort()) {
5499
+ result.push({
5500
+ level,
5501
+ sourceCount: srcByLevel.get(level) ?? 0,
5502
+ targetCount: tgtByLevel.get(level) ?? 0
5503
+ });
5504
+ }
5505
+ return result;
5506
+ }
5507
+ function compareContent(sourceElements, targetElements, config = DEFAULT_CONTENT_COMPARISON_CONFIG) {
5508
+ const srcContent = sourceElements.filter(isContentElement2);
5509
+ const tgtContent = targetElements.filter(isContentElement2);
5510
+ const headings = compareHeadings(srcContent, tgtContent, config);
5511
+ const metrics = compareMetrics(srcContent, tgtContent, config);
5512
+ const statuses = compareStatuses(srcContent, tgtContent, config);
5513
+ const labels = compareLabels(srcContent, tgtContent, config);
5514
+ const tables = compareTables(sourceElements, targetElements, config);
5515
+ const headingHierarchy = compareHeadingHierarchy(srcContent, tgtContent);
5516
+ const contentParity = calculateContentParity(headings, metrics, statuses, labels, tables);
5517
+ return {
5518
+ headings,
5519
+ metrics,
5520
+ statuses,
5521
+ labels,
5522
+ tables,
5523
+ headingHierarchy,
5524
+ contentParity
5525
+ };
5526
+ }
5527
+ function calculateContentParity(headings, metrics, statuses, labels, tables) {
5528
+ const scores = [];
5529
+ const totalHeadings = headings.matched.length + headings.changed.length + headings.sourceOnly.length + headings.targetOnly.length;
5530
+ if (totalHeadings > 0) {
5531
+ scores.push(headings.matched.length / totalHeadings);
5532
+ }
5533
+ const totalMetrics = metrics.matched.length + metrics.changed.length + metrics.sourceOnly.length + metrics.targetOnly.length;
5534
+ if (totalMetrics > 0) {
5535
+ const metricScore = (metrics.matched.length + metrics.changed.length * 0.5) / totalMetrics;
5536
+ scores.push(metricScore);
5537
+ }
5538
+ const totalStatuses = statuses.matched.length + statuses.changed.length;
5539
+ if (totalStatuses > 0) {
5540
+ scores.push(statuses.matched.length / totalStatuses);
5541
+ }
5542
+ const totalLabels = labels.matched.length + labels.sourceOnly.length + labels.targetOnly.length;
5543
+ if (totalLabels > 0) {
5544
+ scores.push(labels.matched.length / totalLabels);
5545
+ }
5546
+ if (tables.length > 0) {
5547
+ let tableScore = 0;
5548
+ for (const table of tables) {
5549
+ let tScore = table.columnsMatch ? 0.5 : 0;
5550
+ if (table.sourceRowCount > 0) {
5551
+ const rowRatio = Math.min(
5552
+ table.targetRowCount / table.sourceRowCount,
5553
+ table.sourceRowCount / table.targetRowCount
5554
+ );
5555
+ tScore += rowRatio * 0.3;
5556
+ } else {
5557
+ tScore += 0.3;
5558
+ }
5559
+ const totalCells = Math.max(table.sourceRowCount, 1) * Math.max(
5560
+ table.sourceOnlyColumns.length + table.targetOnlyColumns.length + (table.columnsMatch ? 1 : 0),
5561
+ 1
5562
+ );
5563
+ const diffRatio = totalCells > 0 ? 1 - Math.min(table.cellDifferences.length / totalCells, 1) : 1;
5564
+ tScore += diffRatio * 0.2;
5565
+ tableScore += tScore;
5566
+ }
5567
+ scores.push(tableScore / tables.length);
5568
+ }
5569
+ if (scores.length === 0) return 1;
5570
+ return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length * 100) / 100;
5571
+ }
5572
+
5573
+ // src/ai/comparison-report.ts
5574
+ var DEFAULT_COMPARISON_REPORT_CONFIG = {
5575
+ includeComponents: false
5576
+ };
5577
+ function generateComparisonReport(source, target, options) {
5578
+ const startTime = Date.now();
5579
+ const config = { ...DEFAULT_COMPARISON_REPORT_CONFIG, ...options?.config };
5580
+ const srcElements = source.elements;
5581
+ const tgtElements = target.elements;
5582
+ const diff = computeCrossAppDiff(srcElements, tgtElements);
5583
+ const navigation = buildNavigationMap(srcElements, tgtElements);
5584
+ const sourceRegions = segmentPageRegions(srcElements);
5585
+ const targetRegions = segmentPageRegions(tgtElements);
5586
+ const layout = compareLayouts(srcElements, tgtElements, sourceRegions, targetRegions);
5587
+ const actionParityResults = analyzeActionParity(diff.matchedPairs, srcElements, tgtElements);
5588
+ const componentComparison = config.includeComponents && options?.sourceComponents && options?.targetComponents ? compareComponents(options.sourceComponents, options.targetComponents) : null;
5589
+ const contentComparison = compareContent(srcElements, tgtElements);
5590
+ const sourceData = extractPageData(srcElements);
5591
+ extractPageData(tgtElements);
5592
+ const sourceFieldCount = Object.keys(sourceData.values).length;
5593
+ const matchedDataCount = diff.dataComparisons.length;
5594
+ const dataCompleteness = sourceFieldCount > 0 ? Math.round(matchedDataCount / sourceFieldCount * 100) / 100 : 1;
5595
+ const formatMatchCount = diff.dataComparisons.filter((c) => c.formatsMatch).length;
5596
+ const formatAlignment = matchedDataCount > 0 ? Math.round(formatMatchCount / matchedDataCount * 100) / 100 : 1;
5597
+ const presentationAlignment = layout.similarity;
5598
+ const totalNavItems = navigation.pairs.length + navigation.sourceOnly.length;
5599
+ const navigationParity = totalNavItems > 0 ? Math.round(navigation.pairs.length / totalNavItems * 100) / 100 : 1;
5600
+ const totalActionChecks = actionParityResults.length;
5601
+ const fullParityCount = actionParityResults.filter((r) => r.missingInTarget.length === 0).length;
5602
+ const actionParity = totalActionChecks > 0 ? Math.round(fullParityCount / totalActionChecks * 100) / 100 : 1;
5603
+ const contentParity = contentComparison.contentParity;
5604
+ const overallScore = Math.round(
5605
+ (dataCompleteness * 0.2 + formatAlignment * 0.1 + presentationAlignment * 0.15 + navigationParity * 0.15 + actionParity * 0.15 + contentParity * 0.25) * 100
5606
+ ) / 100;
5607
+ const issues = [];
5608
+ for (const srcId of diff.unmatchedSourceIds) {
5609
+ const srcVal = Object.values(sourceData.values).find((v) => v.elementId === srcId);
5610
+ if (srcVal) {
5611
+ issues.push({
5612
+ severity: "warning",
5613
+ category: "missing-data",
5614
+ description: `Data field "${srcVal.label}" (${srcVal.dataType}) exists in source but has no match in target`,
5615
+ sourceElementId: srcId
5616
+ });
5617
+ }
5618
+ }
5619
+ for (const comp of diff.dataComparisons) {
5620
+ if (!comp.valuesMatch) {
5621
+ issues.push({
5622
+ severity: "error",
5623
+ category: "value-mismatch",
5624
+ description: `Value mismatch for "${comp.label}": source="${comp.sourceValue}", target="${comp.targetValue}"`
5625
+ });
5626
+ }
5627
+ }
5628
+ for (const fm of diff.formatMismatches) {
5629
+ issues.push({
5630
+ severity: fm.severity,
5631
+ category: "format-mismatch",
5632
+ description: fm.description
5633
+ });
5634
+ }
5635
+ for (const ap of actionParityResults) {
5636
+ for (const action of ap.missingInTarget) {
5637
+ issues.push({
5638
+ severity: "warning",
5639
+ category: "missing-action",
5640
+ description: `Action "${action}" available on source element "${ap.pair.sourceLabel}" is missing in target`,
5641
+ sourceElementId: ap.pair.sourceId,
5642
+ targetElementId: ap.pair.targetId
5643
+ });
5644
+ }
5645
+ }
5646
+ for (const srcId of navigation.sourceOnly) {
5647
+ issues.push({
5648
+ severity: "warning",
5649
+ category: "navigation-gap",
5650
+ description: `Navigation item "${srcId}" in source has no match in target`,
5651
+ sourceElementId: srcId
5652
+ });
5653
+ }
5654
+ if (layout.similarity < 0.5) {
5655
+ issues.push({
5656
+ severity: "warning",
5657
+ category: "layout-difference",
5658
+ description: `Layout similarity is low (${layout.similarity}). Grid: ${layout.gridDiff.sourceGrid.columnCount} cols vs ${layout.gridDiff.targetGrid.columnCount} cols`
5659
+ });
5660
+ }
5661
+ if (componentComparison) {
5662
+ for (const src of componentComparison.sourceOnly) {
5663
+ issues.push({
5664
+ severity: "info",
5665
+ category: "component-mismatch",
5666
+ description: `Component "${src.name}" (${src.type}) exists in source but not target`
5667
+ });
5668
+ }
5669
+ for (const match of componentComparison.matches) {
5670
+ if (match.stateKeyDiff.missing.length > 0) {
5671
+ issues.push({
5672
+ severity: "warning",
5673
+ category: "component-mismatch",
5674
+ description: `Component "${match.source.name}": state keys missing in target: ${match.stateKeyDiff.missing.join(", ")}`
5675
+ });
5676
+ }
5677
+ }
5678
+ }
5679
+ for (const heading of contentComparison.headings.sourceOnly) {
5680
+ issues.push({
5681
+ severity: "warning",
5682
+ category: "content-difference",
5683
+ description: `Heading "${heading}" exists in source but not in target`
5684
+ });
5685
+ }
5686
+ for (const heading of contentComparison.headings.targetOnly) {
5687
+ issues.push({
5688
+ severity: "info",
5689
+ category: "content-difference",
5690
+ description: `Heading "${heading}" exists in target but not in source`
5691
+ });
5692
+ }
5693
+ for (const change of contentComparison.headings.changed) {
5694
+ issues.push({
5695
+ severity: "warning",
5696
+ category: "content-difference",
5697
+ description: `Heading changed: "${change.source}" -> "${change.target}"`
5698
+ });
5699
+ }
5700
+ for (const change of contentComparison.metrics.changed) {
5701
+ issues.push({
5702
+ severity: "warning",
5703
+ category: "content-difference",
5704
+ description: `Metric "${change.label}" value differs: "${change.sourceValue}" vs "${change.targetValue}"`
5705
+ });
5706
+ }
5707
+ for (const label of contentComparison.metrics.sourceOnly) {
5708
+ issues.push({
5709
+ severity: "warning",
5710
+ category: "content-difference",
5711
+ description: `Metric "${label}" exists in source but not in target`
5712
+ });
5713
+ }
5714
+ for (const change of contentComparison.statuses.changed) {
5715
+ issues.push({
5716
+ severity: "warning",
5717
+ category: "content-difference",
5718
+ description: `Status "${change.label}" differs: "${change.sourceStatus}" vs "${change.targetStatus}"`
5719
+ });
5720
+ }
5721
+ for (const table of contentComparison.tables) {
5722
+ if (!table.columnsMatch) {
5723
+ issues.push({
5724
+ severity: "warning",
5725
+ category: "content-difference",
5726
+ description: `Table "${table.sourceLabel}" column mismatch: source-only=[${table.sourceOnlyColumns.join(", ")}], target-only=[${table.targetOnlyColumns.join(", ")}]`
5727
+ });
5728
+ }
5729
+ if (table.sourceRowCount !== table.targetRowCount) {
5730
+ issues.push({
5731
+ severity: "info",
5732
+ category: "content-difference",
5733
+ description: `Table "${table.sourceLabel}" row count differs: ${table.sourceRowCount} vs ${table.targetRowCount}`
5734
+ });
5735
+ }
5736
+ if (table.cellDifferences.length > 0) {
5737
+ issues.push({
5738
+ severity: "warning",
5739
+ category: "content-difference",
5740
+ description: `Table "${table.sourceLabel}" has ${table.cellDifferences.length} cell value difference(s)`
5741
+ });
5742
+ }
5743
+ }
5744
+ const severityOrder = { error: 0, warning: 1, info: 2 };
5745
+ issues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
5746
+ const errorCount = issues.filter((i) => i.severity === "error").length;
5747
+ const warningCount = issues.filter((i) => i.severity === "warning").length;
5748
+ const infoCount = issues.filter((i) => i.severity === "info").length;
5749
+ const summaryLines = [
5750
+ `Cross-app comparison: ${source.page.url} vs ${target.page.url}`,
5751
+ `Overall score: ${(overallScore * 100).toFixed(0)}%`,
5752
+ `Matched elements: ${diff.matchedPairs.length}`,
5753
+ `Unmatched: ${diff.unmatchedSourceIds.length} source, ${diff.unmatchedTargetIds.length} target`,
5754
+ `Navigation: ${navigation.pairs.length} matched, ${navigation.sourceOnly.length} source-only, ${navigation.targetOnly.length} target-only`
5755
+ ];
5756
+ if (componentComparison) {
5757
+ summaryLines.push(
5758
+ `Components: ${componentComparison.matches.length} matched, ${componentComparison.sourceOnly.length} source-only, ${componentComparison.targetOnly.length} target-only`
5759
+ );
5760
+ }
5761
+ const hMatched = contentComparison.headings.matched.length;
5762
+ const hChanged = contentComparison.headings.changed.length;
5763
+ const hSrcOnly = contentComparison.headings.sourceOnly.length;
5764
+ const hTgtOnly = contentComparison.headings.targetOnly.length;
5765
+ const mMatched = contentComparison.metrics.matched.length;
5766
+ const mChanged = contentComparison.metrics.changed.length;
5767
+ const sMatched = contentComparison.statuses.matched.length;
5768
+ const sChanged = contentComparison.statuses.changed.length;
5769
+ const totalContent = hMatched + hChanged + hSrcOnly + hTgtOnly + mMatched + mChanged + sMatched + sChanged;
5770
+ if (totalContent > 0) {
5771
+ summaryLines.push(
5772
+ `Content: headings=${hMatched} matched/${hChanged} changed/${hSrcOnly + hTgtOnly} unmatched, metrics=${mMatched} matched/${mChanged} changed, statuses=${sMatched} matched/${sChanged} changed, parity=${(contentParity * 100).toFixed(0)}%`
5773
+ );
5774
+ }
5775
+ summaryLines.push(`Issues: ${errorCount} errors, ${warningCount} warnings, ${infoCount} info`);
5776
+ const summary = summaryLines.join("\n");
5777
+ const report = {
5778
+ sourceUrl: source.page.url,
5779
+ targetUrl: target.page.url,
5780
+ timestamp: Date.now(),
5781
+ durationMs: Date.now() - startTime,
5782
+ scores: {
5783
+ dataCompleteness,
5784
+ formatAlignment,
5785
+ presentationAlignment,
5786
+ navigationParity,
5787
+ actionParity,
5788
+ overallScore
5789
+ },
5790
+ diff,
5791
+ navigation,
5792
+ layout,
5793
+ contentComparison,
5794
+ issues,
5795
+ summary
5796
+ };
5797
+ if (componentComparison) {
5798
+ report.components = componentComparison;
5799
+ }
5800
+ return report;
5801
+ }
5802
+
5803
+ // src/server/handlers.ts
5804
+ function success(data) {
5805
+ return {
5806
+ success: true,
5807
+ data,
5808
+ timestamp: Date.now()
5809
+ };
5810
+ }
5811
+ function error(message, code) {
5812
+ return {
5813
+ success: false,
5814
+ error: message,
5815
+ code,
5816
+ timestamp: Date.now()
5817
+ };
5818
+ }
5819
+ function getRecoverySuggestions(errorCode) {
5820
+ switch (errorCode) {
5821
+ case "ELEMENT_NOT_FOUND":
5822
+ return [
5823
+ {
5824
+ suggestion: "Wait for the page to fully load",
5825
+ command: "wait for page to load",
5826
+ confidence: 0.7,
5827
+ retryable: true
5828
+ },
5829
+ {
5830
+ suggestion: "Use a different description for the element",
5831
+ confidence: 0.8,
5832
+ retryable: false
5833
+ },
5834
+ {
5835
+ suggestion: "Scroll the page to reveal the element",
5836
+ command: "scroll down",
5837
+ confidence: 0.6,
5838
+ retryable: true
5839
+ }
5840
+ ];
5841
+ case "ELEMENT_NOT_VISIBLE":
5842
+ return [
5843
+ {
5844
+ suggestion: "Scroll to make the element visible",
5845
+ command: "scroll to element",
5846
+ confidence: 0.9,
5847
+ retryable: true
5848
+ },
5849
+ {
5850
+ suggestion: "Wait for any loading overlays to disappear",
5851
+ confidence: 0.7,
5852
+ retryable: true
5853
+ },
5854
+ {
5855
+ suggestion: "Close any blocking modals or popups",
5856
+ command: "click close button",
5857
+ confidence: 0.8,
5858
+ retryable: true
5859
+ }
5860
+ ];
5861
+ case "ELEMENT_NOT_ENABLED":
5862
+ return [
5863
+ { suggestion: "Fill in required fields first", confidence: 0.8, retryable: false },
5864
+ {
5865
+ suggestion: "Complete prerequisite steps in the form",
5866
+ confidence: 0.7,
5867
+ retryable: false
5868
+ },
5869
+ {
5870
+ suggestion: "Wait for the element to become enabled",
5871
+ command: "wait for element to be enabled",
5872
+ confidence: 0.6,
5873
+ retryable: true
5874
+ }
5875
+ ];
5876
+ case "ELEMENT_NOT_INTERACTABLE":
5877
+ return [
5878
+ {
5879
+ suggestion: "Close any modal or popup blocking the element",
5880
+ command: "click close button",
5881
+ confidence: 0.9,
5882
+ retryable: true
5883
+ },
5884
+ { suggestion: "Wait for animations to complete", confidence: 0.7, retryable: true },
5885
+ {
5886
+ suggestion: "Scroll the element into the viewport",
5887
+ command: "scroll to element",
5888
+ confidence: 0.8,
5889
+ retryable: true
5890
+ }
5891
+ ];
5892
+ case "ACTION_TIMEOUT":
5893
+ return [
5894
+ { suggestion: "Increase the timeout duration", confidence: 0.8, retryable: true },
5895
+ { suggestion: "Check if the condition can ever be met", confidence: 0.7, retryable: false },
5896
+ {
5897
+ suggestion: "Verify the page is responding",
5898
+ command: "check page status",
5899
+ confidence: 0.6,
5900
+ retryable: true
5901
+ }
5902
+ ];
5903
+ case "LOW_CONFIDENCE":
5904
+ return [
5905
+ {
5906
+ suggestion: "Use the exact text shown on the element",
5907
+ confidence: 0.9,
5908
+ retryable: false
5909
+ },
5910
+ {
5911
+ suggestion: "Try a different description that more closely matches the element",
5912
+ confidence: 0.8,
5913
+ retryable: false
5914
+ },
5915
+ {
5916
+ suggestion: "Lower the confidence threshold if the match is correct",
5917
+ confidence: 0.7,
5918
+ retryable: true
5919
+ }
5920
+ ];
5921
+ case "AMBIGUOUS_MATCH":
5922
+ return [
5923
+ {
5924
+ suggestion: "Be more specific about which element you mean",
5925
+ confidence: 0.9,
5926
+ retryable: false
5927
+ },
5928
+ {
5929
+ suggestion: "Include the section or form name in the description",
5930
+ confidence: 0.8,
5931
+ retryable: false
5932
+ },
5933
+ { suggestion: "Use the element ID directly", confidence: 0.7, retryable: false }
5934
+ ];
5935
+ default:
5936
+ return [
5937
+ {
5938
+ suggestion: "Try a different approach or check the page state",
5939
+ confidence: 0.5,
5940
+ retryable: false
5941
+ }
5942
+ ];
5943
+ }
5944
+ }
5945
+ function createFailureDetails(errorCode, message, options = {}) {
5946
+ const retryableErrors = [
5947
+ "ELEMENT_NOT_VISIBLE",
3630
5948
  "ACTION_TIMEOUT",
3631
5949
  "LOW_CONFIDENCE",
3632
5950
  "NETWORK_ERROR",
@@ -3649,6 +5967,9 @@ function createHandlers(registry, actionExecutor, config = {}) {
3649
5967
  const assertionExecutor = new AssertionExecutor();
3650
5968
  const snapshotManager = new SemanticSnapshotManager();
3651
5969
  const diffManager = new SemanticDiffManager();
5970
+ const intentRegistry = /* @__PURE__ */ new Map();
5971
+ const consoleCapture = config.consoleCapture ?? null;
5972
+ const annotationStore = config.annotationStore ?? getGlobalAnnotationStore();
3652
5973
  function refreshElements() {
3653
5974
  const elements = registry.getAllElements();
3654
5975
  searchEngine.updateElements(elements);
@@ -3715,10 +6036,14 @@ function createHandlers(registry, actionExecutor, config = {}) {
3715
6036
  try {
3716
6037
  const element = registry.getElement(id);
3717
6038
  if (!element) {
3718
- const failureDetails = createFailureDetails("ELEMENT_NOT_FOUND", `Element not found: ${id}`, {
3719
- elementId: id,
3720
- selectorsTried: [id]
3721
- });
6039
+ const failureDetails = createFailureDetails(
6040
+ "ELEMENT_NOT_FOUND",
6041
+ `Element not found: ${id}`,
6042
+ {
6043
+ elementId: id,
6044
+ selectorsTried: [id]
6045
+ }
6046
+ );
3722
6047
  return {
3723
6048
  success: false,
3724
6049
  error: `Element not found: ${id}`,
@@ -3748,11 +6073,15 @@ function createHandlers(registry, actionExecutor, config = {}) {
3748
6073
  try {
3749
6074
  const element = registry.getElement(id);
3750
6075
  if (!element) {
3751
- const failureDetails = createFailureDetails("ELEMENT_NOT_FOUND", `Element not found: ${id}`, {
3752
- elementId: id,
3753
- selectorsTried: [id],
3754
- durationMs: Date.now() - startTime
3755
- });
6076
+ const failureDetails = createFailureDetails(
6077
+ "ELEMENT_NOT_FOUND",
6078
+ `Element not found: ${id}`,
6079
+ {
6080
+ elementId: id,
6081
+ selectorsTried: [id],
6082
+ durationMs: Date.now() - startTime
6083
+ }
6084
+ );
3756
6085
  return {
3757
6086
  success: false,
3758
6087
  error: `Element not found: ${id}`,
@@ -3787,10 +6116,14 @@ function createHandlers(registry, actionExecutor, config = {}) {
3787
6116
  } else if (errorMsg.includes("blocked") || errorMsg.includes("interactable")) {
3788
6117
  errorCode = "ELEMENT_NOT_INTERACTABLE";
3789
6118
  }
3790
- const failureDetails = createFailureDetails(errorCode, actionResult.error || "Action failed", {
3791
- elementId: id,
3792
- durationMs: Date.now() - startTime
3793
- });
6119
+ const failureDetails = createFailureDetails(
6120
+ errorCode,
6121
+ actionResult.error || "Action failed",
6122
+ {
6123
+ elementId: id,
6124
+ durationMs: Date.now() - startTime
6125
+ }
6126
+ );
3794
6127
  return success({
3795
6128
  ...actionResult,
3796
6129
  failureDetails
@@ -3889,7 +6222,12 @@ function createHandlers(registry, actionExecutor, config = {}) {
3889
6222
  try {
3890
6223
  const findRequest = request;
3891
6224
  const elements = registry.findElements?.(findRequest) ?? registry.getAllElements();
3892
- return success({ elements, timestamp: Date.now(), total: elements.length, durationMs: 0 });
6225
+ return success({
6226
+ elements,
6227
+ timestamp: Date.now(),
6228
+ total: elements.length,
6229
+ durationMs: 0
6230
+ });
3893
6231
  } catch (err) {
3894
6232
  return error(err.message, "FIND_ERROR");
3895
6233
  }
@@ -3898,7 +6236,12 @@ function createHandlers(registry, actionExecutor, config = {}) {
3898
6236
  try {
3899
6237
  const findRequest = request;
3900
6238
  const elements = registry.findElements?.(findRequest) ?? registry.getAllElements();
3901
- return success({ elements, timestamp: Date.now(), total: elements.length, durationMs: 0 });
6239
+ return success({
6240
+ elements,
6241
+ timestamp: Date.now(),
6242
+ total: elements.length,
6243
+ durationMs: 0
6244
+ });
3902
6245
  } catch (err) {
3903
6246
  return error(err.message, "DISCOVER_ERROR");
3904
6247
  }
@@ -3995,6 +6338,28 @@ function createHandlers(registry, actionExecutor, config = {}) {
3995
6338
  return error(err.message, "ELEMENT_TREE_ERROR");
3996
6339
  }
3997
6340
  },
6341
+ getConsoleErrors: async (params) => {
6342
+ try {
6343
+ if (!consoleCapture) {
6344
+ return success({ errors: [], count: 0 });
6345
+ }
6346
+ const errors = params?.since ? consoleCapture.getConsoleSince(params.since) : consoleCapture.getConsoleRecent(params?.limit ?? 50);
6347
+ return success({ errors, count: errors.length });
6348
+ } catch (err) {
6349
+ return error(err.message, "CONSOLE_ERRORS_ERROR");
6350
+ }
6351
+ },
6352
+ clearConsoleErrors: async () => {
6353
+ try {
6354
+ if (!consoleCapture) {
6355
+ return success({ cleared: false });
6356
+ }
6357
+ consoleCapture.clear();
6358
+ return success({ cleared: true });
6359
+ } catch (err) {
6360
+ return error(err.message, "CONSOLE_CLEAR_ERROR");
6361
+ }
6362
+ },
3998
6363
  // =========================================================================
3999
6364
  // AI-Native Handlers
4000
6365
  // =========================================================================
@@ -4013,106 +6378,219 @@ function createHandlers(registry, actionExecutor, config = {}) {
4013
6378
  const response = await nlExecutor.execute(request);
4014
6379
  return success(response);
4015
6380
  } catch (err) {
4016
- return error(err.message, "AI_EXECUTE_ERROR");
6381
+ return error(err.message, "AI_EXECUTE_ERROR");
6382
+ }
6383
+ },
6384
+ aiAssert: async (request) => {
6385
+ try {
6386
+ refreshElements();
6387
+ const result = await assertionExecutor.assert(request);
6388
+ return success(result);
6389
+ } catch (err) {
6390
+ return error(err.message, "AI_ASSERT_ERROR");
6391
+ }
6392
+ },
6393
+ aiAssertBatch: async (request) => {
6394
+ try {
6395
+ refreshElements();
6396
+ const result = await assertionExecutor.assertBatch(request);
6397
+ return success(result);
6398
+ } catch (err) {
6399
+ return error(err.message, "AI_ASSERT_BATCH_ERROR");
6400
+ }
6401
+ },
6402
+ getSemanticSnapshot: async () => {
6403
+ try {
6404
+ const controlSnapshot = registry.createSnapshot();
6405
+ const snapshot = snapshotManager.createSnapshot(controlSnapshot);
6406
+ return success(snapshot);
6407
+ } catch (err) {
6408
+ return error(err.message, "SEMANTIC_SNAPSHOT_ERROR");
6409
+ }
6410
+ },
6411
+ getSemanticDiff: async (_since) => {
6412
+ try {
6413
+ const controlSnapshot = registry.createSnapshot();
6414
+ const currentSnapshot = snapshotManager.createSnapshot(controlSnapshot);
6415
+ const diff = diffManager.update(currentSnapshot);
6416
+ return success(diff);
6417
+ } catch (err) {
6418
+ return error(err.message, "SEMANTIC_DIFF_ERROR");
6419
+ }
6420
+ },
6421
+ getPageSummary: async () => {
6422
+ try {
6423
+ const snapshot = registry.createSnapshot();
6424
+ const elements = snapshot.elements.map((el) => ({
6425
+ ...el,
6426
+ description: el.label || el.id,
6427
+ aliases: [],
6428
+ suggestedActions: [],
6429
+ tagName: el.type,
6430
+ accessibleName: el.label,
6431
+ registered: true
6432
+ }));
6433
+ const summary = generatePageSummary(elements);
6434
+ return success(summary);
6435
+ } catch (err) {
6436
+ return error(err.message, "PAGE_SUMMARY_ERROR");
6437
+ }
6438
+ },
6439
+ // =========================================================================
6440
+ // Semantic Search Handler (Embedding-based)
6441
+ // =========================================================================
6442
+ // =========================================================================
6443
+ // Page Navigation Handlers
6444
+ // =========================================================================
6445
+ pageRefresh: async () => {
6446
+ try {
6447
+ window.location.reload();
6448
+ return success({ success: true, url: window.location.href, timestamp: Date.now() });
6449
+ } catch (err) {
6450
+ return error(err.message, "PAGE_REFRESH_ERROR");
6451
+ }
6452
+ },
6453
+ pageNavigate: async (request) => {
6454
+ try {
6455
+ if (!request.url) {
6456
+ return error("URL is required", "INVALID_REQUEST");
6457
+ }
6458
+ window.location.href = request.url;
6459
+ return success({ success: true, url: request.url, timestamp: Date.now() });
6460
+ } catch (err) {
6461
+ return error(err.message, "PAGE_NAVIGATE_ERROR");
6462
+ }
6463
+ },
6464
+ pageGoBack: async () => {
6465
+ try {
6466
+ window.history.back();
6467
+ return success({ success: true, url: window.location.href, timestamp: Date.now() });
6468
+ } catch (err) {
6469
+ return error(err.message, "PAGE_GO_BACK_ERROR");
6470
+ }
6471
+ },
6472
+ pageGoForward: async () => {
6473
+ try {
6474
+ window.history.forward();
6475
+ return success({ success: true, url: window.location.href, timestamp: Date.now() });
6476
+ } catch (err) {
6477
+ return error(err.message, "PAGE_GO_FORWARD_ERROR");
6478
+ }
6479
+ },
6480
+ // =========================================================================
6481
+ // Annotation Handlers
6482
+ //
6483
+ // REST API endpoints for managing element annotations:
6484
+ // GET /annotations - List all annotations
6485
+ // GET /annotations/export - Export all annotations as AnnotationConfig
6486
+ // GET /annotations/coverage - Get annotation coverage statistics
6487
+ // GET /annotations/:id - Get annotation for a specific element
6488
+ // PUT /annotations/:id - Create or update an annotation
6489
+ // DELETE /annotations/:id - Delete an annotation
6490
+ // POST /annotations/import - Import annotations from AnnotationConfig
6491
+ // =========================================================================
6492
+ getAnnotations: async () => {
6493
+ try {
6494
+ return success(annotationStore.getAll());
6495
+ } catch (err) {
6496
+ return error(err.message, "ANNOTATIONS_ERROR");
6497
+ }
6498
+ },
6499
+ getAnnotation: async (id) => {
6500
+ try {
6501
+ const annotation = annotationStore.get(id);
6502
+ if (!annotation) {
6503
+ return error(`Annotation not found: ${id}`, "NOT_FOUND");
6504
+ }
6505
+ return success(annotation);
6506
+ } catch (err) {
6507
+ return error(err.message, "ANNOTATION_ERROR");
4017
6508
  }
4018
6509
  },
4019
- aiAssert: async (request) => {
6510
+ setAnnotation: async (id, annotation) => {
4020
6511
  try {
4021
- refreshElements();
4022
- const result = await assertionExecutor.assert(request);
4023
- return success(result);
6512
+ annotationStore.set(id, annotation);
6513
+ return success(annotationStore.get(id));
4024
6514
  } catch (err) {
4025
- return error(err.message, "AI_ASSERT_ERROR");
6515
+ return error(err.message, "ANNOTATION_SET_ERROR");
4026
6516
  }
4027
6517
  },
4028
- aiAssertBatch: async (request) => {
6518
+ deleteAnnotation: async (id) => {
4029
6519
  try {
4030
- refreshElements();
4031
- const result = await assertionExecutor.assertBatch(request);
4032
- return success(result);
6520
+ const existed = annotationStore.delete(id);
6521
+ if (!existed) {
6522
+ return error(`Annotation not found: ${id}`, "NOT_FOUND");
6523
+ }
6524
+ return success(void 0);
4033
6525
  } catch (err) {
4034
- return error(err.message, "AI_ASSERT_BATCH_ERROR");
6526
+ return error(err.message, "ANNOTATION_DELETE_ERROR");
4035
6527
  }
4036
6528
  },
4037
- getSemanticSnapshot: async () => {
6529
+ importAnnotations: async (config2) => {
4038
6530
  try {
4039
- const controlSnapshot = registry.createSnapshot();
4040
- const snapshot = snapshotManager.createSnapshot(controlSnapshot);
4041
- return success(snapshot);
6531
+ const count = annotationStore.importConfig(config2);
6532
+ return success({ count });
4042
6533
  } catch (err) {
4043
- return error(err.message, "SEMANTIC_SNAPSHOT_ERROR");
6534
+ return error(err.message, "ANNOTATION_IMPORT_ERROR");
4044
6535
  }
4045
6536
  },
4046
- getSemanticDiff: async (_since) => {
6537
+ exportAnnotations: async () => {
4047
6538
  try {
4048
- const controlSnapshot = registry.createSnapshot();
4049
- const currentSnapshot = snapshotManager.createSnapshot(controlSnapshot);
4050
- const diff = diffManager.update(currentSnapshot);
4051
- return success(diff);
6539
+ return success(annotationStore.exportConfig());
4052
6540
  } catch (err) {
4053
- return error(err.message, "SEMANTIC_DIFF_ERROR");
6541
+ return error(err.message, "ANNOTATION_EXPORT_ERROR");
4054
6542
  }
4055
6543
  },
4056
- getPageSummary: async () => {
6544
+ getAnnotationCoverage: async () => {
4057
6545
  try {
4058
- const snapshot = registry.createSnapshot();
4059
- const elements = snapshot.elements.map((el) => ({
4060
- ...el,
4061
- description: el.label || el.id,
4062
- aliases: [],
4063
- suggestedActions: [],
4064
- tagName: el.type,
4065
- accessibleName: el.label,
4066
- registered: true
4067
- }));
4068
- const summary = generatePageSummary(elements);
4069
- return success(summary);
6546
+ const allElements = registry.getAllElements();
6547
+ const allIds = allElements.map((el) => el.id);
6548
+ return success(annotationStore.getCoverage(allIds));
4070
6549
  } catch (err) {
4071
- return error(err.message, "PAGE_SUMMARY_ERROR");
6550
+ return error(err.message, "ANNOTATION_COVERAGE_ERROR");
4072
6551
  }
4073
6552
  },
4074
- // =========================================================================
4075
- // Semantic Search Handler (Embedding-based)
4076
- // =========================================================================
4077
6553
  aiSemanticSearch: async (criteria) => {
4078
6554
  const startTime = performance.now();
4079
6555
  try {
4080
6556
  refreshElements();
4081
6557
  const allElements = registry.getAllElements();
4082
- const aiElements = allElements.map((el) => {
4083
- const textParts = [];
4084
- const state = "getState" in el ? el.getState() : el.state;
4085
- const textContent = state?.textContent || "";
4086
- const label = el.label || "";
4087
- const accessibleName = el.accessibleName || "";
4088
- const placeholder = el.placeholder || "";
4089
- const title = el.title || "";
4090
- if (label) textParts.push(label);
4091
- if (accessibleName && accessibleName !== label) textParts.push(accessibleName);
4092
- if (textContent && textContent !== label && textContent !== accessibleName) {
4093
- textParts.push(textContent);
6558
+ const aiElements = allElements.map(
6559
+ (el) => {
6560
+ const textParts = [];
6561
+ const state = "getState" in el ? el.getState() : el.state;
6562
+ const textContent = state?.textContent || "";
6563
+ const label = el.label || "";
6564
+ const accessibleName = el.accessibleName || "";
6565
+ const placeholder = el.placeholder || "";
6566
+ const title = el.title || "";
6567
+ if (label) textParts.push(label);
6568
+ if (accessibleName && accessibleName !== label) textParts.push(accessibleName);
6569
+ if (textContent && textContent !== label && textContent !== accessibleName) {
6570
+ textParts.push(textContent);
6571
+ }
6572
+ if (placeholder) textParts.push(`placeholder: ${placeholder}`);
6573
+ if (title) textParts.push(title);
6574
+ const combinedText = textParts.join(" ").trim() || el.id;
6575
+ return {
6576
+ element: {
6577
+ id: el.id,
6578
+ type: el.type,
6579
+ label: el.label,
6580
+ tagName: el.tagName || el.type,
6581
+ role: el.role,
6582
+ accessibleName: el.accessibleName,
6583
+ actions: el.actions || [],
6584
+ state: state || {},
6585
+ registered: true,
6586
+ description: label || el.id,
6587
+ aliases: [],
6588
+ suggestedActions: []
6589
+ },
6590
+ text: combinedText
6591
+ };
4094
6592
  }
4095
- if (placeholder) textParts.push(`placeholder: ${placeholder}`);
4096
- if (title) textParts.push(title);
4097
- const combinedText = textParts.join(" ").trim() || el.id;
4098
- return {
4099
- element: {
4100
- id: el.id,
4101
- type: el.type,
4102
- label: el.label,
4103
- tagName: el.tagName || el.type,
4104
- role: el.role,
4105
- accessibleName: el.accessibleName,
4106
- actions: el.actions || [],
4107
- state: state || {},
4108
- registered: true,
4109
- description: label || el.id,
4110
- aliases: [],
4111
- suggestedActions: []
4112
- },
4113
- text: combinedText
4114
- };
4115
- });
6593
+ );
4116
6594
  let filteredElements = aiElements;
4117
6595
  if (criteria.type) {
4118
6596
  filteredElements = filteredElements.filter(
@@ -4177,6 +6655,398 @@ function createHandlers(registry, actionExecutor, config = {}) {
4177
6655
  } catch (err) {
4178
6656
  return error(err.message, "AI_SEMANTIC_SEARCH_ERROR");
4179
6657
  }
6658
+ },
6659
+ // =========================================================================
6660
+ // State Management Handlers
6661
+ // =========================================================================
6662
+ getStates: async () => {
6663
+ try {
6664
+ const states = registry.getStates?.() ?? [];
6665
+ return success(states);
6666
+ } catch (err) {
6667
+ return error(err.message, "STATES_ERROR");
6668
+ }
6669
+ },
6670
+ getState: async (id) => {
6671
+ try {
6672
+ const state = registry.getState?.(id);
6673
+ if (!state) {
6674
+ return error(`State not found: ${id}`, "NOT_FOUND");
6675
+ }
6676
+ return success(state);
6677
+ } catch (err) {
6678
+ return error(err.message, "STATE_ERROR");
6679
+ }
6680
+ },
6681
+ getActiveStates: async () => {
6682
+ try {
6683
+ const states = registry.getActiveStates?.() ?? [];
6684
+ return success(states);
6685
+ } catch (err) {
6686
+ return error(err.message, "ACTIVE_STATES_ERROR");
6687
+ }
6688
+ },
6689
+ activateState: async (id) => {
6690
+ try {
6691
+ if (!registry.activateState) {
6692
+ return error("State management not available", "NOT_IMPLEMENTED");
6693
+ }
6694
+ registry.activateState(id);
6695
+ return success(void 0);
6696
+ } catch (err) {
6697
+ return error(err.message, "ACTIVATE_STATE_ERROR");
6698
+ }
6699
+ },
6700
+ deactivateState: async (id) => {
6701
+ try {
6702
+ if (!registry.deactivateState) {
6703
+ return error("State management not available", "NOT_IMPLEMENTED");
6704
+ }
6705
+ registry.deactivateState(id);
6706
+ return success(void 0);
6707
+ } catch (err) {
6708
+ return error(err.message, "DEACTIVATE_STATE_ERROR");
6709
+ }
6710
+ },
6711
+ getStateGroups: async () => {
6712
+ try {
6713
+ const groups = registry.getStateGroups?.() ?? [];
6714
+ return success(groups);
6715
+ } catch (err) {
6716
+ return error(err.message, "STATE_GROUPS_ERROR");
6717
+ }
6718
+ },
6719
+ activateStateGroup: async (id) => {
6720
+ try {
6721
+ if (!registry.activateStateGroup) {
6722
+ return error("State group management not available", "NOT_IMPLEMENTED");
6723
+ }
6724
+ registry.activateStateGroup(id);
6725
+ return success(void 0);
6726
+ } catch (err) {
6727
+ return error(err.message, "ACTIVATE_STATE_GROUP_ERROR");
6728
+ }
6729
+ },
6730
+ deactivateStateGroup: async (id) => {
6731
+ try {
6732
+ if (!registry.deactivateStateGroup) {
6733
+ return error("State group management not available", "NOT_IMPLEMENTED");
6734
+ }
6735
+ registry.deactivateStateGroup(id);
6736
+ return success(void 0);
6737
+ } catch (err) {
6738
+ return error(err.message, "DEACTIVATE_STATE_GROUP_ERROR");
6739
+ }
6740
+ },
6741
+ getTransitions: async () => {
6742
+ try {
6743
+ const transitions = registry.getTransitions?.() ?? [];
6744
+ return success(transitions);
6745
+ } catch (err) {
6746
+ return error(err.message, "TRANSITIONS_ERROR");
6747
+ }
6748
+ },
6749
+ canExecuteTransition: async (id) => {
6750
+ try {
6751
+ if (!registry.canExecuteTransition) {
6752
+ return error("Transition management not available", "NOT_IMPLEMENTED");
6753
+ }
6754
+ const result = registry.canExecuteTransition(id);
6755
+ return success(result);
6756
+ } catch (err) {
6757
+ return error(err.message, "CAN_EXECUTE_TRANSITION_ERROR");
6758
+ }
6759
+ },
6760
+ executeTransition: async (id) => {
6761
+ try {
6762
+ if (!registry.executeTransition) {
6763
+ return error("Transition execution not available", "NOT_IMPLEMENTED");
6764
+ }
6765
+ const result = await registry.executeTransition(id);
6766
+ return success(result);
6767
+ } catch (err) {
6768
+ return error(err.message, "EXECUTE_TRANSITION_ERROR");
6769
+ }
6770
+ },
6771
+ findPath: async (request) => {
6772
+ try {
6773
+ if (!registry.findPath) {
6774
+ return error("Pathfinding not available", "NOT_IMPLEMENTED");
6775
+ }
6776
+ const result = registry.findPath(request.targetStates);
6777
+ return success(result);
6778
+ } catch (err) {
6779
+ return error(err.message, "FIND_PATH_ERROR");
6780
+ }
6781
+ },
6782
+ navigateTo: async (request) => {
6783
+ try {
6784
+ if (!registry.navigateTo) {
6785
+ return error("Navigation not available", "NOT_IMPLEMENTED");
6786
+ }
6787
+ const result = await registry.navigateTo(request.targetStates);
6788
+ return success(result);
6789
+ } catch (err) {
6790
+ return error(err.message, "NAVIGATE_TO_ERROR");
6791
+ }
6792
+ },
6793
+ getStateSnapshot: async () => {
6794
+ try {
6795
+ if (!registry.getStateSnapshot) {
6796
+ const snapshot = {
6797
+ timestamp: Date.now(),
6798
+ activeStates: (registry.getActiveStates?.() ?? []).map((s) => s.id),
6799
+ states: registry.getStates?.() ?? [],
6800
+ groups: registry.getStateGroups?.() ?? [],
6801
+ transitions: registry.getTransitions?.() ?? []
6802
+ };
6803
+ return success(snapshot);
6804
+ }
6805
+ return success(registry.getStateSnapshot());
6806
+ } catch (err) {
6807
+ return error(err.message, "STATE_SNAPSHOT_ERROR");
6808
+ }
6809
+ },
6810
+ // =========================================================================
6811
+ // Intent Handlers
6812
+ // =========================================================================
6813
+ executeIntent: async (request) => {
6814
+ const startTime = Date.now();
6815
+ try {
6816
+ refreshElements();
6817
+ const intent = intentRegistry.get(request.intentId);
6818
+ if (!intent) {
6819
+ return error(`Intent not found: ${request.intentId}`, "NOT_FOUND");
6820
+ }
6821
+ const nlResponse = await nlExecutor.execute({
6822
+ instruction: intent.description,
6823
+ context: `Executing intent: ${intent.name}`
6824
+ });
6825
+ return success({
6826
+ success: nlResponse.success,
6827
+ intentId: request.intentId,
6828
+ result: nlResponse,
6829
+ error: nlResponse.error,
6830
+ durationMs: Date.now() - startTime
6831
+ });
6832
+ } catch (err) {
6833
+ return error(err.message, "EXECUTE_INTENT_ERROR");
6834
+ }
6835
+ },
6836
+ findIntent: async (request) => {
6837
+ try {
6838
+ const query = request.query.toLowerCase();
6839
+ const results = [];
6840
+ for (const intent of intentRegistry.values()) {
6841
+ let confidence = 0;
6842
+ const nameLower = intent.name.toLowerCase();
6843
+ const descLower = intent.description.toLowerCase();
6844
+ if (nameLower === query) {
6845
+ confidence = 1;
6846
+ } else if (nameLower.includes(query) || query.includes(nameLower)) {
6847
+ confidence = 0.8;
6848
+ } else if (descLower.includes(query)) {
6849
+ confidence = 0.6;
6850
+ } else if (intent.tags?.some((t) => t.toLowerCase().includes(query))) {
6851
+ confidence = 0.5;
6852
+ }
6853
+ if (confidence > 0) {
6854
+ results.push({ intent, confidence });
6855
+ }
6856
+ }
6857
+ results.sort((a, b) => b.confidence - a.confidence);
6858
+ return success({ intents: results });
6859
+ } catch (err) {
6860
+ return error(err.message, "FIND_INTENT_ERROR");
6861
+ }
6862
+ },
6863
+ listIntents: async () => {
6864
+ try {
6865
+ return success(Array.from(intentRegistry.values()));
6866
+ } catch (err) {
6867
+ return error(err.message, "LIST_INTENTS_ERROR");
6868
+ }
6869
+ },
6870
+ registerIntent: async (intent) => {
6871
+ try {
6872
+ intentRegistry.set(intent.id, intent);
6873
+ return success(intent);
6874
+ } catch (err) {
6875
+ return error(err.message, "REGISTER_INTENT_ERROR");
6876
+ }
6877
+ },
6878
+ executeIntentFromQuery: async (request) => {
6879
+ const startTime = Date.now();
6880
+ try {
6881
+ refreshElements();
6882
+ const query = request.query.toLowerCase();
6883
+ let bestIntent = null;
6884
+ let bestConfidence = 0;
6885
+ for (const intent of intentRegistry.values()) {
6886
+ let confidence = 0;
6887
+ const nameLower = intent.name.toLowerCase();
6888
+ const descLower = intent.description.toLowerCase();
6889
+ if (nameLower === query) {
6890
+ confidence = 1;
6891
+ } else if (nameLower.includes(query) || query.includes(nameLower)) {
6892
+ confidence = 0.8;
6893
+ } else if (descLower.includes(query)) {
6894
+ confidence = 0.6;
6895
+ }
6896
+ if (confidence > bestConfidence) {
6897
+ bestConfidence = confidence;
6898
+ bestIntent = intent;
6899
+ }
6900
+ }
6901
+ if (!bestIntent) {
6902
+ return success({
6903
+ success: false,
6904
+ intentId: "",
6905
+ error: `No intent found matching query: ${request.query}`,
6906
+ durationMs: Date.now() - startTime
6907
+ });
6908
+ }
6909
+ const nlResponse = await nlExecutor.execute({
6910
+ instruction: bestIntent.description,
6911
+ context: `Executing intent from query: ${request.query}`
6912
+ });
6913
+ return success({
6914
+ success: nlResponse.success,
6915
+ intentId: bestIntent.id,
6916
+ result: nlResponse,
6917
+ error: nlResponse.error,
6918
+ durationMs: Date.now() - startTime
6919
+ });
6920
+ } catch (err) {
6921
+ return error(err.message, "EXECUTE_INTENT_FROM_QUERY_ERROR");
6922
+ }
6923
+ },
6924
+ // =========================================================================
6925
+ // Recovery Handler
6926
+ // =========================================================================
6927
+ attemptRecovery: async (request) => {
6928
+ const startTime = Date.now();
6929
+ try {
6930
+ refreshElements();
6931
+ const strategiesAttempted = [];
6932
+ let lastResult;
6933
+ const suggestions = request.failure.suggestedActions ?? [];
6934
+ for (let i = 0; i < Math.min(suggestions.length, request.maxRetries); i++) {
6935
+ const suggestion = suggestions[i];
6936
+ strategiesAttempted.push(suggestion.suggestion || `strategy-${i}`);
6937
+ const instruction = suggestion.command || request.instruction;
6938
+ try {
6939
+ const result = await nlExecutor.execute({
6940
+ instruction,
6941
+ context: `Recovery attempt ${i + 1}: ${suggestion.suggestion}`
6942
+ });
6943
+ lastResult = result;
6944
+ if (result.success) {
6945
+ return success({
6946
+ recovered: true,
6947
+ strategiesAttempted,
6948
+ finalResult: result,
6949
+ durationMs: Date.now() - startTime
6950
+ });
6951
+ }
6952
+ } catch {
6953
+ }
6954
+ }
6955
+ if (strategiesAttempted.length === 0 || !lastResult?.success) {
6956
+ strategiesAttempted.push("direct-instruction");
6957
+ try {
6958
+ const result = await nlExecutor.execute({
6959
+ instruction: request.instruction,
6960
+ context: "Recovery: direct instruction attempt"
6961
+ });
6962
+ lastResult = result;
6963
+ if (result.success) {
6964
+ return success({
6965
+ recovered: true,
6966
+ strategiesAttempted,
6967
+ finalResult: result,
6968
+ durationMs: Date.now() - startTime
6969
+ });
6970
+ }
6971
+ } catch {
6972
+ }
6973
+ }
6974
+ return success({
6975
+ recovered: false,
6976
+ strategiesAttempted,
6977
+ finalResult: lastResult,
6978
+ error: "All recovery strategies exhausted",
6979
+ durationMs: Date.now() - startTime
6980
+ });
6981
+ } catch (err) {
6982
+ return error(err.message, "RECOVERY_ERROR");
6983
+ }
6984
+ },
6985
+ // =========================================================================
6986
+ // Cross-App Analysis Handlers
6987
+ // =========================================================================
6988
+ analyzePageData: async () => {
6989
+ try {
6990
+ const controlSnapshot = registry.createSnapshot();
6991
+ const snapshot = snapshotManager.createSnapshot(controlSnapshot);
6992
+ const result = extractPageData(snapshot.elements);
6993
+ return success(result);
6994
+ } catch (err) {
6995
+ return error(err.message, "ANALYZE_DATA_ERROR");
6996
+ }
6997
+ },
6998
+ analyzePageRegions: async () => {
6999
+ try {
7000
+ const controlSnapshot = registry.createSnapshot();
7001
+ const snapshot = snapshotManager.createSnapshot(controlSnapshot);
7002
+ const result = segmentPageRegions(snapshot.elements);
7003
+ return success(result);
7004
+ } catch (err) {
7005
+ return error(err.message, "ANALYZE_REGIONS_ERROR");
7006
+ }
7007
+ },
7008
+ analyzeStructuredData: async () => {
7009
+ try {
7010
+ const controlSnapshot = registry.createSnapshot();
7011
+ const snapshot = snapshotManager.createSnapshot(controlSnapshot);
7012
+ const result = extractStructuredData(snapshot.elements);
7013
+ return success(result);
7014
+ } catch (err) {
7015
+ return error(err.message, "ANALYZE_STRUCTURED_DATA_ERROR");
7016
+ }
7017
+ },
7018
+ crossAppCompare: async (request) => {
7019
+ try {
7020
+ const hasComponents = request.sourceComponents && request.targetComponents;
7021
+ const report = generateComparisonReport(
7022
+ request.sourceSnapshot,
7023
+ request.targetSnapshot,
7024
+ hasComponents ? {
7025
+ config: { includeComponents: true },
7026
+ sourceComponents: request.sourceComponents,
7027
+ targetComponents: request.targetComponents
7028
+ } : void 0
7029
+ );
7030
+ return success(report);
7031
+ } catch (err) {
7032
+ return error(err.message, "CROSS_APP_COMPARE_ERROR");
7033
+ }
7034
+ },
7035
+ // =========================================================================
7036
+ // Performance Diagnostics Handlers
7037
+ // =========================================================================
7038
+ getPerformanceEntries: async () => {
7039
+ return {
7040
+ success: true,
7041
+ data: { navigation: null, resources: [], paint: [] },
7042
+ timestamp: Date.now()
7043
+ };
7044
+ },
7045
+ clearPerformanceEntries: async () => {
7046
+ return { success: true, data: { cleared: true }, timestamp: Date.now() };
7047
+ },
7048
+ getBrowserEvents: async (_params) => {
7049
+ return { success: true, data: { events: [], count: 0 }, timestamp: Date.now() };
4180
7050
  }
4181
7051
  };
4182
7052
  }
@@ -4200,7 +7070,12 @@ function createAIHandlers(registry, actionExecutor) {
4200
7070
  const response = searchEngine.search(criteria);
4201
7071
  return { success: true, data: response, timestamp: Date.now() };
4202
7072
  } catch (err) {
4203
- return { success: false, error: err.message, code: "AI_SEARCH_ERROR", timestamp: Date.now() };
7073
+ return {
7074
+ success: false,
7075
+ error: err.message,
7076
+ code: "AI_SEARCH_ERROR",
7077
+ timestamp: Date.now()
7078
+ };
4204
7079
  }
4205
7080
  },
4206
7081
  aiExecute: async (request) => {
@@ -4209,7 +7084,12 @@ function createAIHandlers(registry, actionExecutor) {
4209
7084
  const response = await nlExecutor.execute(request);
4210
7085
  return { success: true, data: response, timestamp: Date.now() };
4211
7086
  } catch (err) {
4212
- return { success: false, error: err.message, code: "AI_EXECUTE_ERROR", timestamp: Date.now() };
7087
+ return {
7088
+ success: false,
7089
+ error: err.message,
7090
+ code: "AI_EXECUTE_ERROR",
7091
+ timestamp: Date.now()
7092
+ };
4213
7093
  }
4214
7094
  },
4215
7095
  aiAssert: async (request) => {
@@ -4218,7 +7098,12 @@ function createAIHandlers(registry, actionExecutor) {
4218
7098
  const result = await assertionExecutor.assert(request);
4219
7099
  return { success: true, data: result, timestamp: Date.now() };
4220
7100
  } catch (err) {
4221
- return { success: false, error: err.message, code: "AI_ASSERT_ERROR", timestamp: Date.now() };
7101
+ return {
7102
+ success: false,
7103
+ error: err.message,
7104
+ code: "AI_ASSERT_ERROR",
7105
+ timestamp: Date.now()
7106
+ };
4222
7107
  }
4223
7108
  },
4224
7109
  aiAssertBatch: async (request) => {
@@ -4227,7 +7112,12 @@ function createAIHandlers(registry, actionExecutor) {
4227
7112
  const result = await assertionExecutor.assertBatch(request);
4228
7113
  return { success: true, data: result, timestamp: Date.now() };
4229
7114
  } catch (err) {
4230
- return { success: false, error: err.message, code: "AI_ASSERT_BATCH_ERROR", timestamp: Date.now() };
7115
+ return {
7116
+ success: false,
7117
+ error: err.message,
7118
+ code: "AI_ASSERT_BATCH_ERROR",
7119
+ timestamp: Date.now()
7120
+ };
4231
7121
  }
4232
7122
  },
4233
7123
  getSemanticSnapshot: async () => {
@@ -4236,7 +7126,12 @@ function createAIHandlers(registry, actionExecutor) {
4236
7126
  const snapshot = snapshotManager.createSnapshot(controlSnapshot);
4237
7127
  return { success: true, data: snapshot, timestamp: Date.now() };
4238
7128
  } catch (err) {
4239
- return { success: false, error: err.message, code: "SEMANTIC_SNAPSHOT_ERROR", timestamp: Date.now() };
7129
+ return {
7130
+ success: false,
7131
+ error: err.message,
7132
+ code: "SEMANTIC_SNAPSHOT_ERROR",
7133
+ timestamp: Date.now()
7134
+ };
4240
7135
  }
4241
7136
  },
4242
7137
  getSemanticDiff: async (_since) => {
@@ -4246,7 +7141,12 @@ function createAIHandlers(registry, actionExecutor) {
4246
7141
  const diff = diffManager.update(currentSnapshot);
4247
7142
  return { success: true, data: diff, timestamp: Date.now() };
4248
7143
  } catch (err) {
4249
- return { success: false, error: err.message, code: "SEMANTIC_DIFF_ERROR", timestamp: Date.now() };
7144
+ return {
7145
+ success: false,
7146
+ error: err.message,
7147
+ code: "SEMANTIC_DIFF_ERROR",
7148
+ timestamp: Date.now()
7149
+ };
4250
7150
  }
4251
7151
  },
4252
7152
  getPageSummary: async () => {
@@ -4264,7 +7164,12 @@ function createAIHandlers(registry, actionExecutor) {
4264
7164
  const summary = generatePageSummary(elements);
4265
7165
  return { success: true, data: summary, timestamp: Date.now() };
4266
7166
  } catch (err) {
4267
- return { success: false, error: err.message, code: "PAGE_SUMMARY_ERROR", timestamp: Date.now() };
7167
+ return {
7168
+ success: false,
7169
+ error: err.message,
7170
+ code: "PAGE_SUMMARY_ERROR",
7171
+ timestamp: Date.now()
7172
+ };
4268
7173
  }
4269
7174
  }
4270
7175
  };