@qontinui/ui-bridge 0.1.1 → 0.3.0

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 (112) hide show
  1. package/dist/ai/index.d.mts +893 -0
  2. package/dist/ai/index.d.ts +893 -0
  3. package/dist/ai/index.js +3897 -0
  4. package/dist/ai/index.js.map +1 -0
  5. package/dist/ai/index.mjs +3839 -0
  6. package/dist/ai/index.mjs.map +1 -0
  7. package/dist/babel-plugin/index.js +515 -0
  8. package/dist/babel-plugin/index.js.map +1 -0
  9. package/dist/babel-plugin/index.mjs +499 -0
  10. package/dist/babel-plugin/index.mjs.map +1 -0
  11. package/dist/control/index.d.mts +5 -4
  12. package/dist/control/index.d.ts +5 -4
  13. package/dist/core/index.d.mts +115 -42
  14. package/dist/core/index.d.ts +115 -42
  15. package/dist/core/index.js +0 -983
  16. package/dist/core/index.js.map +1 -1
  17. package/dist/core/index.mjs +1 -972
  18. package/dist/core/index.mjs.map +1 -1
  19. package/dist/debug/index.d.mts +3 -3
  20. package/dist/debug/index.d.ts +3 -3
  21. package/dist/index.d.mts +8 -7
  22. package/dist/index.d.ts +8 -7
  23. package/dist/index.js +8249 -4163
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mjs +8193 -4152
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/{metrics-QCnK0EFw.d.ts → metrics-BfiT_rhZ.d.ts} +2 -2
  28. package/dist/{metrics-BCG7z7Aq.d.mts → metrics-DTA2bwG7.d.mts} +2 -2
  29. package/dist/native/control/index.js +453 -0
  30. package/dist/native/control/index.js.map +1 -0
  31. package/dist/native/control/index.mjs +450 -0
  32. package/dist/native/control/index.mjs.map +1 -0
  33. package/dist/native/core/index.js +486 -0
  34. package/dist/native/core/index.js.map +1 -0
  35. package/dist/native/core/index.mjs +475 -0
  36. package/dist/native/core/index.mjs.map +1 -0
  37. package/dist/native/debug/index.js +451 -0
  38. package/dist/native/debug/index.js.map +1 -0
  39. package/dist/native/debug/index.mjs +449 -0
  40. package/dist/native/debug/index.mjs.map +1 -0
  41. package/dist/native/index.js +2274 -0
  42. package/dist/native/index.js.map +1 -0
  43. package/dist/native/index.mjs +2246 -0
  44. package/dist/native/index.mjs.map +1 -0
  45. package/dist/native/react/index.js +1401 -0
  46. package/dist/native/react/index.js.map +1 -0
  47. package/dist/native/react/index.mjs +1389 -0
  48. package/dist/native/react/index.mjs.map +1 -0
  49. package/dist/native/server/index.js +415 -0
  50. package/dist/native/server/index.js.map +1 -0
  51. package/dist/native/server/index.mjs +410 -0
  52. package/dist/native/server/index.mjs.map +1 -0
  53. package/dist/react/index.d.mts +20 -6
  54. package/dist/react/index.d.ts +20 -6
  55. package/dist/react/index.js +629 -14
  56. package/dist/react/index.js.map +1 -1
  57. package/dist/react/index.mjs +629 -14
  58. package/dist/react/index.mjs.map +1 -1
  59. package/dist/{registry-CT6BVVKr.d.mts → registry-BKLEm-yk.d.ts} +29 -14
  60. package/dist/{registry-D4mQ01B3.d.ts → registry-BmZgyCz8.d.mts} +29 -14
  61. package/dist/render-log/index.d.mts +1 -1
  62. package/dist/render-log/index.d.ts +1 -1
  63. package/dist/server/express.d.mts +36 -0
  64. package/dist/server/express.d.ts +36 -0
  65. package/dist/server/express.js +196 -0
  66. package/dist/server/express.js.map +1 -0
  67. package/dist/server/express.mjs +192 -0
  68. package/dist/server/express.mjs.map +1 -0
  69. package/dist/server/handlers.d.mts +93 -0
  70. package/dist/server/handlers.d.ts +93 -0
  71. package/dist/server/handlers.js +4278 -0
  72. package/dist/server/handlers.js.map +1 -0
  73. package/dist/server/handlers.mjs +4275 -0
  74. package/dist/server/handlers.mjs.map +1 -0
  75. package/dist/server/index.d.mts +10 -0
  76. package/dist/server/index.d.ts +10 -0
  77. package/dist/server/index.js +5352 -0
  78. package/dist/server/index.js.map +1 -0
  79. package/dist/server/index.mjs +5337 -0
  80. package/dist/server/index.mjs.map +1 -0
  81. package/dist/server/nextjs.d.mts +126 -0
  82. package/dist/server/nextjs.d.ts +126 -0
  83. package/dist/server/nextjs.js +287 -0
  84. package/dist/server/nextjs.js.map +1 -0
  85. package/dist/server/nextjs.mjs +282 -0
  86. package/dist/server/nextjs.mjs.map +1 -0
  87. package/dist/server/standalone.d.mts +6 -0
  88. package/dist/server/standalone.d.ts +6 -0
  89. package/dist/server/standalone.js +719 -0
  90. package/dist/server/standalone.js.map +1 -0
  91. package/dist/server/standalone.mjs +715 -0
  92. package/dist/server/standalone.mjs.map +1 -0
  93. package/dist/standalone-BURj8J3G.d.ts +212 -0
  94. package/dist/standalone-Dwmel29d.d.mts +212 -0
  95. package/dist/swc-plugin/index.d.mts +79 -0
  96. package/dist/swc-plugin/index.d.ts +79 -0
  97. package/dist/swc-plugin/index.js +15 -0
  98. package/dist/swc-plugin/index.js.map +1 -0
  99. package/dist/swc-plugin/index.mjs +9 -0
  100. package/dist/swc-plugin/index.mjs.map +1 -0
  101. package/dist/types-B5Q0GVo0.d.mts +646 -0
  102. package/dist/{types-DdJD9yw5.d.mts → types-B7J7noLK.d.mts} +1 -1
  103. package/dist/{types-BDkXy5si.d.ts → types-BkNRILUa.d.ts} +1 -1
  104. package/dist/types-CEQLnFMv.d.mts +156 -0
  105. package/dist/types-CHnlwiTK.d.ts +156 -0
  106. package/dist/types-DfPqwU-i.d.ts +646 -0
  107. package/dist/{types-BpvpStn3.d.mts → types-jKVgTI6_.d.mts} +364 -160
  108. package/dist/{types-BpvpStn3.d.ts → types-jKVgTI6_.d.ts} +364 -160
  109. package/package.json +111 -3
  110. package/swc-plugin-wasm/ui_bridge_swc_plugin.wasm +0 -0
  111. package/dist/websocket-client-B2LC9CYc.d.mts +0 -124
  112. package/dist/websocket-client-DupH0X7B.d.ts +0 -124
@@ -0,0 +1,4278 @@
1
+ 'use strict';
2
+
3
+ // src/ai/fuzzy-matcher.ts
4
+ var DEFAULT_FUZZY_CONFIG = {
5
+ threshold: 0.7,
6
+ levenshteinWeight: 0.3,
7
+ jaroWinklerWeight: 0.4,
8
+ ngramWeight: 0.3,
9
+ ngramSize: 2,
10
+ caseSensitive: false,
11
+ ignoreWhitespace: true
12
+ };
13
+ function levenshteinDistance(s1, s2) {
14
+ const len1 = s1.length;
15
+ const len2 = s2.length;
16
+ const matrix = Array(len1 + 1).fill(null).map(() => Array(len2 + 1).fill(0));
17
+ for (let i = 0; i <= len1; i++) matrix[i][0] = i;
18
+ for (let j = 0; j <= len2; j++) matrix[0][j] = j;
19
+ for (let i = 1; i <= len1; i++) {
20
+ for (let j = 1; j <= len2; j++) {
21
+ const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
22
+ matrix[i][j] = Math.min(
23
+ matrix[i - 1][j] + 1,
24
+ // deletion
25
+ matrix[i][j - 1] + 1,
26
+ // insertion
27
+ matrix[i - 1][j - 1] + cost
28
+ // substitution
29
+ );
30
+ }
31
+ }
32
+ return matrix[len1][len2];
33
+ }
34
+ function levenshteinSimilarity(s1, s2) {
35
+ if (s1.length === 0 && s2.length === 0) return 1;
36
+ if (s1.length === 0 || s2.length === 0) return 0;
37
+ const distance = levenshteinDistance(s1, s2);
38
+ const maxLength = Math.max(s1.length, s2.length);
39
+ return 1 - distance / maxLength;
40
+ }
41
+ function jaroSimilarity(s1, s2) {
42
+ if (s1.length === 0 && s2.length === 0) return 1;
43
+ if (s1.length === 0 || s2.length === 0) return 0;
44
+ const matchDistance = Math.floor(Math.max(s1.length, s2.length) / 2) - 1;
45
+ const s1Matches = new Array(s1.length).fill(false);
46
+ const s2Matches = new Array(s2.length).fill(false);
47
+ let matches = 0;
48
+ let transpositions = 0;
49
+ for (let i = 0; i < s1.length; i++) {
50
+ const start = Math.max(0, i - matchDistance);
51
+ const end = Math.min(i + matchDistance + 1, s2.length);
52
+ for (let j = start; j < end; j++) {
53
+ if (s2Matches[j] || s1[i] !== s2[j]) continue;
54
+ s1Matches[i] = true;
55
+ s2Matches[j] = true;
56
+ matches++;
57
+ break;
58
+ }
59
+ }
60
+ if (matches === 0) return 0;
61
+ let k = 0;
62
+ for (let i = 0; i < s1.length; i++) {
63
+ if (!s1Matches[i]) continue;
64
+ while (!s2Matches[k]) k++;
65
+ if (s1[i] !== s2[k]) transpositions++;
66
+ k++;
67
+ }
68
+ return (matches / s1.length + matches / s2.length + (matches - transpositions / 2) / matches) / 3;
69
+ }
70
+ function jaroWinklerSimilarity(s1, s2, prefixScale = 0.1) {
71
+ const jaroSim = jaroSimilarity(s1, s2);
72
+ let prefixLength = 0;
73
+ const maxPrefix = Math.min(4, Math.min(s1.length, s2.length));
74
+ for (let i = 0; i < maxPrefix; i++) {
75
+ if (s1[i] === s2[i]) {
76
+ prefixLength++;
77
+ } else {
78
+ break;
79
+ }
80
+ }
81
+ return jaroSim + prefixLength * prefixScale * (1 - jaroSim);
82
+ }
83
+ function generateNgrams(s, n) {
84
+ const ngrams = /* @__PURE__ */ new Set();
85
+ if (s.length < n) {
86
+ ngrams.add(s);
87
+ return ngrams;
88
+ }
89
+ for (let i = 0; i <= s.length - n; i++) {
90
+ ngrams.add(s.substring(i, i + n));
91
+ }
92
+ return ngrams;
93
+ }
94
+ function ngramSimilarity(s1, s2, n = 2) {
95
+ if (s1.length === 0 && s2.length === 0) return 1;
96
+ if (s1.length === 0 || s2.length === 0) return 0;
97
+ const ngrams1 = generateNgrams(s1, n);
98
+ const ngrams2 = generateNgrams(s2, n);
99
+ let intersection = 0;
100
+ for (const ngram of ngrams1) {
101
+ if (ngrams2.has(ngram)) {
102
+ intersection++;
103
+ }
104
+ }
105
+ const union = ngrams1.size + ngrams2.size - intersection;
106
+ return union === 0 ? 0 : intersection / union;
107
+ }
108
+ function normalizeString(s, config = {}) {
109
+ let normalized = s;
110
+ if (!config.caseSensitive) {
111
+ normalized = normalized.toLowerCase();
112
+ }
113
+ if (config.ignoreWhitespace !== false) {
114
+ normalized = normalized.replace(/\s+/g, " ").trim();
115
+ }
116
+ return normalized;
117
+ }
118
+ function fuzzyMatch(source, target, config = {}) {
119
+ const finalConfig = { ...DEFAULT_FUZZY_CONFIG, ...config };
120
+ const normalizedSource = normalizeString(source, finalConfig);
121
+ const normalizedTarget = normalizeString(target, finalConfig);
122
+ const levenshteinScore = levenshteinSimilarity(normalizedSource, normalizedTarget);
123
+ const jaroWinklerScore = jaroWinklerSimilarity(normalizedSource, normalizedTarget);
124
+ const ngramScore = ngramSimilarity(normalizedSource, normalizedTarget, finalConfig.ngramSize);
125
+ const similarity = levenshteinScore * finalConfig.levenshteinWeight + jaroWinklerScore * finalConfig.jaroWinklerWeight + ngramScore * finalConfig.ngramWeight;
126
+ return {
127
+ similarity,
128
+ isMatch: similarity >= finalConfig.threshold,
129
+ scores: {
130
+ levenshtein: levenshteinScore,
131
+ jaroWinkler: jaroWinklerScore,
132
+ ngram: ngramScore
133
+ },
134
+ normalizedSource,
135
+ normalizedTarget
136
+ };
137
+ }
138
+ function fuzzyContains(source, target, config = {}) {
139
+ const finalConfig = { ...DEFAULT_FUZZY_CONFIG, ...config };
140
+ const normalizedSource = normalizeString(source, finalConfig);
141
+ const normalizedTarget = normalizeString(target, finalConfig);
142
+ if (normalizedSource.includes(normalizedTarget)) {
143
+ return true;
144
+ }
145
+ const sourceWords = normalizedSource.split(/\s+/);
146
+ const targetWords = normalizedTarget.split(/\s+/);
147
+ for (const targetWord of targetWords) {
148
+ const hasMatch = sourceWords.some((sourceWord) => {
149
+ const result = fuzzyMatch(sourceWord, targetWord, { ...finalConfig, threshold: 0.8 });
150
+ return result.isMatch;
151
+ });
152
+ if (!hasMatch) {
153
+ return false;
154
+ }
155
+ }
156
+ return true;
157
+ }
158
+ function wordSimilarity(s1, s2, config = {}) {
159
+ const finalConfig = { ...DEFAULT_FUZZY_CONFIG, ...config };
160
+ const words1 = normalizeString(s1, finalConfig).split(/\s+/);
161
+ const words2 = normalizeString(s2, finalConfig).split(/\s+/);
162
+ if (words1.length === 0 && words2.length === 0) return 1;
163
+ if (words1.length === 0 || words2.length === 0) return 0;
164
+ let totalSimilarity = 0;
165
+ let matchCount = 0;
166
+ for (const word1 of words1) {
167
+ let bestSim = 0;
168
+ for (const word2 of words2) {
169
+ const result = fuzzyMatch(word1, word2, finalConfig);
170
+ if (result.similarity > bestSim) {
171
+ bestSim = result.similarity;
172
+ }
173
+ }
174
+ totalSimilarity += bestSim;
175
+ if (bestSim >= finalConfig.threshold) {
176
+ matchCount++;
177
+ }
178
+ }
179
+ const avgSimilarity = totalSimilarity / words1.length;
180
+ const matchRatio = matchCount / Math.max(words1.length, words2.length);
181
+ return avgSimilarity * 0.5 + matchRatio * 0.5;
182
+ }
183
+ function tokenize(s) {
184
+ return s.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase().split(" ").filter((token) => token.length > 0);
185
+ }
186
+ function tokenSimilarity(s1, s2) {
187
+ const tokens1 = tokenize(s1);
188
+ const tokens2 = tokenize(s2);
189
+ if (tokens1.length === 0 && tokens2.length === 0) return 1;
190
+ if (tokens1.length === 0 || tokens2.length === 0) return 0;
191
+ const set1 = new Set(tokens1);
192
+ const set2 = new Set(tokens2);
193
+ let intersection = 0;
194
+ for (const token of set1) {
195
+ if (set2.has(token)) {
196
+ intersection++;
197
+ }
198
+ }
199
+ const union = set1.size + set2.size - intersection;
200
+ return union === 0 ? 0 : intersection / union;
201
+ }
202
+
203
+ // src/ai/alias-generator.ts
204
+ var DEFAULT_ALIAS_CONFIG = {
205
+ includeText: true,
206
+ includeAriaLabel: true,
207
+ includePlaceholder: true,
208
+ includeTitle: true,
209
+ includeSynonyms: true,
210
+ maxAliases: 20,
211
+ minLength: 2,
212
+ maxLength: 50
213
+ };
214
+ var SYNONYMS = {
215
+ // Submit-related
216
+ submit: ["send", "go", "confirm", "ok", "apply", "save", "done", "finish"],
217
+ send: ["submit", "deliver", "post"],
218
+ save: ["submit", "store", "keep", "apply"],
219
+ cancel: ["close", "dismiss", "abort", "back", "exit", "quit", "nevermind"],
220
+ close: ["cancel", "dismiss", "exit", "x"],
221
+ delete: ["remove", "trash", "erase", "clear", "destroy"],
222
+ remove: ["delete", "clear", "discard"],
223
+ edit: ["modify", "change", "update", "alter"],
224
+ update: ["edit", "modify", "save", "refresh"],
225
+ add: ["create", "new", "plus", "insert"],
226
+ create: ["add", "new", "make"],
227
+ search: ["find", "lookup", "query", "filter"],
228
+ find: ["search", "locate", "lookup"],
229
+ login: ["signin", "sign in", "log in", "authenticate", "enter"],
230
+ logout: ["signout", "sign out", "log out", "exit"],
231
+ register: ["signup", "sign up", "join", "create account"],
232
+ next: ["continue", "forward", "proceed", "advance"],
233
+ previous: ["back", "backward", "return", "prior"],
234
+ back: ["previous", "return", "backward"],
235
+ start: ["begin", "launch", "initiate", "run", "execute"],
236
+ stop: ["end", "halt", "pause", "terminate"],
237
+ enable: ["activate", "turn on", "switch on"],
238
+ disable: ["deactivate", "turn off", "switch off"],
239
+ show: ["display", "reveal", "view", "open"],
240
+ hide: ["conceal", "collapse", "close"],
241
+ expand: ["open", "show", "unfold", "reveal"],
242
+ collapse: ["close", "hide", "fold", "minimize"],
243
+ yes: ["ok", "confirm", "agree", "accept"],
244
+ no: ["cancel", "decline", "reject", "deny"],
245
+ help: ["support", "assistance", "info", "information", "faq"],
246
+ settings: ["preferences", "options", "config", "configuration"],
247
+ profile: ["account", "user", "me"],
248
+ download: ["export", "save", "get"],
249
+ upload: ["import", "load", "attach"],
250
+ refresh: ["reload", "update", "sync"],
251
+ copy: ["duplicate", "clone"],
252
+ paste: ["insert"],
253
+ select: ["choose", "pick"],
254
+ toggle: ["switch", "flip"],
255
+ // Form fields
256
+ email: ["e-mail", "mail"],
257
+ password: ["pass", "pwd", "secret"],
258
+ username: ["user", "login", "account", "name"],
259
+ firstname: ["first name", "given name", "forename"],
260
+ lastname: ["last name", "surname", "family name"],
261
+ fullname: ["full name", "name", "complete name"],
262
+ phone: ["telephone", "tel", "mobile", "cell"],
263
+ address: ["location", "street"],
264
+ city: ["town"],
265
+ country: ["nation"],
266
+ zip: ["zipcode", "postal", "postal code", "postcode"],
267
+ // Navigation
268
+ home: ["main", "start", "dashboard"],
269
+ menu: ["navigation", "nav"],
270
+ sidebar: ["side bar", "side panel", "side menu"]
271
+ };
272
+ var ELEMENT_ACTION_WORDS = {
273
+ button: ["button", "btn", "click"],
274
+ input: ["input", "field", "textbox", "box"],
275
+ textarea: ["textarea", "text area", "text field", "multiline"],
276
+ select: ["select", "dropdown", "combo", "picker", "chooser"],
277
+ checkbox: ["checkbox", "check", "tick"],
278
+ radio: ["radio", "option", "choice"],
279
+ link: ["link", "anchor", "href"],
280
+ form: ["form"],
281
+ menu: ["menu"],
282
+ menuitem: ["menu item", "option"],
283
+ tab: ["tab"],
284
+ dialog: ["dialog", "modal", "popup"],
285
+ switch: ["switch", "toggle"],
286
+ slider: ["slider", "range"]
287
+ };
288
+ function normalizeAlias(text) {
289
+ return text.toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
290
+ }
291
+ function extractWords(text) {
292
+ const tokens = tokenize(text);
293
+ return tokens.filter((t) => t.length >= 2);
294
+ }
295
+ function generateTextAliases(text, config) {
296
+ if (!text || !config.includeText) return [];
297
+ const aliases = [];
298
+ const normalized = normalizeAlias(text);
299
+ if (normalized.length >= config.minLength && normalized.length <= config.maxLength) {
300
+ aliases.push(normalized);
301
+ }
302
+ const words = extractWords(text);
303
+ for (const word of words) {
304
+ if (word.length >= config.minLength) {
305
+ aliases.push(word);
306
+ }
307
+ }
308
+ if (words.length >= 2 && words.length <= 4) {
309
+ const twoWords = words.slice(0, 2).join(" ");
310
+ if (twoWords.length <= config.maxLength) {
311
+ aliases.push(twoWords);
312
+ }
313
+ if (words.length > 2) {
314
+ const lastTwo = words.slice(-2).join(" ");
315
+ if (lastTwo.length <= config.maxLength) {
316
+ aliases.push(lastTwo);
317
+ }
318
+ }
319
+ }
320
+ return aliases;
321
+ }
322
+ function generateSynonyms(aliases, config) {
323
+ if (!config.includeSynonyms) return [];
324
+ const synonyms = [];
325
+ for (const alias of aliases) {
326
+ const words = alias.toLowerCase().split(/\s+/);
327
+ for (const word of words) {
328
+ if (SYNONYMS[word]) {
329
+ for (const synonym of SYNONYMS[word]) {
330
+ const newAlias = alias.toLowerCase().replace(word, synonym);
331
+ if (newAlias !== alias.toLowerCase()) {
332
+ synonyms.push(newAlias);
333
+ }
334
+ if (synonym.length >= config.minLength) {
335
+ synonyms.push(synonym);
336
+ }
337
+ }
338
+ }
339
+ }
340
+ }
341
+ return synonyms;
342
+ }
343
+ function generateTypeAliases(elementType) {
344
+ const type = elementType.toLowerCase();
345
+ return ELEMENT_ACTION_WORDS[type] || [type];
346
+ }
347
+ function generateAliases(input, config = {}) {
348
+ const finalConfig = { ...DEFAULT_ALIAS_CONFIG, ...config };
349
+ const aliasSet = /* @__PURE__ */ new Set();
350
+ const addAlias = (alias) => {
351
+ const normalized = normalizeAlias(alias);
352
+ if (normalized.length >= finalConfig.minLength && normalized.length <= finalConfig.maxLength) {
353
+ aliasSet.add(normalized);
354
+ }
355
+ };
356
+ const addAliases = (aliases2) => {
357
+ for (const alias of aliases2) {
358
+ addAlias(alias);
359
+ }
360
+ };
361
+ if (finalConfig.includeText && input.textContent) {
362
+ addAliases(generateTextAliases(input.textContent, finalConfig));
363
+ }
364
+ if (finalConfig.includeAriaLabel && input.ariaLabel) {
365
+ addAliases(generateTextAliases(input.ariaLabel, finalConfig));
366
+ }
367
+ if (finalConfig.includeAriaLabel && input.ariaLabelledBy) {
368
+ addAliases(generateTextAliases(input.ariaLabelledBy, finalConfig));
369
+ }
370
+ if (finalConfig.includePlaceholder && input.placeholder) {
371
+ addAliases(generateTextAliases(input.placeholder, finalConfig));
372
+ }
373
+ if (finalConfig.includeTitle && input.title) {
374
+ addAliases(generateTextAliases(input.title, finalConfig));
375
+ }
376
+ if (input.labelText) {
377
+ addAliases(generateTextAliases(input.labelText, finalConfig));
378
+ }
379
+ if (input.id) {
380
+ addAliases(extractWords(input.id));
381
+ }
382
+ if (input.name) {
383
+ addAliases(extractWords(input.name));
384
+ }
385
+ if (input.value && (input.elementType === "button" || input.inputType === "submit" || input.inputType === "button")) {
386
+ addAliases(generateTextAliases(input.value, finalConfig));
387
+ }
388
+ if (input.elementType) {
389
+ addAliases(generateTypeAliases(input.elementType));
390
+ }
391
+ if (input.inputType) {
392
+ addAlias(input.inputType);
393
+ if (input.inputType === "email") {
394
+ addAliases(["email", "e-mail", "email address"]);
395
+ } else if (input.inputType === "password") {
396
+ addAliases(["password", "pass", "pwd"]);
397
+ } else if (input.inputType === "tel") {
398
+ addAliases(["phone", "telephone", "mobile"]);
399
+ } else if (input.inputType === "url") {
400
+ addAliases(["url", "website", "link", "address"]);
401
+ } else if (input.inputType === "search") {
402
+ addAliases(["search", "find", "query"]);
403
+ }
404
+ }
405
+ if (finalConfig.includeSynonyms) {
406
+ const currentAliases = Array.from(aliasSet);
407
+ addAliases(generateSynonyms(currentAliases, finalConfig));
408
+ }
409
+ let aliases = Array.from(aliasSet);
410
+ aliases.sort((a, b) => a.length - b.length);
411
+ if (aliases.length > finalConfig.maxAliases) {
412
+ aliases = aliases.slice(0, finalConfig.maxAliases);
413
+ }
414
+ return aliases;
415
+ }
416
+ function generateDescription(input) {
417
+ const parts = [];
418
+ let name = input.ariaLabel || input.labelText || input.textContent || input.placeholder || input.title || input.id || input.name;
419
+ if (name) {
420
+ name = name.trim();
421
+ if (name.length > 30) {
422
+ name = name.substring(0, 27) + "...";
423
+ }
424
+ parts.push(`"${name}"`);
425
+ }
426
+ const typeWords = ELEMENT_ACTION_WORDS[input.elementType || ""] || [input.elementType || "element"];
427
+ parts.push(typeWords[0]);
428
+ if (input.inputType && input.inputType !== "text") {
429
+ parts.push(`(${input.inputType})`);
430
+ }
431
+ return parts.join(" ");
432
+ }
433
+ function generatePurpose(input) {
434
+ const text = (input.textContent || input.ariaLabel || input.title || "").toLowerCase();
435
+ const type = input.elementType?.toLowerCase() || "";
436
+ const inputType = input.inputType?.toLowerCase() || "";
437
+ if (type === "button" || inputType === "submit") {
438
+ if (text.match(/submit|send|save|confirm|ok|done|finish|apply/)) {
439
+ return "Submits the form";
440
+ }
441
+ if (text.match(/cancel|close|dismiss|back|exit/)) {
442
+ return "Cancels or closes the current action";
443
+ }
444
+ if (text.match(/delete|remove|trash|clear/)) {
445
+ return "Deletes or removes an item";
446
+ }
447
+ if (text.match(/edit|modify|change|update/)) {
448
+ return "Edits or modifies an item";
449
+ }
450
+ if (text.match(/add|create|new|\+/)) {
451
+ return "Creates or adds a new item";
452
+ }
453
+ if (text.match(/search|find|lookup/)) {
454
+ return "Performs a search";
455
+ }
456
+ if (text.match(/login|sign.?in/)) {
457
+ return "Signs the user in";
458
+ }
459
+ if (text.match(/logout|sign.?out/)) {
460
+ return "Signs the user out";
461
+ }
462
+ if (text.match(/register|sign.?up|join/)) {
463
+ return "Creates a new account";
464
+ }
465
+ if (text.match(/next|continue|proceed/)) {
466
+ return "Proceeds to the next step";
467
+ }
468
+ if (text.match(/previous|back|return/)) {
469
+ return "Returns to the previous step";
470
+ }
471
+ }
472
+ if (type === "input" || type === "textarea") {
473
+ if (inputType === "email") return "Accepts email address input";
474
+ if (inputType === "password") return "Accepts password input";
475
+ if (inputType === "search") return "Accepts search query input";
476
+ if (inputType === "tel") return "Accepts phone number input";
477
+ if (inputType === "url") return "Accepts URL input";
478
+ if (inputType === "number") return "Accepts numeric input";
479
+ if (inputType === "date") return "Accepts date input";
480
+ if (inputType === "file") return "Accepts file upload";
481
+ }
482
+ if (type === "checkbox") {
483
+ return "Toggles an option on or off";
484
+ }
485
+ if (type === "radio") {
486
+ return "Selects one option from a group";
487
+ }
488
+ if (type === "select") {
489
+ return "Selects an option from a dropdown";
490
+ }
491
+ if (type === "link") {
492
+ return "Navigates to another page";
493
+ }
494
+ return void 0;
495
+ }
496
+ function generateSuggestedActions(input) {
497
+ const type = input.elementType?.toLowerCase() || "";
498
+ const inputType = input.inputType?.toLowerCase() || "";
499
+ const text = (input.textContent || input.ariaLabel || "").toLowerCase();
500
+ const actions = [];
501
+ switch (type) {
502
+ case "button":
503
+ actions.push(`click "${text || "this button"}"`);
504
+ break;
505
+ case "input":
506
+ if (inputType === "checkbox") {
507
+ actions.push("check to enable", "uncheck to disable");
508
+ } else if (inputType === "radio") {
509
+ actions.push("select this option");
510
+ } else {
511
+ actions.push(`type into "${text || "this field"}"`);
512
+ actions.push("clear the field");
513
+ }
514
+ break;
515
+ case "textarea":
516
+ actions.push(`type into "${text || "this text area"}"`);
517
+ actions.push("clear the content");
518
+ break;
519
+ case "select":
520
+ actions.push(`select an option from "${text || "this dropdown"}"`);
521
+ break;
522
+ case "checkbox":
523
+ actions.push("check to enable", "uncheck to disable");
524
+ break;
525
+ case "radio":
526
+ actions.push("select this option");
527
+ break;
528
+ case "link":
529
+ actions.push(`click to navigate to "${text || "the linked page"}"`);
530
+ break;
531
+ case "switch":
532
+ actions.push("toggle on", "toggle off");
533
+ break;
534
+ default:
535
+ actions.push("click");
536
+ }
537
+ return actions;
538
+ }
539
+ function areSynonyms(word1, word2) {
540
+ const w1 = word1.toLowerCase().trim();
541
+ const w2 = word2.toLowerCase().trim();
542
+ if (w1 === w2) return true;
543
+ const synonyms1 = SYNONYMS[w1] || [];
544
+ const synonyms2 = SYNONYMS[w2] || [];
545
+ return synonyms1.includes(w2) || synonyms2.includes(w1);
546
+ }
547
+
548
+ // src/ai/search-engine.ts
549
+ var DEFAULT_SEARCH_CONFIG = {
550
+ fuzzyThreshold: 0.7,
551
+ textWeight: 0.35,
552
+ accessibilityWeight: 0.25,
553
+ roleWeight: 0.15,
554
+ spatialWeight: 0.1,
555
+ aliasWeight: 0.15,
556
+ maxResults: 20,
557
+ includeHidden: false
558
+ };
559
+ var SearchEngine = class {
560
+ // Cache valid for 100ms
561
+ constructor(config = {}) {
562
+ this.cachedElements = [];
563
+ this.cacheTimestamp = 0;
564
+ this.cacheValidityMs = 100;
565
+ this.config = { ...DEFAULT_SEARCH_CONFIG, ...config };
566
+ }
567
+ /**
568
+ * Update cached elements from various sources
569
+ */
570
+ updateElements(elements, getState) {
571
+ this.cachedElements = elements.map((el) => this.toSearchable(el, getState));
572
+ this.cacheTimestamp = Date.now();
573
+ }
574
+ /**
575
+ * Convert an element to searchable format
576
+ */
577
+ toSearchable(element, getState) {
578
+ let state;
579
+ let textContent;
580
+ let tagName;
581
+ let role;
582
+ let ariaLabel;
583
+ let placeholder;
584
+ let title;
585
+ let labelText;
586
+ let value;
587
+ if ("getState" in element && typeof element.getState === "function") {
588
+ state = getState ? getState(element) : element.getState();
589
+ textContent = state.textContent || void 0;
590
+ tagName = element.element.tagName.toLowerCase();
591
+ role = element.element.getAttribute("role") || void 0;
592
+ ariaLabel = element.element.getAttribute("aria-label") || void 0;
593
+ placeholder = element.element.getAttribute("placeholder") || void 0;
594
+ title = element.element.getAttribute("title") || void 0;
595
+ if (element.element.id) {
596
+ const labelEl = document.querySelector(`label[for="${element.element.id}"]`);
597
+ labelText = labelEl?.textContent?.trim() || void 0;
598
+ }
599
+ if (element.element instanceof HTMLInputElement || element.element instanceof HTMLTextAreaElement || element.element instanceof HTMLSelectElement) {
600
+ value = element.element.value || void 0;
601
+ }
602
+ } else {
603
+ const discovered = element;
604
+ state = discovered.state;
605
+ textContent = state.textContent || void 0;
606
+ tagName = discovered.tagName;
607
+ role = discovered.role || void 0;
608
+ ariaLabel = discovered.accessibleName || void 0;
609
+ }
610
+ const aliases = generateAliases({
611
+ textContent,
612
+ ariaLabel,
613
+ placeholder,
614
+ title,
615
+ elementType: element.type,
616
+ id: element.id,
617
+ labelText,
618
+ value
619
+ });
620
+ const description = generateDescription({
621
+ textContent,
622
+ ariaLabel,
623
+ placeholder,
624
+ title,
625
+ elementType: element.type,
626
+ id: element.id,
627
+ labelText
628
+ });
629
+ return {
630
+ id: element.id,
631
+ element,
632
+ state,
633
+ textContent,
634
+ ariaLabel,
635
+ placeholder,
636
+ title,
637
+ role,
638
+ tagName,
639
+ type: element.type,
640
+ aliases,
641
+ description,
642
+ rect: state.rect,
643
+ labelText,
644
+ value
645
+ };
646
+ }
647
+ /**
648
+ * Search for elements matching the criteria
649
+ */
650
+ search(criteria, elements) {
651
+ const startTime = performance.now();
652
+ if (elements) {
653
+ this.updateElements(elements);
654
+ }
655
+ let searchableElements = this.cachedElements;
656
+ if (!this.config.includeHidden && !criteria.fuzzy) {
657
+ searchableElements = searchableElements.filter((el) => el.state.visible);
658
+ }
659
+ const results = [];
660
+ for (const searchable of searchableElements) {
661
+ const result = this.scoreElement(searchable, criteria);
662
+ if (result.confidence >= (criteria.fuzzyThreshold ?? this.config.fuzzyThreshold)) {
663
+ results.push(result);
664
+ }
665
+ }
666
+ results.sort((a, b) => b.confidence - a.confidence);
667
+ const limitedResults = results.slice(0, this.config.maxResults);
668
+ return {
669
+ results: limitedResults,
670
+ bestMatch: limitedResults.length > 0 ? limitedResults[0] : null,
671
+ scannedCount: searchableElements.length,
672
+ durationMs: performance.now() - startTime,
673
+ criteria,
674
+ timestamp: Date.now()
675
+ };
676
+ }
677
+ /**
678
+ * Find the best matching element
679
+ */
680
+ findBest(criteria, elements) {
681
+ const response = this.search(criteria, elements);
682
+ return response.bestMatch;
683
+ }
684
+ /**
685
+ * Find elements by text content
686
+ */
687
+ findByText(text, fuzzy = true, elements) {
688
+ return this.search({ text, fuzzy }, elements).results;
689
+ }
690
+ /**
691
+ * Find elements by role
692
+ */
693
+ findByRole(role, name, elements) {
694
+ const criteria = { role };
695
+ if (name) {
696
+ criteria.accessibleName = name;
697
+ }
698
+ return this.search(criteria, elements).results;
699
+ }
700
+ /**
701
+ * Find elements by accessible name
702
+ */
703
+ findByAccessibleName(name, elements) {
704
+ return this.search({ accessibleName: name, fuzzy: true }, elements).results;
705
+ }
706
+ /**
707
+ * Find elements near another element
708
+ */
709
+ findNear(referenceId, criteria, elements) {
710
+ return this.search({ ...criteria, near: referenceId }, elements).results;
711
+ }
712
+ /**
713
+ * Find elements within a container
714
+ */
715
+ findWithin(containerId, criteria, elements) {
716
+ return this.search({ ...criteria, within: containerId }, elements).results;
717
+ }
718
+ /**
719
+ * Score an element against search criteria
720
+ */
721
+ scoreElement(searchable, criteria) {
722
+ const scores = {};
723
+ const matchReasons = [];
724
+ let totalWeight = 0;
725
+ let weightedScore = 0;
726
+ const fuzzyConfig = {
727
+ ...DEFAULT_FUZZY_CONFIG,
728
+ threshold: criteria.fuzzyThreshold ?? this.config.fuzzyThreshold
729
+ };
730
+ if (criteria.text) {
731
+ const textScore = this.scoreTextMatch(searchable, criteria.text, criteria.fuzzy !== false, fuzzyConfig.threshold);
732
+ scores.text = textScore.score;
733
+ if (textScore.score > 0) {
734
+ matchReasons.push(...textScore.reasons);
735
+ }
736
+ weightedScore += textScore.score * this.config.textWeight;
737
+ totalWeight += this.config.textWeight;
738
+ }
739
+ if (criteria.textContains) {
740
+ const containsScore = this.scoreContainsMatch(searchable, criteria.textContains, criteria.fuzzy !== false);
741
+ scores.text = Math.max(scores.text || 0, containsScore.score);
742
+ if (containsScore.score > 0 && containsScore.reasons.length > 0) {
743
+ matchReasons.push(...containsScore.reasons);
744
+ }
745
+ weightedScore += containsScore.score * this.config.textWeight;
746
+ totalWeight += this.config.textWeight;
747
+ }
748
+ if (criteria.accessibleName) {
749
+ const accessibilityScore = this.scoreAccessibilityMatch(
750
+ searchable,
751
+ criteria.accessibleName,
752
+ criteria.fuzzy !== false,
753
+ fuzzyConfig.threshold
754
+ );
755
+ scores.accessibility = accessibilityScore.score;
756
+ if (accessibilityScore.score > 0) {
757
+ matchReasons.push(...accessibilityScore.reasons);
758
+ }
759
+ weightedScore += accessibilityScore.score * this.config.accessibilityWeight;
760
+ totalWeight += this.config.accessibilityWeight;
761
+ }
762
+ if (criteria.role) {
763
+ const roleScore = this.scoreRoleMatch(searchable, criteria.role);
764
+ scores.role = roleScore.score;
765
+ if (roleScore.score > 0) {
766
+ matchReasons.push(...roleScore.reasons);
767
+ }
768
+ weightedScore += roleScore.score * this.config.roleWeight;
769
+ totalWeight += this.config.roleWeight;
770
+ }
771
+ if (criteria.type) {
772
+ const typeMatch = searchable.type.toLowerCase() === criteria.type.toLowerCase();
773
+ if (typeMatch) {
774
+ matchReasons.push(`type: ${criteria.type}`);
775
+ weightedScore += 1 * this.config.roleWeight;
776
+ totalWeight += this.config.roleWeight;
777
+ }
778
+ }
779
+ if (criteria.near) {
780
+ const spatialScore = this.scoreSpatialMatch(searchable, criteria.near);
781
+ scores.spatial = spatialScore.score;
782
+ if (spatialScore.score > 0) {
783
+ matchReasons.push(...spatialScore.reasons);
784
+ }
785
+ weightedScore += spatialScore.score * this.config.spatialWeight;
786
+ totalWeight += this.config.spatialWeight;
787
+ }
788
+ if (criteria.placeholder && searchable.placeholder) {
789
+ const placeholderResult = fuzzyMatch(searchable.placeholder, criteria.placeholder, fuzzyConfig);
790
+ if (placeholderResult.isMatch) {
791
+ matchReasons.push(`placeholder matches`);
792
+ weightedScore += placeholderResult.similarity * this.config.textWeight;
793
+ totalWeight += this.config.textWeight;
794
+ }
795
+ }
796
+ if (criteria.title && searchable.title) {
797
+ const titleResult = fuzzyMatch(searchable.title, criteria.title, fuzzyConfig);
798
+ if (titleResult.isMatch) {
799
+ matchReasons.push(`title matches`);
800
+ weightedScore += titleResult.similarity * this.config.textWeight;
801
+ totalWeight += this.config.textWeight;
802
+ }
803
+ }
804
+ if (criteria.idPattern) {
805
+ const idMatch = this.matchPattern(searchable.id, criteria.idPattern);
806
+ if (idMatch) {
807
+ matchReasons.push(`id matches pattern`);
808
+ weightedScore += 1 * this.config.textWeight;
809
+ totalWeight += this.config.textWeight;
810
+ }
811
+ }
812
+ const aliasScore = this.scoreAliasMatch(searchable, criteria, fuzzyConfig.threshold);
813
+ if (aliasScore.score > 0) {
814
+ scores.fuzzy = aliasScore.score;
815
+ matchReasons.push(...aliasScore.reasons);
816
+ weightedScore += aliasScore.score * this.config.aliasWeight;
817
+ totalWeight += this.config.aliasWeight;
818
+ }
819
+ const confidence = totalWeight > 0 ? weightedScore / totalWeight : 0;
820
+ const aiElement = this.toAIDiscoveredElement(searchable);
821
+ return {
822
+ element: aiElement,
823
+ confidence,
824
+ matchReasons,
825
+ scores
826
+ };
827
+ }
828
+ /**
829
+ * Score text match
830
+ */
831
+ scoreTextMatch(searchable, text, fuzzy, threshold) {
832
+ const reasons = [];
833
+ let maxScore = 0;
834
+ const textsToMatch = [
835
+ searchable.textContent,
836
+ searchable.labelText,
837
+ searchable.value
838
+ ].filter(Boolean);
839
+ for (const targetText of textsToMatch) {
840
+ if (targetText.toLowerCase() === text.toLowerCase()) {
841
+ maxScore = Math.max(maxScore, 1);
842
+ reasons.push("exact text match");
843
+ continue;
844
+ }
845
+ if (fuzzy) {
846
+ const result = fuzzyMatch(targetText, text, { threshold });
847
+ if (result.isMatch && result.similarity > maxScore) {
848
+ maxScore = result.similarity;
849
+ reasons.push(`text similarity: ${(result.similarity * 100).toFixed(0)}%`);
850
+ }
851
+ const wordSim = wordSimilarity(targetText, text, { threshold });
852
+ if (wordSim > maxScore && wordSim >= threshold) {
853
+ maxScore = wordSim;
854
+ reasons.push(`word match: ${(wordSim * 100).toFixed(0)}%`);
855
+ }
856
+ }
857
+ }
858
+ return { score: maxScore, reasons };
859
+ }
860
+ /**
861
+ * Score contains match
862
+ */
863
+ scoreContainsMatch(searchable, text, fuzzy) {
864
+ const reasons = [];
865
+ let maxScore = 0;
866
+ const textsToMatch = [
867
+ searchable.textContent,
868
+ searchable.labelText,
869
+ searchable.ariaLabel
870
+ ].filter(Boolean);
871
+ for (const targetText of textsToMatch) {
872
+ if (targetText.toLowerCase().includes(text.toLowerCase())) {
873
+ maxScore = Math.max(maxScore, 0.9);
874
+ reasons.push("text contains match");
875
+ continue;
876
+ }
877
+ if (fuzzy && fuzzyContains(targetText, text)) {
878
+ maxScore = Math.max(maxScore, 0.7);
879
+ reasons.push("fuzzy contains match");
880
+ }
881
+ }
882
+ return { score: maxScore, reasons };
883
+ }
884
+ /**
885
+ * Score accessibility match
886
+ */
887
+ scoreAccessibilityMatch(searchable, name, fuzzy, threshold) {
888
+ const reasons = [];
889
+ let maxScore = 0;
890
+ const accessibleNames = [
891
+ searchable.ariaLabel,
892
+ searchable.ariaLabelledBy,
893
+ searchable.labelText,
894
+ searchable.title
895
+ ].filter(Boolean);
896
+ for (const accessibleName of accessibleNames) {
897
+ if (accessibleName.toLowerCase() === name.toLowerCase()) {
898
+ maxScore = Math.max(maxScore, 1);
899
+ reasons.push("exact accessible name match");
900
+ continue;
901
+ }
902
+ if (fuzzy) {
903
+ const result = fuzzyMatch(accessibleName, name, { threshold });
904
+ if (result.isMatch && result.similarity > maxScore) {
905
+ maxScore = result.similarity;
906
+ reasons.push(`accessible name similarity: ${(result.similarity * 100).toFixed(0)}%`);
907
+ }
908
+ }
909
+ }
910
+ return { score: maxScore, reasons };
911
+ }
912
+ /**
913
+ * Score role match
914
+ */
915
+ scoreRoleMatch(searchable, role) {
916
+ const reasons = [];
917
+ const normalizedRole = role.toLowerCase();
918
+ if (searchable.role?.toLowerCase() === normalizedRole) {
919
+ return { score: 1, reasons: [`role: ${role}`] };
920
+ }
921
+ const tagRoleMap = {
922
+ button: ["button", "input[type=button]", "input[type=submit]"],
923
+ textbox: ["input", "textarea"],
924
+ checkbox: ["input[type=checkbox]"],
925
+ radio: ["input[type=radio]"],
926
+ link: ["a"],
927
+ listbox: ["select"],
928
+ combobox: ["select", "input[list]"],
929
+ navigation: ["nav"],
930
+ main: ["main"],
931
+ heading: ["h1", "h2", "h3", "h4", "h5", "h6"]
932
+ };
933
+ const inferredRoles = tagRoleMap[normalizedRole] || [];
934
+ if (inferredRoles.some((r) => searchable.tagName === r || searchable.type.toLowerCase() === normalizedRole)) {
935
+ return { score: 0.8, reasons: [`inferred role: ${role}`] };
936
+ }
937
+ return { score: 0, reasons };
938
+ }
939
+ /**
940
+ * Score spatial match (proximity to another element)
941
+ */
942
+ scoreSpatialMatch(searchable, nearId) {
943
+ const reference = this.cachedElements.find((el) => el.id === nearId);
944
+ if (!reference) {
945
+ return { score: 0, reasons: [] };
946
+ }
947
+ const distance = this.calculateDistance(searchable.rect, reference.rect);
948
+ const nearThreshold = 200;
949
+ if (distance > nearThreshold * 3) {
950
+ return { score: 0, reasons: [] };
951
+ }
952
+ const score = Math.max(0, 1 - distance / (nearThreshold * 3));
953
+ return {
954
+ score,
955
+ reasons: [`${distance.toFixed(0)}px from ${nearId}`]
956
+ };
957
+ }
958
+ /**
959
+ * Calculate distance between two element rectangles
960
+ */
961
+ calculateDistance(rect1, rect2) {
962
+ const center1 = {
963
+ x: rect1.x + rect1.width / 2,
964
+ y: rect1.y + rect1.height / 2
965
+ };
966
+ const center2 = {
967
+ x: rect2.x + rect2.width / 2,
968
+ y: rect2.y + rect2.height / 2
969
+ };
970
+ return Math.sqrt(Math.pow(center1.x - center2.x, 2) + Math.pow(center1.y - center2.y, 2));
971
+ }
972
+ /**
973
+ * Score alias match
974
+ */
975
+ scoreAliasMatch(searchable, criteria, threshold) {
976
+ const reasons = [];
977
+ let maxScore = 0;
978
+ const searchTerms = [];
979
+ if (criteria.text) searchTerms.push(criteria.text);
980
+ if (criteria.textContains) searchTerms.push(criteria.textContains);
981
+ if (criteria.accessibleName) searchTerms.push(criteria.accessibleName);
982
+ for (const searchTerm of searchTerms) {
983
+ const termLower = searchTerm.toLowerCase();
984
+ for (const alias of searchable.aliases) {
985
+ if (alias === termLower) {
986
+ maxScore = Math.max(maxScore, 1);
987
+ reasons.push(`alias match: "${alias}"`);
988
+ continue;
989
+ }
990
+ const searchWords = termLower.split(/\s+/);
991
+ const aliasWords = alias.split(/\s+/);
992
+ for (const searchWord of searchWords) {
993
+ for (const aliasWord of aliasWords) {
994
+ if (areSynonyms(searchWord, aliasWord)) {
995
+ maxScore = Math.max(maxScore, 0.85);
996
+ reasons.push(`synonym match: "${searchWord}" ~ "${aliasWord}"`);
997
+ }
998
+ }
999
+ }
1000
+ const result = fuzzyMatch(alias, termLower, { threshold });
1001
+ if (result.isMatch && result.similarity > maxScore) {
1002
+ maxScore = result.similarity;
1003
+ reasons.push(`fuzzy alias: "${alias}" (${(result.similarity * 100).toFixed(0)}%)`);
1004
+ }
1005
+ const tokenSim = tokenSimilarity(alias, termLower);
1006
+ if (tokenSim > maxScore && tokenSim >= threshold) {
1007
+ maxScore = tokenSim;
1008
+ reasons.push(`token match: "${alias}"`);
1009
+ }
1010
+ }
1011
+ }
1012
+ return { score: maxScore, reasons };
1013
+ }
1014
+ /**
1015
+ * Match a string against a pattern (supports * wildcard)
1016
+ */
1017
+ matchPattern(str, pattern) {
1018
+ const regexPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
1019
+ return new RegExp(`^${regexPattern}$`, "i").test(str);
1020
+ }
1021
+ /**
1022
+ * Convert searchable element to AI discovered element
1023
+ */
1024
+ toAIDiscoveredElement(searchable) {
1025
+ const discoveredBase = "getState" in searchable.element ? {
1026
+ id: searchable.id,
1027
+ type: searchable.type,
1028
+ label: searchable.element.label,
1029
+ tagName: searchable.tagName,
1030
+ role: searchable.role,
1031
+ accessibleName: searchable.ariaLabel,
1032
+ actions: searchable.element.actions,
1033
+ state: searchable.state,
1034
+ registered: true
1035
+ } : searchable.element;
1036
+ return {
1037
+ ...discoveredBase,
1038
+ description: searchable.description,
1039
+ aliases: searchable.aliases,
1040
+ purpose: generatePurpose({
1041
+ textContent: searchable.textContent,
1042
+ ariaLabel: searchable.ariaLabel,
1043
+ elementType: searchable.type,
1044
+ tagName: searchable.tagName
1045
+ }),
1046
+ parentContext: void 0,
1047
+ // Would need DOM traversal
1048
+ suggestedActions: generateSuggestedActions({
1049
+ textContent: searchable.textContent,
1050
+ ariaLabel: searchable.ariaLabel,
1051
+ elementType: searchable.type,
1052
+ tagName: searchable.tagName
1053
+ }),
1054
+ semanticType: this.inferSemanticType(searchable),
1055
+ labelText: searchable.labelText,
1056
+ placeholder: searchable.placeholder,
1057
+ title: searchable.title
1058
+ };
1059
+ }
1060
+ /**
1061
+ * Infer a semantic type for the element
1062
+ */
1063
+ inferSemanticType(searchable) {
1064
+ const text = (searchable.textContent || searchable.ariaLabel || "").toLowerCase();
1065
+ const type = searchable.type.toLowerCase();
1066
+ if (type === "input" || type === "textarea") {
1067
+ if (searchable.placeholder?.toLowerCase().includes("email") || text.includes("email")) {
1068
+ return "email-input";
1069
+ }
1070
+ if (searchable.placeholder?.toLowerCase().includes("password") || text.includes("password")) {
1071
+ return "password-input";
1072
+ }
1073
+ if (searchable.placeholder?.toLowerCase().includes("search") || text.includes("search")) {
1074
+ return "search-input";
1075
+ }
1076
+ return "text-input";
1077
+ }
1078
+ if (type === "button") {
1079
+ if (text.match(/submit|save|confirm|ok|done|apply/)) return "submit-button";
1080
+ if (text.match(/cancel|close|dismiss/)) return "cancel-button";
1081
+ if (text.match(/delete|remove|trash/)) return "delete-button";
1082
+ if (text.match(/add|create|new|\+/)) return "add-button";
1083
+ if (text.match(/edit|modify/)) return "edit-button";
1084
+ if (text.match(/next|continue/)) return "next-button";
1085
+ if (text.match(/back|previous/)) return "back-button";
1086
+ return "action-button";
1087
+ }
1088
+ if (type === "link") {
1089
+ if (text.match(/home|dashboard/)) return "home-link";
1090
+ if (text.match(/login|sign.?in/)) return "login-link";
1091
+ if (text.match(/logout|sign.?out/)) return "logout-link";
1092
+ return "navigation-link";
1093
+ }
1094
+ return type;
1095
+ }
1096
+ };
1097
+
1098
+ // src/ai/summary-generator.ts
1099
+ var DEFAULT_SUMMARY_CONFIG = {
1100
+ maxLength: 2e3,
1101
+ includeForms: true,
1102
+ includeElementCounts: true,
1103
+ includeModals: true,
1104
+ includeFocused: true,
1105
+ verbosity: "normal"
1106
+ };
1107
+ function generatePageSummary(elements, pageContext, config = {}) {
1108
+ const finalConfig = { ...DEFAULT_SUMMARY_CONFIG, ...config };
1109
+ const lines = [];
1110
+ if (pageContext) {
1111
+ if (pageContext.title) {
1112
+ lines.push(`Page: "${pageContext.title}"`);
1113
+ }
1114
+ if (pageContext.pageType && pageContext.pageType !== "unknown") {
1115
+ lines.push(`Type: ${formatPageType(pageContext.pageType)}`);
1116
+ }
1117
+ }
1118
+ if (finalConfig.includeElementCounts) {
1119
+ const counts = countElementTypes(elements);
1120
+ const countParts = [];
1121
+ if (counts.button > 0) countParts.push(`${counts.button} button${counts.button > 1 ? "s" : ""}`);
1122
+ if (counts.input > 0) countParts.push(`${counts.input} input${counts.input > 1 ? "s" : ""}`);
1123
+ if (counts.link > 0) countParts.push(`${counts.link} link${counts.link > 1 ? "s" : ""}`);
1124
+ if (counts.select > 0) countParts.push(`${counts.select} dropdown${counts.select > 1 ? "s" : ""}`);
1125
+ if (counts.checkbox > 0) countParts.push(`${counts.checkbox} checkbox${counts.checkbox > 1 ? "es" : ""}`);
1126
+ if (countParts.length > 0) {
1127
+ lines.push(`Contains: ${countParts.join(", ")}`);
1128
+ }
1129
+ }
1130
+ if (finalConfig.includeForms) {
1131
+ const forms = detectForms(elements);
1132
+ if (forms.length > 0) {
1133
+ lines.push("");
1134
+ lines.push("Forms:");
1135
+ for (const form of forms) {
1136
+ lines.push(generateFormSummary(form, finalConfig.verbosity));
1137
+ }
1138
+ }
1139
+ }
1140
+ if (finalConfig.includeModals && pageContext?.activeModals && pageContext.activeModals.length > 0) {
1141
+ lines.push("");
1142
+ lines.push(`Active modals: ${pageContext.activeModals.join(", ")}`);
1143
+ }
1144
+ if (finalConfig.includeFocused && pageContext?.focusedElement) {
1145
+ lines.push(`Focus: ${pageContext.focusedElement}`);
1146
+ }
1147
+ const keyElements = getKeyElements(elements);
1148
+ if (keyElements.length > 0) {
1149
+ lines.push("");
1150
+ lines.push("Key elements:");
1151
+ for (const el of keyElements) {
1152
+ lines.push(` - ${el.description}${el.state.enabled ? "" : " (disabled)"}`);
1153
+ }
1154
+ }
1155
+ let summary = lines.join("\n");
1156
+ if (summary.length > finalConfig.maxLength) {
1157
+ summary = summary.substring(0, finalConfig.maxLength - 3) + "...";
1158
+ }
1159
+ return summary;
1160
+ }
1161
+ function generateFormSummary(form, verbosity) {
1162
+ const lines = [];
1163
+ const formName = form.name || form.purpose || form.id;
1164
+ lines.push(` ${formName}:`);
1165
+ if (verbosity === "brief") {
1166
+ const fieldCount = form.fields.length;
1167
+ const filledCount = form.fields.filter((f) => f.value).length;
1168
+ lines.push(` ${filledCount}/${fieldCount} fields filled, ${form.isValid ? "valid" : "has errors"}`);
1169
+ } else {
1170
+ for (const field of form.fields) {
1171
+ let fieldLine = ` - ${field.label || field.id}`;
1172
+ if (field.value) {
1173
+ fieldLine += ` = "${truncate(field.value, 15)}"`;
1174
+ } else if (field.placeholder) {
1175
+ fieldLine += ` (${field.placeholder})`;
1176
+ } else {
1177
+ fieldLine += " (empty)";
1178
+ }
1179
+ if (!field.valid && field.error) {
1180
+ fieldLine += ` [ERROR: ${field.error}]`;
1181
+ } else if (field.required && !field.value) {
1182
+ fieldLine += " [required]";
1183
+ }
1184
+ lines.push(fieldLine);
1185
+ }
1186
+ if (form.submitButton) {
1187
+ lines.push(` Submit: ${form.submitButton}`);
1188
+ }
1189
+ }
1190
+ return lines.join("\n");
1191
+ }
1192
+ function generateDiffSummary(appeared, disappeared, modified) {
1193
+ const lines = [];
1194
+ if (appeared.length > 0) {
1195
+ lines.push(`Appeared: ${appeared.join(", ")}`);
1196
+ }
1197
+ if (disappeared.length > 0) {
1198
+ lines.push(`Disappeared: ${disappeared.join(", ")}`);
1199
+ }
1200
+ if (modified.length > 0) {
1201
+ lines.push("Changed:");
1202
+ for (const mod of modified.slice(0, 5)) {
1203
+ lines.push(` - ${mod.description}: ${mod.property} changed from "${mod.from}" to "${mod.to}"`);
1204
+ }
1205
+ if (modified.length > 5) {
1206
+ lines.push(` ... and ${modified.length - 5} more changes`);
1207
+ }
1208
+ }
1209
+ if (lines.length === 0) {
1210
+ return "No changes detected";
1211
+ }
1212
+ return lines.join("\n");
1213
+ }
1214
+ function countElementTypes(elements) {
1215
+ const counts = {};
1216
+ for (const el of elements) {
1217
+ const type = el.type.toLowerCase();
1218
+ counts[type] = (counts[type] || 0) + 1;
1219
+ }
1220
+ return counts;
1221
+ }
1222
+ function detectForms(elements) {
1223
+ const formElements = elements.filter(
1224
+ (el) => el.type === "input" || el.type === "textarea" || el.type === "select" || el.type === "checkbox"
1225
+ );
1226
+ if (formElements.length === 0) return [];
1227
+ const forms = [];
1228
+ const submitButtons = elements.filter(
1229
+ (el) => el.type === "button" && (el.state.textContent?.toLowerCase().includes("submit") || el.state.textContent?.toLowerCase().includes("save") || el.state.textContent?.toLowerCase().includes("send") || el.semanticType === "submit-button")
1230
+ );
1231
+ const defaultForm = {
1232
+ id: "detected-form",
1233
+ purpose: inferFormPurpose(formElements),
1234
+ fields: formElements.map((el) => ({
1235
+ id: el.id,
1236
+ label: el.labelText || el.accessibleName || el.placeholder || el.id,
1237
+ type: el.type,
1238
+ value: el.state.value || "",
1239
+ valid: true,
1240
+ // Can't determine without validation state
1241
+ required: false,
1242
+ // Can't determine without DOM access
1243
+ placeholder: el.placeholder
1244
+ })),
1245
+ isValid: true,
1246
+ submitButton: submitButtons[0]?.id
1247
+ };
1248
+ if (defaultForm.fields.length > 0) {
1249
+ forms.push(defaultForm);
1250
+ }
1251
+ return forms;
1252
+ }
1253
+ function inferFormPurpose(fields) {
1254
+ const labels = fields.map(
1255
+ (f) => (f.labelText || f.accessibleName || f.placeholder || "").toLowerCase()
1256
+ );
1257
+ const allLabels = labels.join(" ");
1258
+ if (allLabels.includes("email") && allLabels.includes("password")) {
1259
+ if (allLabels.includes("confirm") || allLabels.includes("name")) {
1260
+ return "Registration form";
1261
+ }
1262
+ return "Login form";
1263
+ }
1264
+ if (allLabels.includes("search")) {
1265
+ return "Search form";
1266
+ }
1267
+ if (allLabels.includes("address") || allLabels.includes("city") || allLabels.includes("zip")) {
1268
+ return "Address form";
1269
+ }
1270
+ if (allLabels.includes("card") || allLabels.includes("cvv") || allLabels.includes("expir")) {
1271
+ return "Payment form";
1272
+ }
1273
+ if (allLabels.includes("contact") || allLabels.includes("message")) {
1274
+ return "Contact form";
1275
+ }
1276
+ return "Form";
1277
+ }
1278
+ function getKeyElements(elements) {
1279
+ const keyElements = [];
1280
+ const actionButtons = elements.filter(
1281
+ (el) => el.type === "button" && el.state.visible && (el.semanticType?.includes("submit") || el.semanticType?.includes("action") || el.semanticType?.includes("next"))
1282
+ );
1283
+ keyElements.push(...actionButtons.slice(0, 2));
1284
+ const primaryInputs = elements.filter(
1285
+ (el) => (el.type === "input" || el.type === "textarea") && el.state.visible
1286
+ );
1287
+ keyElements.push(...primaryInputs.slice(0, 3));
1288
+ const links = elements.filter((el) => el.type === "link" && el.state.visible);
1289
+ keyElements.push(...links.slice(0, 2));
1290
+ const unique = [...new Map(keyElements.map((e) => [e.id, e])).values()];
1291
+ return unique.slice(0, 8);
1292
+ }
1293
+ function formatPageType(pageType) {
1294
+ const typeLabels = {
1295
+ login: "Login page",
1296
+ dashboard: "Dashboard",
1297
+ form: "Form page",
1298
+ list: "List/table page",
1299
+ detail: "Detail page",
1300
+ search: "Search page",
1301
+ checkout: "Checkout page",
1302
+ settings: "Settings page",
1303
+ unknown: "Unknown"
1304
+ };
1305
+ return typeLabels[pageType || "unknown"] || "Page";
1306
+ }
1307
+ function truncate(str, maxLength) {
1308
+ if (str.length <= maxLength) return str;
1309
+ return str.substring(0, maxLength - 3) + "...";
1310
+ }
1311
+ function inferPageType(url, title, elements) {
1312
+ const urlLower = url.toLowerCase();
1313
+ const titleLower = title.toLowerCase();
1314
+ if (urlLower.includes("login") || urlLower.includes("signin")) return "login";
1315
+ if (urlLower.includes("dashboard")) return "dashboard";
1316
+ if (urlLower.includes("search")) return "search";
1317
+ if (urlLower.includes("checkout") || urlLower.includes("payment")) return "checkout";
1318
+ if (urlLower.includes("settings") || urlLower.includes("preferences")) return "settings";
1319
+ if (titleLower.includes("login") || titleLower.includes("sign in")) return "login";
1320
+ if (titleLower.includes("dashboard")) return "dashboard";
1321
+ if (titleLower.includes("search")) return "search";
1322
+ const hasLoginForm = elements.some((el) => el.type === "input" && el.semanticType === "email-input") && elements.some((el) => el.type === "input" && el.semanticType === "password-input");
1323
+ if (hasLoginForm) return "login";
1324
+ const hasSearchInput = elements.some(
1325
+ (el) => el.type === "input" && el.semanticType === "search-input"
1326
+ );
1327
+ if (hasSearchInput) return "search";
1328
+ const inputCount = elements.filter(
1329
+ (el) => el.type === "input" || el.type === "textarea" || el.type === "select"
1330
+ ).length;
1331
+ if (inputCount >= 3) return "form";
1332
+ const hasTable = elements.some((el) => el.tagName === "table");
1333
+ const hasMany = elements.length > 20;
1334
+ if (hasTable || hasMany) return "list";
1335
+ return "unknown";
1336
+ }
1337
+
1338
+ // src/ai/nl-action-parser.ts
1339
+ var ACTION_PATTERNS = [
1340
+ // Click patterns
1341
+ {
1342
+ regex: /^click\s+(?:on\s+)?(?:the\s+)?(.+?)(?:\s+button)?$/i,
1343
+ action: "click",
1344
+ targetGroup: 1,
1345
+ confidence: 0.95
1346
+ },
1347
+ {
1348
+ regex: /^press\s+(?:the\s+)?(.+?)(?:\s+button)?$/i,
1349
+ action: "click",
1350
+ targetGroup: 1,
1351
+ confidence: 0.9
1352
+ },
1353
+ {
1354
+ regex: /^tap\s+(?:on\s+)?(?:the\s+)?(.+)$/i,
1355
+ action: "click",
1356
+ targetGroup: 1,
1357
+ confidence: 0.85
1358
+ },
1359
+ {
1360
+ regex: /^activate\s+(?:the\s+)?(.+)$/i,
1361
+ action: "click",
1362
+ targetGroup: 1,
1363
+ confidence: 0.8
1364
+ },
1365
+ // Double click patterns
1366
+ {
1367
+ regex: /^double[\s-]?click\s+(?:on\s+)?(?:the\s+)?(.+)$/i,
1368
+ action: "doubleClick",
1369
+ targetGroup: 1,
1370
+ confidence: 0.95
1371
+ },
1372
+ // Right click patterns
1373
+ {
1374
+ regex: /^right[\s-]?click\s+(?:on\s+)?(?:the\s+)?(.+)$/i,
1375
+ action: "rightClick",
1376
+ targetGroup: 1,
1377
+ confidence: 0.95
1378
+ },
1379
+ {
1380
+ regex: /^context\s+click\s+(?:on\s+)?(?:the\s+)?(.+)$/i,
1381
+ action: "rightClick",
1382
+ targetGroup: 1,
1383
+ confidence: 0.9
1384
+ },
1385
+ // Type patterns - "type X in Y"
1386
+ {
1387
+ regex: /^type\s+["'](.+?)["']\s+(?:in(?:to)?|on)\s+(?:the\s+)?(.+)$/i,
1388
+ action: "type",
1389
+ targetGroup: 2,
1390
+ valueGroup: 1,
1391
+ confidence: 0.95
1392
+ },
1393
+ {
1394
+ regex: /^type\s+(.+?)\s+(?:in(?:to)?|on)\s+(?:the\s+)?(.+)$/i,
1395
+ action: "type",
1396
+ targetGroup: 2,
1397
+ valueGroup: 1,
1398
+ confidence: 0.85
1399
+ },
1400
+ // Type patterns - "enter X in Y"
1401
+ {
1402
+ regex: /^enter\s+["'](.+?)["']\s+(?:in(?:to)?|on)\s+(?:the\s+)?(.+)$/i,
1403
+ action: "type",
1404
+ targetGroup: 2,
1405
+ valueGroup: 1,
1406
+ confidence: 0.95
1407
+ },
1408
+ {
1409
+ regex: /^enter\s+(.+?)\s+(?:in(?:to)?|on)\s+(?:the\s+)?(.+)$/i,
1410
+ action: "type",
1411
+ targetGroup: 2,
1412
+ valueGroup: 1,
1413
+ confidence: 0.85
1414
+ },
1415
+ // Type patterns - "input X into Y"
1416
+ {
1417
+ regex: /^input\s+["'](.+?)["']\s+(?:in(?:to)?)\s+(?:the\s+)?(.+)$/i,
1418
+ action: "type",
1419
+ targetGroup: 2,
1420
+ valueGroup: 1,
1421
+ confidence: 0.9
1422
+ },
1423
+ // Type patterns - "fill Y with X"
1424
+ {
1425
+ regex: /^fill\s+(?:in\s+)?(?:the\s+)?(.+?)\s+with\s+["'](.+?)["']$/i,
1426
+ action: "type",
1427
+ targetGroup: 1,
1428
+ valueGroup: 2,
1429
+ confidence: 0.95
1430
+ },
1431
+ {
1432
+ regex: /^fill\s+(?:in\s+)?(?:the\s+)?(.+?)\s+with\s+(.+)$/i,
1433
+ action: "type",
1434
+ targetGroup: 1,
1435
+ valueGroup: 2,
1436
+ confidence: 0.85
1437
+ },
1438
+ // Type patterns - "set Y to X"
1439
+ {
1440
+ regex: /^set\s+(?:the\s+)?(.+?)\s+to\s+["'](.+?)["']$/i,
1441
+ action: "type",
1442
+ targetGroup: 1,
1443
+ valueGroup: 2,
1444
+ confidence: 0.9
1445
+ },
1446
+ // Select patterns
1447
+ {
1448
+ regex: /^select\s+["'](.+?)["']\s+(?:from|in)\s+(?:the\s+)?(.+)$/i,
1449
+ action: "select",
1450
+ targetGroup: 2,
1451
+ valueGroup: 1,
1452
+ confidence: 0.95
1453
+ },
1454
+ {
1455
+ regex: /^choose\s+["'](.+?)["']\s+(?:from|in)\s+(?:the\s+)?(.+)$/i,
1456
+ action: "select",
1457
+ targetGroup: 2,
1458
+ valueGroup: 1,
1459
+ confidence: 0.9
1460
+ },
1461
+ {
1462
+ regex: /^pick\s+["'](.+?)["']\s+(?:from|in)\s+(?:the\s+)?(.+)$/i,
1463
+ action: "select",
1464
+ targetGroup: 2,
1465
+ valueGroup: 1,
1466
+ confidence: 0.85
1467
+ },
1468
+ // Check patterns
1469
+ {
1470
+ regex: /^check\s+(?:the\s+)?(.+?)(?:\s+checkbox)?$/i,
1471
+ action: "check",
1472
+ targetGroup: 1,
1473
+ confidence: 0.9
1474
+ },
1475
+ {
1476
+ regex: /^enable\s+(?:the\s+)?(.+)$/i,
1477
+ action: "check",
1478
+ targetGroup: 1,
1479
+ confidence: 0.8
1480
+ },
1481
+ {
1482
+ regex: /^tick\s+(?:the\s+)?(.+)$/i,
1483
+ action: "check",
1484
+ targetGroup: 1,
1485
+ confidence: 0.85
1486
+ },
1487
+ // Uncheck patterns
1488
+ {
1489
+ regex: /^uncheck\s+(?:the\s+)?(.+?)(?:\s+checkbox)?$/i,
1490
+ action: "uncheck",
1491
+ targetGroup: 1,
1492
+ confidence: 0.9
1493
+ },
1494
+ {
1495
+ regex: /^disable\s+(?:the\s+)?(.+)$/i,
1496
+ action: "uncheck",
1497
+ targetGroup: 1,
1498
+ confidence: 0.8
1499
+ },
1500
+ {
1501
+ regex: /^untick\s+(?:the\s+)?(.+)$/i,
1502
+ action: "uncheck",
1503
+ targetGroup: 1,
1504
+ confidence: 0.85
1505
+ },
1506
+ // Clear patterns
1507
+ {
1508
+ regex: /^clear\s+(?:the\s+)?(.+)$/i,
1509
+ action: "clear",
1510
+ targetGroup: 1,
1511
+ confidence: 0.9
1512
+ },
1513
+ {
1514
+ regex: /^erase\s+(?:the\s+)?(.+)$/i,
1515
+ action: "clear",
1516
+ targetGroup: 1,
1517
+ confidence: 0.85
1518
+ },
1519
+ {
1520
+ regex: /^empty\s+(?:the\s+)?(.+)$/i,
1521
+ action: "clear",
1522
+ targetGroup: 1,
1523
+ confidence: 0.8
1524
+ },
1525
+ // Hover patterns
1526
+ {
1527
+ regex: /^hover\s+(?:over\s+)?(?:the\s+)?(.+)$/i,
1528
+ action: "hover",
1529
+ targetGroup: 1,
1530
+ confidence: 0.9
1531
+ },
1532
+ {
1533
+ regex: /^mouse\s+over\s+(?:the\s+)?(.+)$/i,
1534
+ action: "hover",
1535
+ targetGroup: 1,
1536
+ confidence: 0.85
1537
+ },
1538
+ // Focus patterns
1539
+ {
1540
+ regex: /^focus\s+(?:on\s+)?(?:the\s+)?(.+)$/i,
1541
+ action: "focus",
1542
+ targetGroup: 1,
1543
+ confidence: 0.9
1544
+ },
1545
+ // Scroll patterns
1546
+ {
1547
+ regex: /^scroll\s+(up|down|left|right)$/i,
1548
+ action: "scroll",
1549
+ targetGroup: 1,
1550
+ confidence: 0.9
1551
+ },
1552
+ {
1553
+ regex: /^scroll\s+(?:the\s+)?(.+?)\s+(up|down|left|right)$/i,
1554
+ action: "scroll",
1555
+ targetGroup: 1,
1556
+ confidence: 0.85
1557
+ },
1558
+ {
1559
+ regex: /^scroll\s+to\s+(?:the\s+)?(.+)$/i,
1560
+ action: "scroll",
1561
+ targetGroup: 1,
1562
+ confidence: 0.85
1563
+ },
1564
+ // Wait patterns
1565
+ {
1566
+ regex: /^wait\s+(?:for\s+)?(?:the\s+)?(.+?)(?:\s+to\s+(?:be\s+)?(.+))?$/i,
1567
+ action: "wait",
1568
+ targetGroup: 1,
1569
+ confidence: 0.85
1570
+ },
1571
+ {
1572
+ regex: /^wait\s+until\s+(?:the\s+)?(.+?)(?:\s+(?:is|becomes)\s+(.+))?$/i,
1573
+ action: "wait",
1574
+ targetGroup: 1,
1575
+ confidence: 0.85
1576
+ },
1577
+ // Assert patterns
1578
+ {
1579
+ regex: /^(?:assert|verify|check)\s+(?:that\s+)?(?:the\s+)?(.+?)\s+(?:is\s+)?(visible|hidden|enabled|disabled|checked|unchecked|focused)$/i,
1580
+ action: "assert",
1581
+ targetGroup: 1,
1582
+ confidence: 0.9
1583
+ },
1584
+ {
1585
+ regex: /^(?:assert|verify|check)\s+(?:that\s+)?(?:the\s+)?(.+?)\s+(?:contains|has)\s+["'](.+?)["']$/i,
1586
+ action: "assert",
1587
+ targetGroup: 1,
1588
+ valueGroup: 2,
1589
+ confidence: 0.9
1590
+ },
1591
+ {
1592
+ regex: /^(?:the\s+)?(.+?)\s+should\s+(?:be\s+)?(visible|hidden|enabled|disabled|checked|unchecked|focused)$/i,
1593
+ action: "assert",
1594
+ targetGroup: 1,
1595
+ confidence: 0.85
1596
+ }
1597
+ ];
1598
+ var ASSERTION_TYPE_MAP = {
1599
+ visible: "visible",
1600
+ hidden: "hidden",
1601
+ enabled: "enabled",
1602
+ disabled: "disabled",
1603
+ checked: "checked",
1604
+ unchecked: "unchecked",
1605
+ focused: "focused",
1606
+ contains: "containsText",
1607
+ has: "hasText"
1608
+ };
1609
+ function parseNLInstruction(instruction) {
1610
+ const trimmed = instruction.trim();
1611
+ if (!trimmed) return null;
1612
+ for (const pattern of ACTION_PATTERNS) {
1613
+ const match = trimmed.match(pattern.regex);
1614
+ if (match) {
1615
+ const parsed = {
1616
+ action: pattern.action,
1617
+ targetDescription: cleanTargetDescription(match[pattern.targetGroup] || ""),
1618
+ rawInstruction: instruction,
1619
+ parseConfidence: pattern.confidence
1620
+ };
1621
+ if (pattern.valueGroup && match[pattern.valueGroup]) {
1622
+ parsed.value = match[pattern.valueGroup];
1623
+ }
1624
+ if (pattern.modifierExtractor) {
1625
+ parsed.modifiers = pattern.modifierExtractor(match);
1626
+ }
1627
+ if (pattern.action === "scroll") {
1628
+ const directionMatch = trimmed.match(/(up|down|left|right)/i);
1629
+ if (directionMatch) {
1630
+ parsed.scrollDirection = directionMatch[1].toLowerCase();
1631
+ }
1632
+ }
1633
+ if (pattern.action === "assert") {
1634
+ const assertMatch = trimmed.match(/(visible|hidden|enabled|disabled|checked|unchecked|focused|contains|has)/i);
1635
+ if (assertMatch) {
1636
+ parsed.assertionType = ASSERTION_TYPE_MAP[assertMatch[1].toLowerCase()];
1637
+ }
1638
+ }
1639
+ if (pattern.action === "wait") {
1640
+ const waitCondition = match[2];
1641
+ if (waitCondition) {
1642
+ parsed.waitCondition = waitCondition;
1643
+ }
1644
+ }
1645
+ return parsed;
1646
+ }
1647
+ }
1648
+ return inferAction(trimmed);
1649
+ }
1650
+ function cleanTargetDescription(target) {
1651
+ return target.trim().replace(/^(the|a|an)\s+/i, "").replace(/\s+(button|field|input|link|dropdown|checkbox|radio)$/i, "").trim();
1652
+ }
1653
+ function inferAction(instruction) {
1654
+ const lower = instruction.toLowerCase();
1655
+ if (lower.includes("click") || lower.includes("press") || lower.includes("tap")) {
1656
+ const target = instruction.replace(/click|press|tap|on|the/gi, "").trim();
1657
+ if (target) {
1658
+ return {
1659
+ action: "click",
1660
+ targetDescription: cleanTargetDescription(target),
1661
+ rawInstruction: instruction,
1662
+ parseConfidence: 0.6
1663
+ };
1664
+ }
1665
+ }
1666
+ if (lower.includes("type") || lower.includes("enter") || lower.includes("input")) {
1667
+ const quotedMatch = instruction.match(/["'](.+?)["']/);
1668
+ if (quotedMatch) {
1669
+ const target = instruction.replace(/type|enter|input|into|in|the|["'].*?["']/gi, "").trim();
1670
+ return {
1671
+ action: "type",
1672
+ targetDescription: cleanTargetDescription(target),
1673
+ value: quotedMatch[1],
1674
+ rawInstruction: instruction,
1675
+ parseConfidence: 0.5
1676
+ };
1677
+ }
1678
+ }
1679
+ return null;
1680
+ }
1681
+ function validateParsedAction(action) {
1682
+ const errors = [];
1683
+ if (!action.targetDescription && action.action !== "scroll") {
1684
+ errors.push("No target element specified");
1685
+ }
1686
+ if ((action.action === "type" || action.action === "select") && !action.value) {
1687
+ errors.push(`No value specified for ${action.action} action`);
1688
+ }
1689
+ if (action.parseConfidence < 0.5) {
1690
+ errors.push("Low confidence parsing - instruction may be ambiguous");
1691
+ }
1692
+ return {
1693
+ valid: errors.length === 0,
1694
+ errors
1695
+ };
1696
+ }
1697
+ function describeAction(action) {
1698
+ switch (action.action) {
1699
+ case "click":
1700
+ return `Click on "${action.targetDescription}"`;
1701
+ case "doubleClick":
1702
+ return `Double-click on "${action.targetDescription}"`;
1703
+ case "rightClick":
1704
+ return `Right-click on "${action.targetDescription}"`;
1705
+ case "type":
1706
+ return `Type "${action.value}" into "${action.targetDescription}"`;
1707
+ case "select":
1708
+ return `Select "${action.value}" from "${action.targetDescription}"`;
1709
+ case "check":
1710
+ return `Check "${action.targetDescription}"`;
1711
+ case "uncheck":
1712
+ return `Uncheck "${action.targetDescription}"`;
1713
+ case "clear":
1714
+ return `Clear "${action.targetDescription}"`;
1715
+ case "hover":
1716
+ return `Hover over "${action.targetDescription}"`;
1717
+ case "focus":
1718
+ return `Focus on "${action.targetDescription}"`;
1719
+ case "scroll":
1720
+ if (action.scrollDirection) {
1721
+ return `Scroll ${action.scrollDirection}`;
1722
+ }
1723
+ return `Scroll to "${action.targetDescription}"`;
1724
+ case "wait":
1725
+ return `Wait for "${action.targetDescription}"${action.waitCondition ? ` to be ${action.waitCondition}` : ""}`;
1726
+ case "assert":
1727
+ return `Assert "${action.targetDescription}" is ${action.assertionType || "valid"}`;
1728
+ default:
1729
+ return `${action.action} on "${action.targetDescription}"`;
1730
+ }
1731
+ }
1732
+
1733
+ // src/ai/error-context.ts
1734
+ function getElementState(el) {
1735
+ if ("state" in el && el.state) {
1736
+ return el.state;
1737
+ }
1738
+ if ("getState" in el && typeof el.getState === "function") {
1739
+ try {
1740
+ return el.getState();
1741
+ } catch {
1742
+ return void 0;
1743
+ }
1744
+ }
1745
+ return void 0;
1746
+ }
1747
+ var ERROR_MESSAGES = {
1748
+ PARSE_ERROR: "Could not parse the natural language instruction",
1749
+ VALIDATION_ERROR: "The parsed action failed validation",
1750
+ ELEMENT_NOT_FOUND: "No element matching the description could be found",
1751
+ ELEMENT_NOT_VISIBLE: "The element exists but is not visible",
1752
+ ELEMENT_DISABLED: "The element is disabled and cannot be interacted with",
1753
+ ELEMENT_BLOCKED: "The element is blocked by another element",
1754
+ MULTIPLE_ELEMENTS: "Multiple elements match the description",
1755
+ LOW_CONFIDENCE: "The best match has low confidence",
1756
+ AMBIGUOUS_MATCH: "Multiple elements match with similar confidence",
1757
+ ACTION_FAILED: "The action could not be completed",
1758
+ ACTION_TIMEOUT: "The action timed out waiting for a condition",
1759
+ UNSUPPORTED_ACTION: "The requested action is not supported",
1760
+ UNEXPECTED_STATE: "The element is in an unexpected state",
1761
+ STALE_ELEMENT: "The element is no longer attached to the DOM",
1762
+ PAGE_LOAD_ERROR: "The page failed to load correctly",
1763
+ NAVIGATION_ERROR: "Navigation to the target page failed"
1764
+ };
1765
+ var ERROR_SUGGESTIONS = {
1766
+ PARSE_ERROR: [
1767
+ {
1768
+ action: 'Use a simpler instruction format like "click Submit button"',
1769
+ confidence: 0.8,
1770
+ priority: 1
1771
+ },
1772
+ {
1773
+ action: "Use specific element names visible on the page",
1774
+ confidence: 0.7,
1775
+ priority: 2
1776
+ }
1777
+ ],
1778
+ VALIDATION_ERROR: [
1779
+ {
1780
+ action: "Provide required parameters for the action",
1781
+ confidence: 0.9,
1782
+ priority: 1
1783
+ },
1784
+ {
1785
+ action: "Check the instruction format",
1786
+ confidence: 0.7,
1787
+ priority: 2
1788
+ }
1789
+ ],
1790
+ ELEMENT_NOT_FOUND: [
1791
+ {
1792
+ action: "Wait for the page to fully load",
1793
+ command: "wait for page to load",
1794
+ confidence: 0.7,
1795
+ priority: 1
1796
+ },
1797
+ {
1798
+ action: "Use a different description for the element",
1799
+ confidence: 0.8,
1800
+ priority: 2
1801
+ },
1802
+ {
1803
+ action: "Scroll the page to reveal the element",
1804
+ command: "scroll down",
1805
+ confidence: 0.6,
1806
+ priority: 3
1807
+ }
1808
+ ],
1809
+ ELEMENT_NOT_VISIBLE: [
1810
+ {
1811
+ action: "Scroll to make the element visible",
1812
+ command: "scroll to element",
1813
+ confidence: 0.9,
1814
+ priority: 1
1815
+ },
1816
+ {
1817
+ action: "Close any overlaying elements",
1818
+ confidence: 0.7,
1819
+ priority: 2
1820
+ },
1821
+ {
1822
+ action: "Wait for loading to complete",
1823
+ command: "wait for loading",
1824
+ confidence: 0.6,
1825
+ priority: 3
1826
+ }
1827
+ ],
1828
+ ELEMENT_DISABLED: [
1829
+ {
1830
+ action: "Fill in required fields first",
1831
+ confidence: 0.8,
1832
+ priority: 1
1833
+ },
1834
+ {
1835
+ action: "Complete prerequisite steps",
1836
+ confidence: 0.7,
1837
+ priority: 2
1838
+ },
1839
+ {
1840
+ action: "Wait for the element to become enabled",
1841
+ command: "wait for element to be enabled",
1842
+ confidence: 0.6,
1843
+ priority: 3
1844
+ }
1845
+ ],
1846
+ ELEMENT_BLOCKED: [
1847
+ {
1848
+ action: "Close the modal or popup",
1849
+ command: "click close button",
1850
+ confidence: 0.9,
1851
+ priority: 1
1852
+ },
1853
+ {
1854
+ action: "Dismiss the overlay",
1855
+ confidence: 0.8,
1856
+ priority: 2
1857
+ },
1858
+ {
1859
+ action: "Wait for the blocking element to disappear",
1860
+ confidence: 0.6,
1861
+ priority: 3
1862
+ }
1863
+ ],
1864
+ MULTIPLE_ELEMENTS: [
1865
+ {
1866
+ action: "Use a more specific description",
1867
+ confidence: 0.9,
1868
+ priority: 1
1869
+ },
1870
+ {
1871
+ action: "Include the element position (first, second, etc.)",
1872
+ confidence: 0.8,
1873
+ priority: 2
1874
+ },
1875
+ {
1876
+ action: "Use the element ID directly",
1877
+ confidence: 0.7,
1878
+ priority: 3
1879
+ }
1880
+ ],
1881
+ LOW_CONFIDENCE: [
1882
+ {
1883
+ action: "Use the exact text shown on the element",
1884
+ confidence: 0.9,
1885
+ priority: 1
1886
+ },
1887
+ {
1888
+ action: "Lower the confidence threshold if the match is correct",
1889
+ confidence: 0.7,
1890
+ priority: 2
1891
+ },
1892
+ {
1893
+ action: "Try a different way to describe the element",
1894
+ confidence: 0.6,
1895
+ priority: 3
1896
+ }
1897
+ ],
1898
+ AMBIGUOUS_MATCH: [
1899
+ {
1900
+ action: "Be more specific about which element you mean",
1901
+ confidence: 0.9,
1902
+ priority: 1
1903
+ },
1904
+ {
1905
+ action: "Include the section or form name",
1906
+ confidence: 0.8,
1907
+ priority: 2
1908
+ }
1909
+ ],
1910
+ ACTION_FAILED: [
1911
+ {
1912
+ action: "Check if the element is interactable",
1913
+ confidence: 0.7,
1914
+ priority: 1
1915
+ },
1916
+ {
1917
+ action: "Wait and retry the action",
1918
+ command: "wait 1 second then retry",
1919
+ confidence: 0.6,
1920
+ priority: 2
1921
+ }
1922
+ ],
1923
+ ACTION_TIMEOUT: [
1924
+ {
1925
+ action: "Increase the timeout duration",
1926
+ confidence: 0.8,
1927
+ priority: 1
1928
+ },
1929
+ {
1930
+ action: "Check if the condition can ever be met",
1931
+ confidence: 0.7,
1932
+ priority: 2
1933
+ }
1934
+ ],
1935
+ UNSUPPORTED_ACTION: [
1936
+ {
1937
+ action: "Use a different action type",
1938
+ confidence: 0.9,
1939
+ priority: 1
1940
+ },
1941
+ {
1942
+ action: "Break down into simpler actions",
1943
+ confidence: 0.7,
1944
+ priority: 2
1945
+ }
1946
+ ],
1947
+ UNEXPECTED_STATE: [
1948
+ {
1949
+ action: "Refresh the page state",
1950
+ command: "refresh",
1951
+ confidence: 0.7,
1952
+ priority: 1
1953
+ },
1954
+ {
1955
+ action: "Wait for state to stabilize",
1956
+ command: "wait 2 seconds",
1957
+ confidence: 0.6,
1958
+ priority: 2
1959
+ }
1960
+ ],
1961
+ STALE_ELEMENT: [
1962
+ {
1963
+ action: "Re-find the element",
1964
+ confidence: 0.9,
1965
+ priority: 1
1966
+ },
1967
+ {
1968
+ action: "Wait for page to stabilize",
1969
+ command: "wait 1 second",
1970
+ confidence: 0.7,
1971
+ priority: 2
1972
+ }
1973
+ ],
1974
+ PAGE_LOAD_ERROR: [
1975
+ {
1976
+ action: "Refresh the page",
1977
+ command: "refresh page",
1978
+ confidence: 0.8,
1979
+ priority: 1
1980
+ },
1981
+ {
1982
+ action: "Check network connectivity",
1983
+ confidence: 0.6,
1984
+ priority: 2
1985
+ }
1986
+ ],
1987
+ NAVIGATION_ERROR: [
1988
+ {
1989
+ action: "Try the navigation again",
1990
+ confidence: 0.7,
1991
+ priority: 1
1992
+ },
1993
+ {
1994
+ action: "Check if the URL is correct",
1995
+ confidence: 0.6,
1996
+ priority: 2
1997
+ }
1998
+ ]
1999
+ };
2000
+ function createErrorContext(errorCode, attemptedAction, availableElements, searchCriteria, nearestMatch) {
2001
+ const message = ERROR_MESSAGES[errorCode] || "An unknown error occurred";
2002
+ const baseSuggestions = ERROR_SUGGESTIONS[errorCode] || [];
2003
+ const possibleBlockers = detectPossibleBlockers(availableElements);
2004
+ const visibleElements = availableElements.filter((el) => {
2005
+ const state = getElementState(el);
2006
+ return state?.visible ?? false;
2007
+ }).length;
2008
+ const suggestions = enhanceSuggestions(
2009
+ baseSuggestions,
2010
+ errorCode,
2011
+ nearestMatch,
2012
+ possibleBlockers
2013
+ );
2014
+ return {
2015
+ code: errorCode,
2016
+ message,
2017
+ attemptedAction,
2018
+ searchCriteria,
2019
+ searchResults: {
2020
+ candidatesFound: availableElements.length,
2021
+ nearestMatch: nearestMatch ? {
2022
+ element: nearestMatch.element,
2023
+ confidence: nearestMatch.confidence,
2024
+ whyNotSelected: determineWhyNotSelected(errorCode, nearestMatch)
2025
+ } : void 0
2026
+ },
2027
+ pageContext: {
2028
+ url: typeof window !== "undefined" ? window.location.href : "",
2029
+ title: typeof document !== "undefined" ? document.title : "",
2030
+ visibleElements,
2031
+ possibleBlockers
2032
+ },
2033
+ suggestions,
2034
+ timestamp: Date.now()
2035
+ };
2036
+ }
2037
+ function detectPossibleBlockers(elements) {
2038
+ const blockers = [];
2039
+ for (const el of elements) {
2040
+ const state = getElementState(el);
2041
+ if (!state) continue;
2042
+ if (el.type === "dialog" && state.visible) {
2043
+ blockers.push(`Modal dialog: ${el.id}`);
2044
+ }
2045
+ if (state.computedStyles?.pointerEvents === "none") {
2046
+ continue;
2047
+ }
2048
+ }
2049
+ return blockers;
2050
+ }
2051
+ function enhanceSuggestions(baseSuggestions, errorCode, nearestMatch, possibleBlockers) {
2052
+ const suggestions = [...baseSuggestions];
2053
+ if (possibleBlockers && possibleBlockers.length > 0) {
2054
+ suggestions.unshift({
2055
+ action: `Close the blocking element: ${possibleBlockers[0]}`,
2056
+ command: "click close button",
2057
+ confidence: 0.85,
2058
+ priority: 0
2059
+ });
2060
+ }
2061
+ if (nearestMatch && errorCode === "LOW_CONFIDENCE") {
2062
+ suggestions.unshift({
2063
+ action: `Did you mean: "${nearestMatch.element.description}"?`,
2064
+ command: `click "${nearestMatch.element.description}"`,
2065
+ confidence: nearestMatch.confidence,
2066
+ priority: 0
2067
+ });
2068
+ }
2069
+ suggestions.sort((a, b) => a.priority - b.priority);
2070
+ return suggestions;
2071
+ }
2072
+ function determineWhyNotSelected(errorCode, nearestMatch) {
2073
+ switch (errorCode) {
2074
+ case "LOW_CONFIDENCE":
2075
+ return `Confidence (${(nearestMatch.confidence * 100).toFixed(0)}%) below threshold`;
2076
+ case "ELEMENT_NOT_VISIBLE":
2077
+ return "Element is not visible";
2078
+ case "ELEMENT_DISABLED":
2079
+ return "Element is disabled";
2080
+ case "AMBIGUOUS_MATCH":
2081
+ return "Multiple elements with similar confidence";
2082
+ default:
2083
+ return "Did not meet selection criteria";
2084
+ }
2085
+ }
2086
+
2087
+ // src/ai/nl-action-executor.ts
2088
+ var DEFAULT_EXECUTOR_CONFIG = {
2089
+ defaultConfidenceThreshold: 0.7,
2090
+ defaultTimeout: 5e3,
2091
+ maxAlternatives: 3,
2092
+ verbose: false
2093
+ };
2094
+ var NLActionExecutor = class {
2095
+ constructor(config = {}) {
2096
+ this.actionExecutor = null;
2097
+ this.elements = [];
2098
+ this.config = { ...DEFAULT_EXECUTOR_CONFIG, ...config };
2099
+ this.searchEngine = new SearchEngine(this.config.searchConfig);
2100
+ }
2101
+ /**
2102
+ * Set the action executor for performing DOM actions
2103
+ */
2104
+ setActionExecutor(executor) {
2105
+ this.actionExecutor = executor;
2106
+ }
2107
+ /**
2108
+ * Update available elements for search
2109
+ */
2110
+ updateElements(elements) {
2111
+ this.elements = elements;
2112
+ this.searchEngine.updateElements(elements);
2113
+ }
2114
+ /**
2115
+ * Execute a natural language instruction
2116
+ */
2117
+ async execute(request) {
2118
+ const startTime = performance.now();
2119
+ const threshold = request.confidenceThreshold ?? this.config.defaultConfidenceThreshold;
2120
+ const parsed = parseNLInstruction(request.instruction);
2121
+ if (!parsed) {
2122
+ return this.createFailureResponse(
2123
+ startTime,
2124
+ "PARSE_ERROR",
2125
+ `Could not parse instruction: "${request.instruction}"`,
2126
+ request.instruction,
2127
+ [],
2128
+ threshold
2129
+ );
2130
+ }
2131
+ const validation = validateParsedAction(parsed);
2132
+ if (!validation.valid) {
2133
+ return this.createFailureResponse(
2134
+ startTime,
2135
+ "VALIDATION_ERROR",
2136
+ validation.errors.join("; "),
2137
+ request.instruction,
2138
+ [],
2139
+ threshold
2140
+ );
2141
+ }
2142
+ const searchCriteria = this.buildSearchCriteria(parsed);
2143
+ const searchResponse = this.searchEngine.search(searchCriteria);
2144
+ if (!searchResponse.bestMatch) {
2145
+ return this.createFailureResponse(
2146
+ startTime,
2147
+ "ELEMENT_NOT_FOUND",
2148
+ `Could not find element matching: "${parsed.targetDescription}"`,
2149
+ request.instruction,
2150
+ searchResponse.results,
2151
+ threshold,
2152
+ searchCriteria
2153
+ );
2154
+ }
2155
+ if (searchResponse.bestMatch.confidence < threshold) {
2156
+ const alternatives = searchResponse.results.slice(0, this.config.maxAlternatives);
2157
+ return this.createFailureResponse(
2158
+ startTime,
2159
+ "LOW_CONFIDENCE",
2160
+ `Best match confidence (${(searchResponse.bestMatch.confidence * 100).toFixed(0)}%) is below threshold (${(threshold * 100).toFixed(0)}%)`,
2161
+ request.instruction,
2162
+ alternatives,
2163
+ threshold,
2164
+ searchCriteria,
2165
+ searchResponse.bestMatch
2166
+ );
2167
+ }
2168
+ try {
2169
+ const result = await this.performAction(
2170
+ parsed,
2171
+ searchResponse.bestMatch.element,
2172
+ request.timeout ?? this.config.defaultTimeout
2173
+ );
2174
+ return {
2175
+ success: true,
2176
+ executedAction: describeAction(parsed),
2177
+ elementUsed: searchResponse.bestMatch.element,
2178
+ confidence: searchResponse.bestMatch.confidence,
2179
+ elementState: result.elementState,
2180
+ durationMs: performance.now() - startTime,
2181
+ timestamp: Date.now()
2182
+ };
2183
+ } catch (error2) {
2184
+ const errorMessage = error2 instanceof Error ? error2.message : String(error2);
2185
+ const alternatives = searchResponse.results.filter((r) => r !== searchResponse.bestMatch).slice(0, this.config.maxAlternatives);
2186
+ return this.createFailureResponse(
2187
+ startTime,
2188
+ "ACTION_FAILED",
2189
+ errorMessage,
2190
+ request.instruction,
2191
+ alternatives,
2192
+ threshold,
2193
+ searchCriteria,
2194
+ searchResponse.bestMatch
2195
+ );
2196
+ }
2197
+ }
2198
+ /**
2199
+ * Execute a parsed action directly (skip parsing)
2200
+ */
2201
+ async executeParsed(parsed, threshold) {
2202
+ const startTime = performance.now();
2203
+ const confidenceThreshold = threshold ?? this.config.defaultConfidenceThreshold;
2204
+ const searchCriteria = this.buildSearchCriteria(parsed);
2205
+ const searchResponse = this.searchEngine.search(searchCriteria);
2206
+ if (!searchResponse.bestMatch) {
2207
+ return this.createFailureResponse(
2208
+ startTime,
2209
+ "ELEMENT_NOT_FOUND",
2210
+ `Could not find element: "${parsed.targetDescription}"`,
2211
+ parsed.rawInstruction,
2212
+ [],
2213
+ confidenceThreshold,
2214
+ searchCriteria
2215
+ );
2216
+ }
2217
+ if (searchResponse.bestMatch.confidence < confidenceThreshold) {
2218
+ return this.createFailureResponse(
2219
+ startTime,
2220
+ "LOW_CONFIDENCE",
2221
+ `Best match confidence too low`,
2222
+ parsed.rawInstruction,
2223
+ searchResponse.results.slice(0, this.config.maxAlternatives),
2224
+ confidenceThreshold,
2225
+ searchCriteria,
2226
+ searchResponse.bestMatch
2227
+ );
2228
+ }
2229
+ try {
2230
+ const result = await this.performAction(
2231
+ parsed,
2232
+ searchResponse.bestMatch.element,
2233
+ this.config.defaultTimeout
2234
+ );
2235
+ return {
2236
+ success: true,
2237
+ executedAction: describeAction(parsed),
2238
+ elementUsed: searchResponse.bestMatch.element,
2239
+ confidence: searchResponse.bestMatch.confidence,
2240
+ elementState: result.elementState,
2241
+ durationMs: performance.now() - startTime,
2242
+ timestamp: Date.now()
2243
+ };
2244
+ } catch (error2) {
2245
+ return this.createFailureResponse(
2246
+ startTime,
2247
+ "ACTION_FAILED",
2248
+ error2 instanceof Error ? error2.message : String(error2),
2249
+ parsed.rawInstruction,
2250
+ searchResponse.results.filter((r) => r !== searchResponse.bestMatch).slice(0, this.config.maxAlternatives),
2251
+ confidenceThreshold,
2252
+ searchCriteria,
2253
+ searchResponse.bestMatch
2254
+ );
2255
+ }
2256
+ }
2257
+ /**
2258
+ * Build search criteria from a parsed action
2259
+ */
2260
+ buildSearchCriteria(parsed) {
2261
+ const criteria = {
2262
+ text: parsed.targetDescription,
2263
+ fuzzy: true,
2264
+ fuzzyThreshold: this.config.defaultConfidenceThreshold
2265
+ };
2266
+ switch (parsed.action) {
2267
+ case "click":
2268
+ case "doubleClick":
2269
+ case "rightClick":
2270
+ break;
2271
+ case "type":
2272
+ case "clear":
2273
+ criteria.type = "input";
2274
+ break;
2275
+ case "select":
2276
+ criteria.type = "select";
2277
+ break;
2278
+ case "check":
2279
+ case "uncheck":
2280
+ criteria.type = "checkbox";
2281
+ break;
2282
+ }
2283
+ return criteria;
2284
+ }
2285
+ /**
2286
+ * Perform the actual action on an element
2287
+ */
2288
+ async performAction(parsed, element, timeout) {
2289
+ if (!this.actionExecutor) {
2290
+ throw new Error("No action executor configured");
2291
+ }
2292
+ const actionMap = {
2293
+ click: "click",
2294
+ doubleClick: "doubleClick",
2295
+ rightClick: "rightClick",
2296
+ type: "type",
2297
+ select: "select",
2298
+ check: "check",
2299
+ uncheck: "uncheck",
2300
+ scroll: "scroll",
2301
+ wait: null,
2302
+ // Special handling
2303
+ assert: null,
2304
+ // Special handling
2305
+ hover: "hover",
2306
+ focus: "focus",
2307
+ clear: "clear"
2308
+ };
2309
+ const standardAction = actionMap[parsed.action];
2310
+ if (!standardAction) {
2311
+ if (parsed.action === "wait") {
2312
+ const waitResult = await this.actionExecutor.waitFor(element.id, {
2313
+ visible: true,
2314
+ timeout
2315
+ });
2316
+ if (!waitResult.met) {
2317
+ throw new Error(waitResult.error || "Wait condition not met");
2318
+ }
2319
+ return { elementState: waitResult.state };
2320
+ }
2321
+ if (parsed.action === "assert") {
2322
+ throw new Error("Use the assertions module for assert actions");
2323
+ }
2324
+ throw new Error(`Unsupported action: ${parsed.action}`);
2325
+ }
2326
+ const actionRequest = {
2327
+ action: standardAction,
2328
+ waitOptions: {
2329
+ visible: true,
2330
+ enabled: true,
2331
+ timeout
2332
+ }
2333
+ };
2334
+ if (standardAction === "type" && parsed.value) {
2335
+ actionRequest.params = { text: parsed.value };
2336
+ } else if (standardAction === "select" && parsed.value) {
2337
+ actionRequest.params = { value: parsed.value };
2338
+ } else if (standardAction === "scroll" && parsed.scrollDirection) {
2339
+ actionRequest.params = { direction: parsed.scrollDirection };
2340
+ }
2341
+ const response = await this.actionExecutor.executeAction(element.id, actionRequest);
2342
+ if (!response.success) {
2343
+ throw new Error(response.error || "Action failed");
2344
+ }
2345
+ return { elementState: response.elementState };
2346
+ }
2347
+ /**
2348
+ * Create a failure response with suggestions
2349
+ */
2350
+ createFailureResponse(startTime, errorCode, errorMessage, instruction, alternatives, threshold, searchCriteria, nearestMatch) {
2351
+ const suggestions = this.generateSuggestions(
2352
+ errorCode,
2353
+ instruction,
2354
+ alternatives,
2355
+ nearestMatch
2356
+ );
2357
+ const dummyElement = nearestMatch?.element || {
2358
+ id: "not-found",
2359
+ type: "unknown",
2360
+ tagName: "unknown",
2361
+ actions: [],
2362
+ state: {
2363
+ visible: false,
2364
+ enabled: false,
2365
+ focused: false,
2366
+ rect: { x: 0, y: 0, width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0 }
2367
+ },
2368
+ registered: false,
2369
+ description: "Element not found",
2370
+ aliases: [],
2371
+ suggestedActions: []
2372
+ };
2373
+ return {
2374
+ success: false,
2375
+ executedAction: instruction,
2376
+ elementUsed: dummyElement,
2377
+ confidence: nearestMatch?.confidence || 0,
2378
+ elementState: dummyElement.state,
2379
+ durationMs: performance.now() - startTime,
2380
+ timestamp: Date.now(),
2381
+ error: errorMessage,
2382
+ errorCode,
2383
+ suggestions,
2384
+ alternatives: alternatives.slice(0, this.config.maxAlternatives)
2385
+ };
2386
+ }
2387
+ /**
2388
+ * Generate recovery suggestions
2389
+ */
2390
+ generateSuggestions(errorCode, instruction, alternatives, nearestMatch) {
2391
+ const suggestions = [];
2392
+ switch (errorCode) {
2393
+ case "PARSE_ERROR":
2394
+ suggestions.push('Try using a simpler phrase like "click Submit button"');
2395
+ suggestions.push('Ensure the instruction follows patterns like "click X" or "type Y into X"');
2396
+ break;
2397
+ case "ELEMENT_NOT_FOUND":
2398
+ if (alternatives.length > 0) {
2399
+ suggestions.push(`Did you mean: "${alternatives[0].element.description}"?`);
2400
+ }
2401
+ suggestions.push("Check if the element is visible on the page");
2402
+ suggestions.push("Try using a more specific description");
2403
+ break;
2404
+ case "LOW_CONFIDENCE":
2405
+ if (nearestMatch) {
2406
+ suggestions.push(
2407
+ `Found "${nearestMatch.element.description}" with ${(nearestMatch.confidence * 100).toFixed(0)}% confidence`
2408
+ );
2409
+ }
2410
+ suggestions.push("Try using the exact text shown on the element");
2411
+ suggestions.push("Lower the confidence threshold if this match is correct");
2412
+ break;
2413
+ case "ACTION_FAILED":
2414
+ suggestions.push("Check if the element is enabled");
2415
+ suggestions.push("Wait for any loading to complete");
2416
+ suggestions.push("Ensure no modal or overlay is blocking the element");
2417
+ break;
2418
+ default:
2419
+ suggestions.push("Try a different approach or check the page state");
2420
+ }
2421
+ return suggestions;
2422
+ }
2423
+ /**
2424
+ * Get rich error context for debugging
2425
+ */
2426
+ getErrorContext(errorCode, instruction, searchCriteria, nearestMatch) {
2427
+ return createErrorContext(
2428
+ errorCode,
2429
+ instruction,
2430
+ this.elements,
2431
+ searchCriteria,
2432
+ nearestMatch
2433
+ );
2434
+ }
2435
+ };
2436
+
2437
+ // src/ai/assertions.ts
2438
+ var DEFAULT_ASSERTION_CONFIG = {
2439
+ defaultTimeout: 5e3,
2440
+ pollInterval: 100,
2441
+ fuzzyThreshold: 0.7,
2442
+ includeSuggestions: true
2443
+ };
2444
+ var AssertionExecutor = class {
2445
+ constructor(config = {}) {
2446
+ this.elements = [];
2447
+ this.config = { ...DEFAULT_ASSERTION_CONFIG, ...config };
2448
+ this.searchEngine = new SearchEngine({ fuzzyThreshold: this.config.fuzzyThreshold });
2449
+ }
2450
+ /**
2451
+ * Update available elements for assertions
2452
+ */
2453
+ updateElements(elements) {
2454
+ this.elements = elements;
2455
+ this.searchEngine.updateElements(elements);
2456
+ }
2457
+ /**
2458
+ * Execute a single assertion
2459
+ */
2460
+ async assert(request) {
2461
+ const startTime = performance.now();
2462
+ const timeout = request.timeout ?? this.config.defaultTimeout;
2463
+ const element = await this.findElement(request.target, request.fuzzy !== false);
2464
+ if (!element && request.type !== "notExists") {
2465
+ return this.createResult(
2466
+ false,
2467
+ typeof request.target === "string" ? request.target : JSON.stringify(request.target),
2468
+ "element not found",
2469
+ request.type === "exists" ? true : request.expected,
2470
+ null,
2471
+ "Element could not be found",
2472
+ this.config.includeSuggestions ? "Check if the element exists and is properly labeled" : void 0,
2473
+ startTime
2474
+ );
2475
+ }
2476
+ return this.executeAssertion(request, element, timeout, startTime);
2477
+ }
2478
+ /**
2479
+ * Execute multiple assertions
2480
+ */
2481
+ async assertBatch(request) {
2482
+ const startTime = performance.now();
2483
+ const results = [];
2484
+ let passedCount = 0;
2485
+ let failedCount = 0;
2486
+ for (const assertion of request.assertions) {
2487
+ const result = await this.assert(assertion);
2488
+ results.push(result);
2489
+ if (result.passed) {
2490
+ passedCount++;
2491
+ } else {
2492
+ failedCount++;
2493
+ if (request.stopOnFailure) {
2494
+ break;
2495
+ }
2496
+ }
2497
+ }
2498
+ const passed = request.mode === "all" ? failedCount === 0 : passedCount > 0;
2499
+ return {
2500
+ passed,
2501
+ results,
2502
+ passedCount,
2503
+ failedCount,
2504
+ durationMs: performance.now() - startTime,
2505
+ timestamp: Date.now()
2506
+ };
2507
+ }
2508
+ /**
2509
+ * Convenience method: assert element is visible
2510
+ */
2511
+ async assertVisible(target, timeout) {
2512
+ return this.assert({ target, type: "visible", timeout });
2513
+ }
2514
+ /**
2515
+ * Convenience method: assert element is hidden
2516
+ */
2517
+ async assertHidden(target, timeout) {
2518
+ return this.assert({ target, type: "hidden", timeout });
2519
+ }
2520
+ /**
2521
+ * Convenience method: assert element is enabled
2522
+ */
2523
+ async assertEnabled(target, timeout) {
2524
+ return this.assert({ target, type: "enabled", timeout });
2525
+ }
2526
+ /**
2527
+ * Convenience method: assert element is disabled
2528
+ */
2529
+ async assertDisabled(target, timeout) {
2530
+ return this.assert({ target, type: "disabled", timeout });
2531
+ }
2532
+ /**
2533
+ * Convenience method: assert element has text
2534
+ */
2535
+ async assertHasText(target, text, timeout) {
2536
+ return this.assert({ target, type: "hasText", expected: text, timeout });
2537
+ }
2538
+ /**
2539
+ * Convenience method: assert element contains text
2540
+ */
2541
+ async assertContainsText(target, text, timeout) {
2542
+ return this.assert({ target, type: "containsText", expected: text, timeout });
2543
+ }
2544
+ /**
2545
+ * Convenience method: assert element has value
2546
+ */
2547
+ async assertHasValue(target, value, timeout) {
2548
+ return this.assert({ target, type: "hasValue", expected: value, timeout });
2549
+ }
2550
+ /**
2551
+ * Convenience method: assert element exists
2552
+ */
2553
+ async assertExists(target, timeout) {
2554
+ return this.assert({ target, type: "exists", timeout });
2555
+ }
2556
+ /**
2557
+ * Convenience method: assert element does not exist
2558
+ */
2559
+ async assertNotExists(target, timeout) {
2560
+ return this.assert({ target, type: "notExists", timeout });
2561
+ }
2562
+ /**
2563
+ * Convenience method: assert checkbox is checked
2564
+ */
2565
+ async assertChecked(target, timeout) {
2566
+ return this.assert({ target, type: "checked", timeout });
2567
+ }
2568
+ /**
2569
+ * Convenience method: assert checkbox is unchecked
2570
+ */
2571
+ async assertUnchecked(target, timeout) {
2572
+ return this.assert({ target, type: "unchecked", timeout });
2573
+ }
2574
+ /**
2575
+ * Convenience method: assert element count
2576
+ */
2577
+ async assertCount(target, expectedCount, timeout) {
2578
+ return this.assert({ target, type: "count", expected: expectedCount, timeout });
2579
+ }
2580
+ /**
2581
+ * Find element by target (string or criteria)
2582
+ */
2583
+ async findElement(target, fuzzy = true) {
2584
+ const criteria = typeof target === "string" ? { text: target, fuzzy } : { ...target, fuzzy };
2585
+ const searchResult = this.searchEngine.findBest(criteria);
2586
+ if (searchResult && searchResult.confidence >= this.config.fuzzyThreshold) {
2587
+ return searchResult.element;
2588
+ }
2589
+ return null;
2590
+ }
2591
+ /**
2592
+ * Execute the actual assertion
2593
+ */
2594
+ async executeAssertion(request, element, timeout, startTime) {
2595
+ const targetStr = typeof request.target === "string" ? request.target : JSON.stringify(request.target);
2596
+ const elementDescription = element?.description || targetStr;
2597
+ switch (request.type) {
2598
+ case "visible":
2599
+ return this.assertVisibility(element, true, elementDescription, request.message, startTime);
2600
+ case "hidden":
2601
+ return this.assertVisibility(element, false, elementDescription, request.message, startTime);
2602
+ case "enabled":
2603
+ return this.assertEnabledState(element, true, elementDescription, request.message, startTime);
2604
+ case "disabled":
2605
+ return this.assertEnabledState(element, false, elementDescription, request.message, startTime);
2606
+ case "focused":
2607
+ return this.assertFocused(element, elementDescription, request.message, startTime);
2608
+ case "checked":
2609
+ return this.assertCheckedState(element, true, elementDescription, request.message, startTime);
2610
+ case "unchecked":
2611
+ return this.assertCheckedState(element, false, elementDescription, request.message, startTime);
2612
+ case "hasText":
2613
+ return this.assertTextMatch(
2614
+ element,
2615
+ request.expected,
2616
+ true,
2617
+ elementDescription,
2618
+ request.message,
2619
+ startTime
2620
+ );
2621
+ case "containsText":
2622
+ return this.assertTextMatch(
2623
+ element,
2624
+ request.expected,
2625
+ false,
2626
+ elementDescription,
2627
+ request.message,
2628
+ startTime
2629
+ );
2630
+ case "hasValue":
2631
+ return this.assertValue(
2632
+ element,
2633
+ request.expected,
2634
+ elementDescription,
2635
+ request.message,
2636
+ startTime
2637
+ );
2638
+ case "exists":
2639
+ return this.createResult(
2640
+ element !== null,
2641
+ targetStr,
2642
+ elementDescription,
2643
+ true,
2644
+ element !== null,
2645
+ element === null ? "Element does not exist" : void 0,
2646
+ void 0,
2647
+ startTime,
2648
+ element?.state
2649
+ );
2650
+ case "notExists":
2651
+ return this.createResult(
2652
+ element === null,
2653
+ targetStr,
2654
+ elementDescription,
2655
+ false,
2656
+ element === null,
2657
+ element !== null ? "Element exists but should not" : void 0,
2658
+ void 0,
2659
+ startTime,
2660
+ element?.state
2661
+ );
2662
+ case "count":
2663
+ return this.assertElementCount(
2664
+ request.target,
2665
+ request.expected,
2666
+ targetStr,
2667
+ request.message,
2668
+ startTime
2669
+ );
2670
+ case "attribute":
2671
+ return this.assertAttribute(
2672
+ element,
2673
+ request.attributeName,
2674
+ request.expected,
2675
+ elementDescription,
2676
+ request.message,
2677
+ startTime
2678
+ );
2679
+ case "hasClass":
2680
+ return this.assertHasClass(
2681
+ element,
2682
+ request.expected,
2683
+ elementDescription,
2684
+ request.message,
2685
+ startTime
2686
+ );
2687
+ case "cssProperty":
2688
+ return this.assertCssProperty(
2689
+ element,
2690
+ request.propertyName,
2691
+ request.expected,
2692
+ elementDescription,
2693
+ request.message,
2694
+ startTime
2695
+ );
2696
+ default:
2697
+ return this.createResult(
2698
+ false,
2699
+ targetStr,
2700
+ elementDescription,
2701
+ void 0,
2702
+ void 0,
2703
+ `Unknown assertion type: ${request.type}`,
2704
+ void 0,
2705
+ startTime
2706
+ );
2707
+ }
2708
+ }
2709
+ /**
2710
+ * Assert visibility state
2711
+ */
2712
+ assertVisibility(element, expectedVisible, description, message, startTime = performance.now()) {
2713
+ const isVisible = element.state.visible;
2714
+ const passed = isVisible === expectedVisible;
2715
+ return this.createResult(
2716
+ passed,
2717
+ element.id,
2718
+ description,
2719
+ expectedVisible,
2720
+ isVisible,
2721
+ passed ? void 0 : message || `Element is ${isVisible ? "visible" : "hidden"} but expected ${expectedVisible ? "visible" : "hidden"}`,
2722
+ passed ? void 0 : "Check if element is covered by another element or has display:none",
2723
+ startTime,
2724
+ element.state
2725
+ );
2726
+ }
2727
+ /**
2728
+ * Assert enabled state
2729
+ */
2730
+ assertEnabledState(element, expectedEnabled, description, message, startTime = performance.now()) {
2731
+ const isEnabled = element.state.enabled;
2732
+ const passed = isEnabled === expectedEnabled;
2733
+ return this.createResult(
2734
+ passed,
2735
+ element.id,
2736
+ description,
2737
+ expectedEnabled,
2738
+ isEnabled,
2739
+ passed ? void 0 : message || `Element is ${isEnabled ? "enabled" : "disabled"} but expected ${expectedEnabled ? "enabled" : "disabled"}`,
2740
+ passed ? void 0 : "Check if the element has a disabled attribute or aria-disabled",
2741
+ startTime,
2742
+ element.state
2743
+ );
2744
+ }
2745
+ /**
2746
+ * Assert focused state
2747
+ */
2748
+ assertFocused(element, description, message, startTime = performance.now()) {
2749
+ const isFocused = element.state.focused;
2750
+ return this.createResult(
2751
+ isFocused,
2752
+ element.id,
2753
+ description,
2754
+ true,
2755
+ isFocused,
2756
+ isFocused ? void 0 : message || "Element is not focused",
2757
+ isFocused ? void 0 : "Click or focus the element first",
2758
+ startTime,
2759
+ element.state
2760
+ );
2761
+ }
2762
+ /**
2763
+ * Assert checked state
2764
+ */
2765
+ assertCheckedState(element, expectedChecked, description, message, startTime = performance.now()) {
2766
+ const isChecked = element.state.checked ?? false;
2767
+ const passed = isChecked === expectedChecked;
2768
+ return this.createResult(
2769
+ passed,
2770
+ element.id,
2771
+ description,
2772
+ expectedChecked,
2773
+ isChecked,
2774
+ passed ? void 0 : message || `Element is ${isChecked ? "checked" : "unchecked"} but expected ${expectedChecked ? "checked" : "unchecked"}`,
2775
+ passed ? void 0 : "Click the checkbox to change its state",
2776
+ startTime,
2777
+ element.state
2778
+ );
2779
+ }
2780
+ /**
2781
+ * Assert text content
2782
+ */
2783
+ assertTextMatch(element, expectedText, exact, description, message, startTime = performance.now()) {
2784
+ const actualText = element.state.textContent || "";
2785
+ const passed = exact ? actualText === expectedText : actualText.includes(expectedText);
2786
+ return this.createResult(
2787
+ passed,
2788
+ element.id,
2789
+ description,
2790
+ expectedText,
2791
+ actualText,
2792
+ passed ? void 0 : message || (exact ? `Text "${actualText}" does not match expected "${expectedText}"` : `Text "${actualText}" does not contain "${expectedText}"`),
2793
+ passed ? void 0 : "Verify the element contains the expected text",
2794
+ startTime,
2795
+ element.state
2796
+ );
2797
+ }
2798
+ /**
2799
+ * Assert input value
2800
+ */
2801
+ assertValue(element, expectedValue, description, message, startTime = performance.now()) {
2802
+ const actualValue = element.state.value || "";
2803
+ const passed = actualValue === expectedValue;
2804
+ return this.createResult(
2805
+ passed,
2806
+ element.id,
2807
+ description,
2808
+ expectedValue,
2809
+ actualValue,
2810
+ passed ? void 0 : message || `Value "${actualValue}" does not match expected "${expectedValue}"`,
2811
+ passed ? void 0 : "Type the expected value into the input",
2812
+ startTime,
2813
+ element.state
2814
+ );
2815
+ }
2816
+ /**
2817
+ * Assert element count
2818
+ */
2819
+ assertElementCount(criteria, expectedCount, targetStr, message, startTime = performance.now()) {
2820
+ const searchResponse = this.searchEngine.search(criteria);
2821
+ const actualCount = searchResponse.results.length;
2822
+ const passed = actualCount === expectedCount;
2823
+ return this.createResult(
2824
+ passed,
2825
+ targetStr,
2826
+ `${actualCount} elements matching criteria`,
2827
+ expectedCount,
2828
+ actualCount,
2829
+ passed ? void 0 : message || `Found ${actualCount} elements but expected ${expectedCount}`,
2830
+ passed ? void 0 : "Adjust search criteria or wait for elements to load",
2831
+ startTime
2832
+ );
2833
+ }
2834
+ /**
2835
+ * Assert attribute value (placeholder for DOM attribute assertions)
2836
+ */
2837
+ assertAttribute(element, attributeName, expectedValue, description, message, startTime = performance.now()) {
2838
+ let actualValue;
2839
+ switch (attributeName.toLowerCase()) {
2840
+ case "placeholder":
2841
+ actualValue = element.placeholder;
2842
+ break;
2843
+ case "title":
2844
+ actualValue = element.title;
2845
+ break;
2846
+ default:
2847
+ return this.createResult(
2848
+ false,
2849
+ element.id,
2850
+ description,
2851
+ expectedValue,
2852
+ void 0,
2853
+ `Cannot check attribute "${attributeName}" without DOM access`,
2854
+ "Use the server API to check element attributes",
2855
+ startTime,
2856
+ element.state
2857
+ );
2858
+ }
2859
+ const passed = actualValue === expectedValue;
2860
+ return this.createResult(
2861
+ passed,
2862
+ element.id,
2863
+ description,
2864
+ expectedValue,
2865
+ actualValue,
2866
+ passed ? void 0 : message || `Attribute "${attributeName}" is "${actualValue}" but expected "${expectedValue}"`,
2867
+ void 0,
2868
+ startTime,
2869
+ element.state
2870
+ );
2871
+ }
2872
+ /**
2873
+ * Assert element has CSS class
2874
+ */
2875
+ assertHasClass(element, className, description, message, startTime = performance.now()) {
2876
+ return this.createResult(
2877
+ false,
2878
+ element.id,
2879
+ description,
2880
+ className,
2881
+ void 0,
2882
+ "Cannot check CSS classes without DOM access",
2883
+ "Use the server API to check element classes",
2884
+ startTime,
2885
+ element.state
2886
+ );
2887
+ }
2888
+ /**
2889
+ * Assert CSS property value
2890
+ */
2891
+ assertCssProperty(element, propertyName, expectedValue, description, message, startTime = performance.now()) {
2892
+ const computedStyles = element.state.computedStyles;
2893
+ if (!computedStyles) {
2894
+ return this.createResult(
2895
+ false,
2896
+ element.id,
2897
+ description,
2898
+ expectedValue,
2899
+ void 0,
2900
+ "Computed styles not available",
2901
+ "Request element state with computed styles",
2902
+ startTime,
2903
+ element.state
2904
+ );
2905
+ }
2906
+ const styleKey = propertyName;
2907
+ const actualValue = computedStyles[styleKey];
2908
+ const passed = actualValue === expectedValue;
2909
+ return this.createResult(
2910
+ passed,
2911
+ element.id,
2912
+ description,
2913
+ expectedValue,
2914
+ actualValue,
2915
+ passed ? void 0 : message || `CSS property "${propertyName}" is "${actualValue}" but expected "${expectedValue}"`,
2916
+ void 0,
2917
+ startTime,
2918
+ element.state
2919
+ );
2920
+ }
2921
+ /**
2922
+ * Create an assertion result
2923
+ */
2924
+ createResult(passed, target, targetDescription, expected, actual, failureReason, suggestion, startTime = performance.now(), elementState) {
2925
+ return {
2926
+ passed,
2927
+ target,
2928
+ targetDescription,
2929
+ expected,
2930
+ actual,
2931
+ failureReason,
2932
+ suggestion: this.config.includeSuggestions ? suggestion : void 0,
2933
+ elementState,
2934
+ durationMs: performance.now() - startTime,
2935
+ timestamp: Date.now()
2936
+ };
2937
+ }
2938
+ };
2939
+
2940
+ // src/ai/semantic-snapshot.ts
2941
+ var DEFAULT_SNAPSHOT_CONFIG = {
2942
+ analyzeForms: true,
2943
+ detectModals: true,
2944
+ inferPageType: true,
2945
+ generateDescriptions: true,
2946
+ maxElements: 500
2947
+ };
2948
+ var SemanticSnapshotManager = class {
2949
+ constructor(config = {}) {
2950
+ this.history = [];
2951
+ this.maxHistorySize = 10;
2952
+ this.snapshotCounter = 0;
2953
+ this.config = { ...DEFAULT_SNAPSHOT_CONFIG, ...config };
2954
+ this.searchEngine = new SearchEngine();
2955
+ }
2956
+ /**
2957
+ * Create a semantic snapshot from a control snapshot
2958
+ */
2959
+ createSnapshot(controlSnapshot, pageContext) {
2960
+ const snapshotId = `snapshot-${++this.snapshotCounter}-${Date.now()}`;
2961
+ const aiElements = this.convertElements(controlSnapshot.elements);
2962
+ this.searchEngine.updateElements(aiElements);
2963
+ const fullPageContext = this.buildPageContext(aiElements, pageContext);
2964
+ const forms = this.config.analyzeForms ? this.analyzeForms(aiElements) : [];
2965
+ const modals = this.config.detectModals ? this.detectModals(aiElements) : [];
2966
+ const elementCounts = this.countElementTypes(aiElements);
2967
+ const summary = generatePageSummary(aiElements, fullPageContext);
2968
+ const focusedElement = aiElements.find((el) => el.state.focused)?.id;
2969
+ const snapshot = {
2970
+ timestamp: Date.now(),
2971
+ snapshotId,
2972
+ page: fullPageContext,
2973
+ elements: aiElements.slice(0, this.config.maxElements),
2974
+ forms,
2975
+ activeModals: modals,
2976
+ focusedElement,
2977
+ summary,
2978
+ elementCounts
2979
+ };
2980
+ this.addToHistory(snapshot);
2981
+ return snapshot;
2982
+ }
2983
+ /**
2984
+ * Get the last snapshot
2985
+ */
2986
+ getLastSnapshot() {
2987
+ if (this.history.length === 0) return null;
2988
+ return this.history[this.history.length - 1].snapshot;
2989
+ }
2990
+ /**
2991
+ * Get snapshot by ID
2992
+ */
2993
+ getSnapshot(snapshotId) {
2994
+ const entry = this.history.find((h) => h.snapshot.snapshotId === snapshotId);
2995
+ return entry?.snapshot || null;
2996
+ }
2997
+ /**
2998
+ * Get snapshot history
2999
+ */
3000
+ getHistory() {
3001
+ return this.history.map((h) => h.snapshot);
3002
+ }
3003
+ /**
3004
+ * Clear history
3005
+ */
3006
+ clearHistory() {
3007
+ this.history = [];
3008
+ }
3009
+ /**
3010
+ * Convert control snapshot elements to AI elements
3011
+ */
3012
+ convertElements(elements) {
3013
+ return elements.map((el) => this.convertElement(el));
3014
+ }
3015
+ /**
3016
+ * Convert a single element to AI element
3017
+ */
3018
+ convertElement(element) {
3019
+ const aliases = generateAliases({
3020
+ textContent: element.state.textContent,
3021
+ elementType: element.type,
3022
+ id: element.id,
3023
+ labelText: element.label
3024
+ });
3025
+ const description = this.config.generateDescriptions ? generateDescription({
3026
+ textContent: element.state.textContent,
3027
+ elementType: element.type,
3028
+ id: element.id,
3029
+ labelText: element.label
3030
+ }) : element.label || element.id;
3031
+ const purpose = generatePurpose({
3032
+ textContent: element.state.textContent,
3033
+ elementType: element.type
3034
+ });
3035
+ const suggestedActions = generateSuggestedActions({
3036
+ textContent: element.state.textContent,
3037
+ elementType: element.type
3038
+ });
3039
+ return {
3040
+ id: element.id,
3041
+ type: element.type,
3042
+ label: element.label,
3043
+ tagName: this.inferTagName(element.type),
3044
+ role: this.inferRole(element.type),
3045
+ accessibleName: element.label || element.state.textContent?.trim(),
3046
+ actions: element.actions,
3047
+ state: element.state,
3048
+ registered: true,
3049
+ description,
3050
+ aliases,
3051
+ purpose,
3052
+ suggestedActions,
3053
+ semanticType: this.inferSemanticType(element)
3054
+ };
3055
+ }
3056
+ /**
3057
+ * Build full page context
3058
+ */
3059
+ buildPageContext(elements, partial) {
3060
+ const url = partial?.url || (typeof window !== "undefined" ? window.location.href : "");
3061
+ const title = partial?.title || (typeof document !== "undefined" ? document.title : "");
3062
+ const pageType = this.config.inferPageType ? inferPageType(url, title, elements) : partial?.pageType || "unknown";
3063
+ const activeModals = elements.filter((el) => el.type === "dialog" && el.state.visible).map((el) => el.id);
3064
+ return {
3065
+ url,
3066
+ title,
3067
+ pageType,
3068
+ activeModals: partial?.activeModals || activeModals,
3069
+ focusedElement: partial?.focusedElement || elements.find((el) => el.state.focused)?.id,
3070
+ navigation: partial?.navigation
3071
+ };
3072
+ }
3073
+ /**
3074
+ * Analyze forms in the snapshot
3075
+ */
3076
+ analyzeForms(elements) {
3077
+ const forms = [];
3078
+ const formElements = elements.filter((el) => el.type === "form");
3079
+ if (formElements.length === 0) {
3080
+ const implicitForm = this.detectImplicitForm(elements);
3081
+ if (implicitForm) {
3082
+ forms.push(implicitForm);
3083
+ }
3084
+ } else {
3085
+ for (const form of formElements) {
3086
+ const formState = this.analyzeForm(form, elements);
3087
+ if (formState) {
3088
+ forms.push(formState);
3089
+ }
3090
+ }
3091
+ }
3092
+ return forms;
3093
+ }
3094
+ /**
3095
+ * Detect implicit form from inputs
3096
+ */
3097
+ detectImplicitForm(elements) {
3098
+ const inputs = elements.filter(
3099
+ (el) => el.type === "input" || el.type === "textarea" || el.type === "select" || el.type === "checkbox"
3100
+ );
3101
+ if (inputs.length === 0) return null;
3102
+ const submitButton = elements.find(
3103
+ (el) => el.type === "button" && el.state.visible && (el.semanticType === "submit-button" || el.state.textContent?.toLowerCase().match(/submit|save|send|continue/))
3104
+ );
3105
+ const fields = this.analyzeFormFields(inputs);
3106
+ const hasErrors = fields.some((f) => !f.valid);
3107
+ return {
3108
+ id: "implicit-form",
3109
+ purpose: this.inferFormPurpose(inputs),
3110
+ fields,
3111
+ isValid: !hasErrors,
3112
+ submitButton: submitButton?.id,
3113
+ isDirty: fields.some((f) => f.value !== "" && f.touched)
3114
+ };
3115
+ }
3116
+ /**
3117
+ * Analyze a specific form
3118
+ */
3119
+ analyzeForm(form, allElements) {
3120
+ const inputs = allElements.filter(
3121
+ (el) => (el.type === "input" || el.type === "textarea" || el.type === "select") && el.state.visible
3122
+ );
3123
+ const fields = this.analyzeFormFields(inputs);
3124
+ const hasErrors = fields.some((f) => !f.valid);
3125
+ const submitButton = allElements.find(
3126
+ (el) => el.type === "button" && el.state.visible && el.semanticType === "submit-button"
3127
+ );
3128
+ return {
3129
+ id: form.id,
3130
+ name: form.label,
3131
+ purpose: form.purpose,
3132
+ fields,
3133
+ isValid: !hasErrors,
3134
+ submitButton: submitButton?.id,
3135
+ isDirty: fields.some((f) => f.value !== "")
3136
+ };
3137
+ }
3138
+ /**
3139
+ * Analyze form fields
3140
+ */
3141
+ analyzeFormFields(inputs) {
3142
+ return inputs.map((input) => ({
3143
+ id: input.id,
3144
+ label: input.accessibleName || input.label || input.id,
3145
+ type: input.type,
3146
+ value: input.state.value || "",
3147
+ valid: true,
3148
+ // Would need validation state
3149
+ required: false,
3150
+ // Would need DOM access
3151
+ touched: input.state.focused || (input.state.value?.length || 0) > 0
3152
+ }));
3153
+ }
3154
+ /**
3155
+ * Detect modal dialogs
3156
+ */
3157
+ detectModals(elements) {
3158
+ const modals = [];
3159
+ const dialogElements = elements.filter(
3160
+ (el) => el.type === "dialog" && el.state.visible
3161
+ );
3162
+ for (const dialog of dialogElements) {
3163
+ const closeButton = elements.find(
3164
+ (el) => el.type === "button" && el.state.visible && (el.semanticType === "cancel-button" || el.state.textContent?.toLowerCase().match(/close|cancel|x|dismiss/))
3165
+ );
3166
+ const primaryAction = elements.find(
3167
+ (el) => el.type === "button" && el.state.visible && el.semanticType === "submit-button"
3168
+ );
3169
+ modals.push({
3170
+ id: dialog.id,
3171
+ title: dialog.accessibleName || dialog.label,
3172
+ type: this.inferModalType(dialog),
3173
+ blocking: true,
3174
+ // Assume dialogs are blocking
3175
+ closeButton: closeButton?.id,
3176
+ primaryAction: primaryAction?.id
3177
+ });
3178
+ }
3179
+ return modals;
3180
+ }
3181
+ /**
3182
+ * Infer modal type
3183
+ */
3184
+ inferModalType(dialog) {
3185
+ const text = (dialog.accessibleName || dialog.state.textContent || "").toLowerCase();
3186
+ if (text.includes("alert") || text.includes("warning") || text.includes("error")) {
3187
+ return "alert";
3188
+ }
3189
+ if (text.includes("confirm") || text.includes("are you sure")) {
3190
+ return "confirm";
3191
+ }
3192
+ if (text.includes("prompt") || text.includes("enter")) {
3193
+ return "prompt";
3194
+ }
3195
+ return "dialog";
3196
+ }
3197
+ /**
3198
+ * Count elements by type
3199
+ */
3200
+ countElementTypes(elements) {
3201
+ const counts = {};
3202
+ for (const el of elements) {
3203
+ const type = el.type.toLowerCase();
3204
+ counts[type] = (counts[type] || 0) + 1;
3205
+ }
3206
+ return counts;
3207
+ }
3208
+ /**
3209
+ * Infer form purpose from fields
3210
+ */
3211
+ inferFormPurpose(fields) {
3212
+ const labels = fields.map(
3213
+ (f) => (f.accessibleName || f.label || "").toLowerCase()
3214
+ );
3215
+ const allLabels = labels.join(" ");
3216
+ if (allLabels.includes("email") && allLabels.includes("password")) {
3217
+ if (allLabels.includes("confirm") || allLabels.includes("name")) {
3218
+ return "Registration";
3219
+ }
3220
+ return "Login";
3221
+ }
3222
+ if (allLabels.includes("search")) return "Search";
3223
+ if (allLabels.includes("address") || allLabels.includes("city")) return "Address";
3224
+ if (allLabels.includes("card") || allLabels.includes("payment")) return "Payment";
3225
+ if (allLabels.includes("contact") || allLabels.includes("message")) return "Contact";
3226
+ return "Form";
3227
+ }
3228
+ /**
3229
+ * Infer tag name from element type
3230
+ */
3231
+ inferTagName(type) {
3232
+ const typeMap = {
3233
+ button: "button",
3234
+ input: "input",
3235
+ textarea: "textarea",
3236
+ select: "select",
3237
+ checkbox: "input",
3238
+ radio: "input",
3239
+ link: "a",
3240
+ form: "form",
3241
+ dialog: "dialog"
3242
+ };
3243
+ return typeMap[type] || "div";
3244
+ }
3245
+ /**
3246
+ * Infer ARIA role from element type
3247
+ */
3248
+ inferRole(type) {
3249
+ const roleMap = {
3250
+ button: "button",
3251
+ input: "textbox",
3252
+ textarea: "textbox",
3253
+ select: "combobox",
3254
+ checkbox: "checkbox",
3255
+ radio: "radio",
3256
+ link: "link",
3257
+ dialog: "dialog",
3258
+ menu: "menu",
3259
+ menuitem: "menuitem",
3260
+ tab: "tab"
3261
+ };
3262
+ return roleMap[type];
3263
+ }
3264
+ /**
3265
+ * Infer semantic type
3266
+ */
3267
+ inferSemanticType(element) {
3268
+ const text = (element.state.textContent || element.label || "").toLowerCase();
3269
+ const type = element.type.toLowerCase();
3270
+ if (type === "button") {
3271
+ if (text.match(/submit|save|confirm|ok|done|apply/)) return "submit-button";
3272
+ if (text.match(/cancel|close|dismiss/)) return "cancel-button";
3273
+ if (text.match(/delete|remove|trash/)) return "delete-button";
3274
+ if (text.match(/add|create|new|\+/)) return "add-button";
3275
+ if (text.match(/edit|modify/)) return "edit-button";
3276
+ if (text.match(/next|continue/)) return "next-button";
3277
+ if (text.match(/back|previous/)) return "back-button";
3278
+ return "action-button";
3279
+ }
3280
+ if (type === "input") {
3281
+ if (text.includes("email") || element.id.includes("email")) return "email-input";
3282
+ if (text.includes("password") || element.id.includes("password")) return "password-input";
3283
+ if (text.includes("search") || element.id.includes("search")) return "search-input";
3284
+ return "text-input";
3285
+ }
3286
+ return type;
3287
+ }
3288
+ /**
3289
+ * Add snapshot to history
3290
+ */
3291
+ addToHistory(snapshot) {
3292
+ this.history.push({
3293
+ snapshot,
3294
+ timestamp: Date.now()
3295
+ });
3296
+ if (this.history.length > this.maxHistorySize) {
3297
+ this.history = this.history.slice(-this.maxHistorySize);
3298
+ }
3299
+ }
3300
+ };
3301
+
3302
+ // src/ai/semantic-diff.ts
3303
+ var DEFAULT_DIFF_CONFIG = {
3304
+ ignoreInsignificant: true,
3305
+ trackedProperties: ["visible", "enabled", "focused", "checked", "value", "textContent"],
3306
+ generateSuggestions: true,
3307
+ maxModifications: 20
3308
+ };
3309
+ var INSIGNIFICANT_PROPERTIES = /* @__PURE__ */ new Set(["rect", "computedStyles", "innerHTML"]);
3310
+ function computeDiff(fromSnapshot, toSnapshot, config = {}) {
3311
+ const startTime = performance.now();
3312
+ const finalConfig = { ...DEFAULT_DIFF_CONFIG, ...config };
3313
+ const fromElements = new Map(fromSnapshot.elements.map((el) => [el.id, el]));
3314
+ const toElements = new Map(toSnapshot.elements.map((el) => [el.id, el]));
3315
+ const appeared = [];
3316
+ for (const [id, element] of toElements) {
3317
+ if (!fromElements.has(id)) {
3318
+ appeared.push({
3319
+ elementId: id,
3320
+ description: element.description,
3321
+ type: element.type,
3322
+ semanticType: element.semanticType
3323
+ });
3324
+ }
3325
+ }
3326
+ const disappeared = [];
3327
+ for (const [id, element] of fromElements) {
3328
+ if (!toElements.has(id)) {
3329
+ disappeared.push({
3330
+ elementId: id,
3331
+ description: element.description,
3332
+ type: element.type,
3333
+ semanticType: element.semanticType
3334
+ });
3335
+ }
3336
+ }
3337
+ const modified = [];
3338
+ for (const [id, toElement] of toElements) {
3339
+ const fromElement = fromElements.get(id);
3340
+ if (fromElement) {
3341
+ const modifications = compareElements(fromElement, toElement, finalConfig);
3342
+ modified.push(...modifications);
3343
+ }
3344
+ }
3345
+ const limitedModifications = modified.slice(0, finalConfig.maxModifications);
3346
+ const probableTrigger = detectTrigger(appeared, disappeared, limitedModifications);
3347
+ const suggestedActions = finalConfig.generateSuggestions ? generateSuggestedActionsFromDiff(appeared, disappeared, limitedModifications, probableTrigger) : void 0;
3348
+ const pageChanges = detectPageChanges(fromSnapshot, toSnapshot);
3349
+ const summary = generateDiffSummary(
3350
+ appeared.map((e) => e.description),
3351
+ disappeared.map((e) => e.description),
3352
+ limitedModifications
3353
+ );
3354
+ return {
3355
+ summary,
3356
+ fromSnapshotId: fromSnapshot.snapshotId,
3357
+ toSnapshotId: toSnapshot.snapshotId,
3358
+ changes: {
3359
+ appeared,
3360
+ disappeared,
3361
+ modified: limitedModifications
3362
+ },
3363
+ probableTrigger,
3364
+ suggestedActions,
3365
+ pageChanges,
3366
+ durationMs: performance.now() - startTime,
3367
+ timestamp: Date.now()
3368
+ };
3369
+ }
3370
+ function compareElements(fromElement, toElement, config) {
3371
+ const modifications = [];
3372
+ for (const property of config.trackedProperties) {
3373
+ const fromValue = getPropertyValue(fromElement, property);
3374
+ const toValue = getPropertyValue(toElement, property);
3375
+ if (fromValue !== toValue) {
3376
+ const isSignificant = isSignificantChange(property, fromValue, toValue);
3377
+ if (!config.ignoreInsignificant || isSignificant) {
3378
+ modifications.push({
3379
+ elementId: toElement.id,
3380
+ description: toElement.description,
3381
+ property,
3382
+ from: formatValue(fromValue),
3383
+ to: formatValue(toValue),
3384
+ significant: isSignificant
3385
+ });
3386
+ }
3387
+ }
3388
+ }
3389
+ return modifications;
3390
+ }
3391
+ function getPropertyValue(element, property) {
3392
+ if (property in element.state) {
3393
+ return element.state[property];
3394
+ }
3395
+ return element[property];
3396
+ }
3397
+ function isSignificantChange(property, fromValue, toValue) {
3398
+ if (INSIGNIFICANT_PROPERTIES.has(property)) {
3399
+ return false;
3400
+ }
3401
+ if (property === "visible") {
3402
+ return true;
3403
+ }
3404
+ if (property === "enabled") {
3405
+ return true;
3406
+ }
3407
+ if (property === "focused") {
3408
+ return true;
3409
+ }
3410
+ if (property === "checked") {
3411
+ return true;
3412
+ }
3413
+ if (property === "value") {
3414
+ return Boolean(fromValue) || Boolean(toValue);
3415
+ }
3416
+ if (property === "textContent") {
3417
+ const fromText = String(fromValue || "");
3418
+ const toText = String(toValue || "");
3419
+ return fromText.trim() !== toText.trim();
3420
+ }
3421
+ return true;
3422
+ }
3423
+ function formatValue(value) {
3424
+ if (value === void 0) return "undefined";
3425
+ if (value === null) return "null";
3426
+ if (typeof value === "boolean") return value ? "true" : "false";
3427
+ if (typeof value === "string") {
3428
+ if (value.length > 50) {
3429
+ return value.substring(0, 47) + "...";
3430
+ }
3431
+ return value;
3432
+ }
3433
+ if (typeof value === "object") {
3434
+ return JSON.stringify(value);
3435
+ }
3436
+ return String(value);
3437
+ }
3438
+ function detectTrigger(appeared, disappeared, modified) {
3439
+ const hasNewErrors = appeared.some(
3440
+ (e) => e.description.toLowerCase().includes("error") || e.type === "error"
3441
+ );
3442
+ if (hasNewErrors) {
3443
+ return "Form validation";
3444
+ }
3445
+ const hasNewModal = appeared.some(
3446
+ (e) => e.type === "dialog" || e.semanticType?.includes("dialog")
3447
+ );
3448
+ if (hasNewModal) {
3449
+ return "Modal opened";
3450
+ }
3451
+ const hasModalDismissed = disappeared.some(
3452
+ (e) => e.type === "dialog" || e.semanticType?.includes("dialog")
3453
+ );
3454
+ if (hasModalDismissed) {
3455
+ return "Modal closed";
3456
+ }
3457
+ const hasLoading = modified.some((m) => m.description.toLowerCase().includes("loading"));
3458
+ if (hasLoading) {
3459
+ return "Loading state change";
3460
+ }
3461
+ const hasFocusChange = modified.some((m) => m.property === "focused");
3462
+ if (hasFocusChange && modified.length <= 2) {
3463
+ return "Focus changed";
3464
+ }
3465
+ const hasValueChange = modified.some((m) => m.property === "value");
3466
+ if (hasValueChange && modified.length <= 2) {
3467
+ return "User input";
3468
+ }
3469
+ const visibilityChanges = modified.filter((m) => m.property === "visible");
3470
+ if (visibilityChanges.length > 0 && visibilityChanges.length <= 5) {
3471
+ return "UI expansion/collapse";
3472
+ }
3473
+ if (appeared.length > 5) {
3474
+ return "Page navigation";
3475
+ }
3476
+ return void 0;
3477
+ }
3478
+ function detectPageChanges(fromSnapshot, toSnapshot) {
3479
+ const urlChanged = fromSnapshot.page.url !== toSnapshot.page.url;
3480
+ const titleChanged = fromSnapshot.page.title !== toSnapshot.page.title;
3481
+ if (!urlChanged && !titleChanged) {
3482
+ return void 0;
3483
+ }
3484
+ return {
3485
+ urlChanged,
3486
+ titleChanged,
3487
+ newUrl: urlChanged ? toSnapshot.page.url : void 0,
3488
+ newTitle: titleChanged ? toSnapshot.page.title : void 0
3489
+ };
3490
+ }
3491
+ function generateSuggestedActionsFromDiff(appeared, disappeared, modified, trigger) {
3492
+ const suggestions = [];
3493
+ if (trigger === "Form validation") {
3494
+ suggestions.push("Fix the validation errors before submitting");
3495
+ }
3496
+ if (trigger === "Modal opened") {
3497
+ const modal = appeared.find(
3498
+ (e) => e.type === "dialog" || e.semanticType?.includes("dialog")
3499
+ );
3500
+ if (modal) {
3501
+ suggestions.push(`Interact with the "${modal.description}" dialog`);
3502
+ }
3503
+ }
3504
+ if (trigger === "Modal closed") {
3505
+ suggestions.push("Continue with the main page interaction");
3506
+ }
3507
+ for (const element of appeared.slice(0, 3)) {
3508
+ if (element.type === "button" && element.semanticType === "submit-button") {
3509
+ suggestions.push(`Click the "${element.description}" to proceed`);
3510
+ }
3511
+ if (element.description.toLowerCase().includes("error")) {
3512
+ suggestions.push(`Address the error: ${element.description}`);
3513
+ }
3514
+ }
3515
+ for (const mod of modified.slice(0, 3)) {
3516
+ if (mod.property === "enabled" && mod.to === "true") {
3517
+ suggestions.push(`"${mod.description}" is now enabled`);
3518
+ }
3519
+ if (mod.property === "visible" && mod.to === "true") {
3520
+ suggestions.push(`"${mod.description}" is now visible`);
3521
+ }
3522
+ }
3523
+ return suggestions.slice(0, 5);
3524
+ }
3525
+ var SemanticDiffManager = class {
3526
+ constructor(config = {}) {
3527
+ this.lastSnapshot = null;
3528
+ this.config = { ...DEFAULT_DIFF_CONFIG, ...config };
3529
+ }
3530
+ /**
3531
+ * Update with new snapshot and get diff
3532
+ */
3533
+ update(newSnapshot) {
3534
+ if (!this.lastSnapshot) {
3535
+ this.lastSnapshot = newSnapshot;
3536
+ return null;
3537
+ }
3538
+ const diff = computeDiff(this.lastSnapshot, newSnapshot, this.config);
3539
+ this.lastSnapshot = newSnapshot;
3540
+ return diff;
3541
+ }
3542
+ /**
3543
+ * Get diff from a specific snapshot to current
3544
+ */
3545
+ diffFrom(fromSnapshot) {
3546
+ if (!this.lastSnapshot) return null;
3547
+ return computeDiff(fromSnapshot, this.lastSnapshot, this.config);
3548
+ }
3549
+ /**
3550
+ * Reset the manager
3551
+ */
3552
+ reset() {
3553
+ this.lastSnapshot = null;
3554
+ }
3555
+ /**
3556
+ * Get the last known snapshot
3557
+ */
3558
+ getLastSnapshot() {
3559
+ return this.lastSnapshot;
3560
+ }
3561
+ };
3562
+
3563
+ // src/server/handlers.ts
3564
+ function success(data) {
3565
+ return {
3566
+ success: true,
3567
+ data,
3568
+ timestamp: Date.now()
3569
+ };
3570
+ }
3571
+ function error(message, code) {
3572
+ return {
3573
+ success: false,
3574
+ error: message,
3575
+ code,
3576
+ timestamp: Date.now()
3577
+ };
3578
+ }
3579
+ function getRecoverySuggestions(errorCode) {
3580
+ switch (errorCode) {
3581
+ case "ELEMENT_NOT_FOUND":
3582
+ return [
3583
+ { suggestion: "Wait for the page to fully load", command: "wait for page to load", confidence: 0.7, retryable: true },
3584
+ { suggestion: "Use a different description for the element", confidence: 0.8, retryable: false },
3585
+ { suggestion: "Scroll the page to reveal the element", command: "scroll down", confidence: 0.6, retryable: true }
3586
+ ];
3587
+ case "ELEMENT_NOT_VISIBLE":
3588
+ return [
3589
+ { suggestion: "Scroll to make the element visible", command: "scroll to element", confidence: 0.9, retryable: true },
3590
+ { suggestion: "Wait for any loading overlays to disappear", confidence: 0.7, retryable: true },
3591
+ { suggestion: "Close any blocking modals or popups", command: "click close button", confidence: 0.8, retryable: true }
3592
+ ];
3593
+ case "ELEMENT_NOT_ENABLED":
3594
+ return [
3595
+ { suggestion: "Fill in required fields first", confidence: 0.8, retryable: false },
3596
+ { suggestion: "Complete prerequisite steps in the form", confidence: 0.7, retryable: false },
3597
+ { suggestion: "Wait for the element to become enabled", command: "wait for element to be enabled", confidence: 0.6, retryable: true }
3598
+ ];
3599
+ case "ELEMENT_NOT_INTERACTABLE":
3600
+ return [
3601
+ { suggestion: "Close any modal or popup blocking the element", command: "click close button", confidence: 0.9, retryable: true },
3602
+ { suggestion: "Wait for animations to complete", confidence: 0.7, retryable: true },
3603
+ { suggestion: "Scroll the element into the viewport", command: "scroll to element", confidence: 0.8, retryable: true }
3604
+ ];
3605
+ case "ACTION_TIMEOUT":
3606
+ return [
3607
+ { suggestion: "Increase the timeout duration", confidence: 0.8, retryable: true },
3608
+ { suggestion: "Check if the condition can ever be met", confidence: 0.7, retryable: false },
3609
+ { suggestion: "Verify the page is responding", command: "check page status", confidence: 0.6, retryable: true }
3610
+ ];
3611
+ case "LOW_CONFIDENCE":
3612
+ return [
3613
+ { suggestion: "Use the exact text shown on the element", confidence: 0.9, retryable: false },
3614
+ { suggestion: "Try a different description that more closely matches the element", confidence: 0.8, retryable: false },
3615
+ { suggestion: "Lower the confidence threshold if the match is correct", confidence: 0.7, retryable: true }
3616
+ ];
3617
+ case "AMBIGUOUS_MATCH":
3618
+ return [
3619
+ { suggestion: "Be more specific about which element you mean", confidence: 0.9, retryable: false },
3620
+ { suggestion: "Include the section or form name in the description", confidence: 0.8, retryable: false },
3621
+ { suggestion: "Use the element ID directly", confidence: 0.7, retryable: false }
3622
+ ];
3623
+ default:
3624
+ return [
3625
+ { suggestion: "Try a different approach or check the page state", confidence: 0.5, retryable: false }
3626
+ ];
3627
+ }
3628
+ }
3629
+ function createFailureDetails(errorCode, message, options = {}) {
3630
+ const retryableErrors = [
3631
+ "ELEMENT_NOT_VISIBLE",
3632
+ "ACTION_TIMEOUT",
3633
+ "LOW_CONFIDENCE",
3634
+ "NETWORK_ERROR",
3635
+ "STATE_NOT_REACHED"
3636
+ ];
3637
+ return {
3638
+ errorCode,
3639
+ message,
3640
+ elementId: options.elementId,
3641
+ selectorsTried: options.selectorsTried,
3642
+ suggestedActions: getRecoverySuggestions(errorCode),
3643
+ retryRecommended: retryableErrors.includes(errorCode),
3644
+ durationMs: options.durationMs,
3645
+ timeoutMs: options.timeoutMs
3646
+ };
3647
+ }
3648
+ function createHandlers(registry, actionExecutor, config = {}) {
3649
+ const searchEngine = new SearchEngine();
3650
+ const nlExecutor = new NLActionExecutor();
3651
+ const assertionExecutor = new AssertionExecutor();
3652
+ const snapshotManager = new SemanticSnapshotManager();
3653
+ const diffManager = new SemanticDiffManager();
3654
+ function refreshElements() {
3655
+ const elements = registry.getAllElements();
3656
+ searchEngine.updateElements(elements);
3657
+ nlExecutor.updateElements(elements);
3658
+ nlExecutor.setActionExecutor(actionExecutor);
3659
+ assertionExecutor.updateElements(elements);
3660
+ }
3661
+ return {
3662
+ // =========================================================================
3663
+ // Render Log Handlers
3664
+ // =========================================================================
3665
+ getRenderLog: async (query) => {
3666
+ try {
3667
+ const entries = registry.getRenderLog?.() ?? [];
3668
+ let filtered = entries;
3669
+ if (query?.type) {
3670
+ filtered = filtered.filter((e) => e.type === query.type);
3671
+ }
3672
+ if (query?.since) {
3673
+ filtered = filtered.filter((e) => e.timestamp >= query.since);
3674
+ }
3675
+ if (query?.until) {
3676
+ filtered = filtered.filter((e) => e.timestamp <= query.until);
3677
+ }
3678
+ if (query?.limit) {
3679
+ filtered = filtered.slice(0, query.limit);
3680
+ }
3681
+ return success(filtered);
3682
+ } catch (err) {
3683
+ return error(err.message, "RENDER_LOG_ERROR");
3684
+ }
3685
+ },
3686
+ clearRenderLog: async () => {
3687
+ try {
3688
+ registry.clearRenderLog?.();
3689
+ return success(void 0);
3690
+ } catch (err) {
3691
+ return error(err.message, "RENDER_LOG_ERROR");
3692
+ }
3693
+ },
3694
+ captureSnapshot: async () => {
3695
+ try {
3696
+ const snapshot = registry.captureSnapshot?.();
3697
+ return success(snapshot);
3698
+ } catch (err) {
3699
+ return error(err.message, "SNAPSHOT_ERROR");
3700
+ }
3701
+ },
3702
+ getRenderLogPath: async () => {
3703
+ return success({ path: config.renderLogPath || "" });
3704
+ },
3705
+ // =========================================================================
3706
+ // Element Handlers
3707
+ // =========================================================================
3708
+ getElements: async () => {
3709
+ try {
3710
+ const elements = registry.getAllElements();
3711
+ return success(elements);
3712
+ } catch (err) {
3713
+ return error(err.message, "ELEMENTS_ERROR");
3714
+ }
3715
+ },
3716
+ getElement: async (id) => {
3717
+ try {
3718
+ const element = registry.getElement(id);
3719
+ if (!element) {
3720
+ const failureDetails = createFailureDetails("ELEMENT_NOT_FOUND", `Element not found: ${id}`, {
3721
+ elementId: id,
3722
+ selectorsTried: [id]
3723
+ });
3724
+ return {
3725
+ success: false,
3726
+ error: `Element not found: ${id}`,
3727
+ code: "ELEMENT_NOT_FOUND",
3728
+ data: { failureDetails },
3729
+ timestamp: Date.now()
3730
+ };
3731
+ }
3732
+ return success(element);
3733
+ } catch (err) {
3734
+ return error(err.message, "ELEMENT_ERROR");
3735
+ }
3736
+ },
3737
+ getElementState: async (id) => {
3738
+ try {
3739
+ const element = registry.getElement(id);
3740
+ if (!element) {
3741
+ return error(`Element not found: ${id}`, "NOT_FOUND");
3742
+ }
3743
+ return success(element.state);
3744
+ } catch (err) {
3745
+ return error(err.message, "ELEMENT_STATE_ERROR");
3746
+ }
3747
+ },
3748
+ executeElementAction: async (id, request) => {
3749
+ const startTime = Date.now();
3750
+ try {
3751
+ const element = registry.getElement(id);
3752
+ if (!element) {
3753
+ const failureDetails = createFailureDetails("ELEMENT_NOT_FOUND", `Element not found: ${id}`, {
3754
+ elementId: id,
3755
+ selectorsTried: [id],
3756
+ durationMs: Date.now() - startTime
3757
+ });
3758
+ return {
3759
+ success: false,
3760
+ error: `Element not found: ${id}`,
3761
+ code: "ELEMENT_NOT_FOUND",
3762
+ data: {
3763
+ success: false,
3764
+ error: `Element not found: ${id}`,
3765
+ failureDetails,
3766
+ durationMs: Date.now() - startTime,
3767
+ timestamp: Date.now()
3768
+ },
3769
+ timestamp: Date.now()
3770
+ };
3771
+ }
3772
+ const result = await actionExecutor.executeAction(id, {
3773
+ action: request.action,
3774
+ params: request.params,
3775
+ waitOptions: request.waitOptions
3776
+ });
3777
+ if (result && typeof result === "object" && "success" in result && !result.success) {
3778
+ const actionResult = result;
3779
+ let errorCode = "UNKNOWN_ERROR";
3780
+ const errorMsg = actionResult.error?.toLowerCase() || "";
3781
+ if (errorMsg.includes("not found")) {
3782
+ errorCode = "ELEMENT_NOT_FOUND";
3783
+ } else if (errorMsg.includes("not visible") || errorMsg.includes("hidden")) {
3784
+ errorCode = "ELEMENT_NOT_VISIBLE";
3785
+ } else if (errorMsg.includes("disabled") || errorMsg.includes("not enabled")) {
3786
+ errorCode = "ELEMENT_NOT_ENABLED";
3787
+ } else if (errorMsg.includes("timeout")) {
3788
+ errorCode = "ACTION_TIMEOUT";
3789
+ } else if (errorMsg.includes("blocked") || errorMsg.includes("interactable")) {
3790
+ errorCode = "ELEMENT_NOT_INTERACTABLE";
3791
+ }
3792
+ const failureDetails = createFailureDetails(errorCode, actionResult.error || "Action failed", {
3793
+ elementId: id,
3794
+ durationMs: Date.now() - startTime
3795
+ });
3796
+ return success({
3797
+ ...actionResult,
3798
+ failureDetails
3799
+ });
3800
+ }
3801
+ return success(result);
3802
+ } catch (err) {
3803
+ const errorMessage = err.message;
3804
+ let errorCode = "UNKNOWN_ERROR";
3805
+ if (errorMessage.includes("not found")) {
3806
+ errorCode = "ELEMENT_NOT_FOUND";
3807
+ } else if (errorMessage.includes("timeout")) {
3808
+ errorCode = "ACTION_TIMEOUT";
3809
+ } else if (errorMessage.includes("network") || errorMessage.includes("fetch")) {
3810
+ errorCode = "NETWORK_ERROR";
3811
+ }
3812
+ const failureDetails = createFailureDetails(errorCode, errorMessage, {
3813
+ elementId: id,
3814
+ durationMs: Date.now() - startTime
3815
+ });
3816
+ return {
3817
+ success: false,
3818
+ error: errorMessage,
3819
+ code: errorCode,
3820
+ data: {
3821
+ success: false,
3822
+ error: errorMessage,
3823
+ failureDetails,
3824
+ durationMs: Date.now() - startTime,
3825
+ timestamp: Date.now()
3826
+ },
3827
+ timestamp: Date.now()
3828
+ };
3829
+ }
3830
+ },
3831
+ // =========================================================================
3832
+ // Component Handlers
3833
+ // =========================================================================
3834
+ getComponents: async () => {
3835
+ try {
3836
+ const components = registry.getAllComponents();
3837
+ return success(components);
3838
+ } catch (err) {
3839
+ return error(err.message, "COMPONENTS_ERROR");
3840
+ }
3841
+ },
3842
+ getComponent: async (id) => {
3843
+ try {
3844
+ const component = registry.getComponent(id);
3845
+ if (!component) {
3846
+ return error(`Component not found: ${id}`, "NOT_FOUND");
3847
+ }
3848
+ return success(component);
3849
+ } catch (err) {
3850
+ return error(err.message, "COMPONENT_ERROR");
3851
+ }
3852
+ },
3853
+ getComponentState: async (id) => {
3854
+ try {
3855
+ const component = registry.getComponent(id);
3856
+ if (!component) {
3857
+ return error(`Component not found: ${id}`, "NOT_FOUND");
3858
+ }
3859
+ if (registry.getComponentState) {
3860
+ const stateResponse = registry.getComponentState(id);
3861
+ if (!stateResponse) {
3862
+ return error(`Component not found or not mounted: ${id}`, "NOT_FOUND");
3863
+ }
3864
+ return success(stateResponse);
3865
+ }
3866
+ const comp = component;
3867
+ return success({
3868
+ state: comp.getState?.() ?? {},
3869
+ computed: comp.getComputed?.() ?? {},
3870
+ timestamp: Date.now()
3871
+ });
3872
+ } catch (err) {
3873
+ return error(err.message, "COMPONENT_STATE_ERROR");
3874
+ }
3875
+ },
3876
+ executeComponentAction: async (id, request) => {
3877
+ try {
3878
+ const result = await actionExecutor.executeComponentAction(id, {
3879
+ action: request.action,
3880
+ params: request.params
3881
+ });
3882
+ return success(result);
3883
+ } catch (err) {
3884
+ return error(err.message, "COMPONENT_ACTION_ERROR");
3885
+ }
3886
+ },
3887
+ // =========================================================================
3888
+ // Find/Discovery Handlers
3889
+ // =========================================================================
3890
+ find: async (request) => {
3891
+ try {
3892
+ const findRequest = request;
3893
+ const elements = registry.findElements?.(findRequest) ?? registry.getAllElements();
3894
+ return success({ elements, timestamp: Date.now(), total: elements.length, durationMs: 0 });
3895
+ } catch (err) {
3896
+ return error(err.message, "FIND_ERROR");
3897
+ }
3898
+ },
3899
+ discover: async (request) => {
3900
+ try {
3901
+ const findRequest = request;
3902
+ const elements = registry.findElements?.(findRequest) ?? registry.getAllElements();
3903
+ return success({ elements, timestamp: Date.now(), total: elements.length, durationMs: 0 });
3904
+ } catch (err) {
3905
+ return error(err.message, "DISCOVER_ERROR");
3906
+ }
3907
+ },
3908
+ getControlSnapshot: async () => {
3909
+ try {
3910
+ const snapshot = registry.createSnapshot();
3911
+ return success(snapshot);
3912
+ } catch (err) {
3913
+ return error(err.message, "SNAPSHOT_ERROR");
3914
+ }
3915
+ },
3916
+ // =========================================================================
3917
+ // Workflow Handlers
3918
+ // =========================================================================
3919
+ getWorkflows: async () => {
3920
+ try {
3921
+ const workflows = registry.getAllWorkflows?.() ?? [];
3922
+ return success(workflows);
3923
+ } catch (err) {
3924
+ return error(err.message, "WORKFLOWS_ERROR");
3925
+ }
3926
+ },
3927
+ runWorkflow: async (id, _request) => {
3928
+ try {
3929
+ const workflow = registry.getWorkflow?.(id);
3930
+ if (!workflow) {
3931
+ return error(`Workflow not found: ${id}`, "NOT_FOUND");
3932
+ }
3933
+ const runId = `run-${Date.now()}`;
3934
+ return success({
3935
+ runId,
3936
+ workflowId: id,
3937
+ status: "pending",
3938
+ startedAt: Date.now(),
3939
+ steps: [],
3940
+ totalSteps: 0
3941
+ });
3942
+ } catch (err) {
3943
+ return error(err.message, "WORKFLOW_ERROR");
3944
+ }
3945
+ },
3946
+ getWorkflowStatus: async (runId) => {
3947
+ try {
3948
+ return success({
3949
+ runId,
3950
+ workflowId: "",
3951
+ status: "completed",
3952
+ completedAt: Date.now(),
3953
+ startedAt: Date.now(),
3954
+ steps: [],
3955
+ totalSteps: 0
3956
+ });
3957
+ } catch (err) {
3958
+ return error(err.message, "WORKFLOW_STATUS_ERROR");
3959
+ }
3960
+ },
3961
+ // =========================================================================
3962
+ // Debug Handlers
3963
+ // =========================================================================
3964
+ getActionHistory: async (limit) => {
3965
+ try {
3966
+ const history = registry.getActionHistory?.() ?? [];
3967
+ const limited = limit ? history.slice(-limit) : history;
3968
+ return success(limited);
3969
+ } catch (err) {
3970
+ return error(err.message, "ACTION_HISTORY_ERROR");
3971
+ }
3972
+ },
3973
+ getMetrics: async () => {
3974
+ try {
3975
+ const metrics = registry.getMetrics?.() ?? {
3976
+ elementCount: registry.getAllElements().length,
3977
+ componentCount: registry.getAllComponents().length
3978
+ };
3979
+ return success(metrics);
3980
+ } catch (err) {
3981
+ return error(err.message, "METRICS_ERROR");
3982
+ }
3983
+ },
3984
+ highlightElement: async (id) => {
3985
+ try {
3986
+ registry.highlightElement?.(id);
3987
+ return success(void 0);
3988
+ } catch (err) {
3989
+ return error(err.message, "HIGHLIGHT_ERROR");
3990
+ }
3991
+ },
3992
+ getElementTree: async () => {
3993
+ try {
3994
+ const tree = registry.getElementTree?.() ?? { root: null, elements: [] };
3995
+ return success(tree);
3996
+ } catch (err) {
3997
+ return error(err.message, "ELEMENT_TREE_ERROR");
3998
+ }
3999
+ },
4000
+ // =========================================================================
4001
+ // AI-Native Handlers
4002
+ // =========================================================================
4003
+ aiSearch: async (criteria) => {
4004
+ try {
4005
+ refreshElements();
4006
+ const response = searchEngine.search(criteria);
4007
+ return success(response);
4008
+ } catch (err) {
4009
+ return error(err.message, "AI_SEARCH_ERROR");
4010
+ }
4011
+ },
4012
+ aiExecute: async (request) => {
4013
+ try {
4014
+ refreshElements();
4015
+ const response = await nlExecutor.execute(request);
4016
+ return success(response);
4017
+ } catch (err) {
4018
+ return error(err.message, "AI_EXECUTE_ERROR");
4019
+ }
4020
+ },
4021
+ aiAssert: async (request) => {
4022
+ try {
4023
+ refreshElements();
4024
+ const result = await assertionExecutor.assert(request);
4025
+ return success(result);
4026
+ } catch (err) {
4027
+ return error(err.message, "AI_ASSERT_ERROR");
4028
+ }
4029
+ },
4030
+ aiAssertBatch: async (request) => {
4031
+ try {
4032
+ refreshElements();
4033
+ const result = await assertionExecutor.assertBatch(request);
4034
+ return success(result);
4035
+ } catch (err) {
4036
+ return error(err.message, "AI_ASSERT_BATCH_ERROR");
4037
+ }
4038
+ },
4039
+ getSemanticSnapshot: async () => {
4040
+ try {
4041
+ const controlSnapshot = registry.createSnapshot();
4042
+ const snapshot = snapshotManager.createSnapshot(controlSnapshot);
4043
+ return success(snapshot);
4044
+ } catch (err) {
4045
+ return error(err.message, "SEMANTIC_SNAPSHOT_ERROR");
4046
+ }
4047
+ },
4048
+ getSemanticDiff: async (_since) => {
4049
+ try {
4050
+ const controlSnapshot = registry.createSnapshot();
4051
+ const currentSnapshot = snapshotManager.createSnapshot(controlSnapshot);
4052
+ const diff = diffManager.update(currentSnapshot);
4053
+ return success(diff);
4054
+ } catch (err) {
4055
+ return error(err.message, "SEMANTIC_DIFF_ERROR");
4056
+ }
4057
+ },
4058
+ getPageSummary: async () => {
4059
+ try {
4060
+ const snapshot = registry.createSnapshot();
4061
+ const elements = snapshot.elements.map((el) => ({
4062
+ ...el,
4063
+ description: el.label || el.id,
4064
+ aliases: [],
4065
+ suggestedActions: [],
4066
+ tagName: el.type,
4067
+ accessibleName: el.label,
4068
+ registered: true
4069
+ }));
4070
+ const summary = generatePageSummary(elements);
4071
+ return success(summary);
4072
+ } catch (err) {
4073
+ return error(err.message, "PAGE_SUMMARY_ERROR");
4074
+ }
4075
+ },
4076
+ // =========================================================================
4077
+ // Semantic Search Handler (Embedding-based)
4078
+ // =========================================================================
4079
+ aiSemanticSearch: async (criteria) => {
4080
+ const startTime = performance.now();
4081
+ try {
4082
+ refreshElements();
4083
+ const allElements = registry.getAllElements();
4084
+ const aiElements = allElements.map((el) => {
4085
+ const textParts = [];
4086
+ const state = "getState" in el ? el.getState() : el.state;
4087
+ const textContent = state?.textContent || "";
4088
+ const label = el.label || "";
4089
+ const accessibleName = el.accessibleName || "";
4090
+ const placeholder = el.placeholder || "";
4091
+ const title = el.title || "";
4092
+ if (label) textParts.push(label);
4093
+ if (accessibleName && accessibleName !== label) textParts.push(accessibleName);
4094
+ if (textContent && textContent !== label && textContent !== accessibleName) {
4095
+ textParts.push(textContent);
4096
+ }
4097
+ if (placeholder) textParts.push(`placeholder: ${placeholder}`);
4098
+ if (title) textParts.push(title);
4099
+ const combinedText = textParts.join(" ").trim() || el.id;
4100
+ return {
4101
+ element: {
4102
+ id: el.id,
4103
+ type: el.type,
4104
+ label: el.label,
4105
+ tagName: el.tagName || el.type,
4106
+ role: el.role,
4107
+ accessibleName: el.accessibleName,
4108
+ actions: el.actions || [],
4109
+ state: state || {},
4110
+ registered: true,
4111
+ description: label || el.id,
4112
+ aliases: [],
4113
+ suggestedActions: []
4114
+ },
4115
+ text: combinedText
4116
+ };
4117
+ });
4118
+ let filteredElements = aiElements;
4119
+ if (criteria.type) {
4120
+ filteredElements = filteredElements.filter(
4121
+ ({ element }) => element.type.toLowerCase() === criteria.type.toLowerCase()
4122
+ );
4123
+ }
4124
+ if (criteria.role) {
4125
+ filteredElements = filteredElements.filter(
4126
+ ({ element }) => element.role?.toLowerCase() === criteria.role.toLowerCase()
4127
+ );
4128
+ }
4129
+ const query = criteria.query.toLowerCase();
4130
+ const threshold = criteria.threshold ?? 0.5;
4131
+ const limit = criteria.limit ?? 10;
4132
+ const scoredResults = filteredElements.map(({ element, text }) => {
4133
+ const textLower = text.toLowerCase();
4134
+ let similarity = 0;
4135
+ if (textLower.includes(query)) {
4136
+ similarity = 0.9;
4137
+ } else {
4138
+ const queryWords = new Set(query.split(/\s+/).filter((w) => w.length > 2));
4139
+ const textWords = new Set(textLower.split(/\s+/).filter((w) => w.length > 2));
4140
+ if (queryWords.size > 0 && textWords.size > 0) {
4141
+ let matchCount = 0;
4142
+ for (const word of queryWords) {
4143
+ for (const textWord of textWords) {
4144
+ if (textWord.includes(word) || word.includes(textWord)) {
4145
+ matchCount++;
4146
+ break;
4147
+ }
4148
+ }
4149
+ }
4150
+ similarity = matchCount / queryWords.size * 0.7;
4151
+ }
4152
+ }
4153
+ return {
4154
+ element,
4155
+ similarity,
4156
+ rank: 0,
4157
+ // Will be set after sorting
4158
+ embeddedText: text
4159
+ };
4160
+ });
4161
+ const filteredResults = scoredResults.filter((r) => r.similarity >= threshold).sort((a, b) => b.similarity - a.similarity).slice(0, limit);
4162
+ filteredResults.forEach((result, index) => {
4163
+ result.rank = index + 1;
4164
+ });
4165
+ const response = {
4166
+ results: filteredResults,
4167
+ bestMatch: filteredResults.length > 0 ? filteredResults[0] : null,
4168
+ scannedCount: filteredElements.length,
4169
+ durationMs: performance.now() - startTime,
4170
+ query: criteria.query,
4171
+ providerInfo: {
4172
+ provider: "text-fallback",
4173
+ model: "simple-similarity",
4174
+ dimension: 0
4175
+ },
4176
+ timestamp: Date.now()
4177
+ };
4178
+ return success(response);
4179
+ } catch (err) {
4180
+ return error(err.message, "AI_SEMANTIC_SEARCH_ERROR");
4181
+ }
4182
+ }
4183
+ };
4184
+ }
4185
+ function createAIHandlers(registry, actionExecutor) {
4186
+ const searchEngine = new SearchEngine();
4187
+ const nlExecutor = new NLActionExecutor();
4188
+ const assertionExecutor = new AssertionExecutor();
4189
+ const snapshotManager = new SemanticSnapshotManager();
4190
+ const diffManager = new SemanticDiffManager();
4191
+ function refreshElements() {
4192
+ const elements = registry.getAllElements();
4193
+ searchEngine.updateElements(elements);
4194
+ nlExecutor.updateElements(elements);
4195
+ nlExecutor.setActionExecutor(actionExecutor);
4196
+ assertionExecutor.updateElements(elements);
4197
+ }
4198
+ return {
4199
+ aiSearch: async (criteria) => {
4200
+ try {
4201
+ refreshElements();
4202
+ const response = searchEngine.search(criteria);
4203
+ return { success: true, data: response, timestamp: Date.now() };
4204
+ } catch (err) {
4205
+ return { success: false, error: err.message, code: "AI_SEARCH_ERROR", timestamp: Date.now() };
4206
+ }
4207
+ },
4208
+ aiExecute: async (request) => {
4209
+ try {
4210
+ refreshElements();
4211
+ const response = await nlExecutor.execute(request);
4212
+ return { success: true, data: response, timestamp: Date.now() };
4213
+ } catch (err) {
4214
+ return { success: false, error: err.message, code: "AI_EXECUTE_ERROR", timestamp: Date.now() };
4215
+ }
4216
+ },
4217
+ aiAssert: async (request) => {
4218
+ try {
4219
+ refreshElements();
4220
+ const result = await assertionExecutor.assert(request);
4221
+ return { success: true, data: result, timestamp: Date.now() };
4222
+ } catch (err) {
4223
+ return { success: false, error: err.message, code: "AI_ASSERT_ERROR", timestamp: Date.now() };
4224
+ }
4225
+ },
4226
+ aiAssertBatch: async (request) => {
4227
+ try {
4228
+ refreshElements();
4229
+ const result = await assertionExecutor.assertBatch(request);
4230
+ return { success: true, data: result, timestamp: Date.now() };
4231
+ } catch (err) {
4232
+ return { success: false, error: err.message, code: "AI_ASSERT_BATCH_ERROR", timestamp: Date.now() };
4233
+ }
4234
+ },
4235
+ getSemanticSnapshot: async () => {
4236
+ try {
4237
+ const controlSnapshot = registry.createSnapshot();
4238
+ const snapshot = snapshotManager.createSnapshot(controlSnapshot);
4239
+ return { success: true, data: snapshot, timestamp: Date.now() };
4240
+ } catch (err) {
4241
+ return { success: false, error: err.message, code: "SEMANTIC_SNAPSHOT_ERROR", timestamp: Date.now() };
4242
+ }
4243
+ },
4244
+ getSemanticDiff: async (_since) => {
4245
+ try {
4246
+ const controlSnapshot = registry.createSnapshot();
4247
+ const currentSnapshot = snapshotManager.createSnapshot(controlSnapshot);
4248
+ const diff = diffManager.update(currentSnapshot);
4249
+ return { success: true, data: diff, timestamp: Date.now() };
4250
+ } catch (err) {
4251
+ return { success: false, error: err.message, code: "SEMANTIC_DIFF_ERROR", timestamp: Date.now() };
4252
+ }
4253
+ },
4254
+ getPageSummary: async () => {
4255
+ try {
4256
+ const snapshot = registry.createSnapshot();
4257
+ const elements = snapshot.elements.map((el) => ({
4258
+ ...el,
4259
+ description: el.label || el.id,
4260
+ aliases: [],
4261
+ suggestedActions: [],
4262
+ tagName: el.type,
4263
+ accessibleName: el.label,
4264
+ registered: true
4265
+ }));
4266
+ const summary = generatePageSummary(elements);
4267
+ return { success: true, data: summary, timestamp: Date.now() };
4268
+ } catch (err) {
4269
+ return { success: false, error: err.message, code: "PAGE_SUMMARY_ERROR", timestamp: Date.now() };
4270
+ }
4271
+ }
4272
+ };
4273
+ }
4274
+
4275
+ exports.createAIHandlers = createAIHandlers;
4276
+ exports.createHandlers = createHandlers;
4277
+ //# sourceMappingURL=handlers.js.map
4278
+ //# sourceMappingURL=handlers.js.map