@thinkingos/vsl-sdk 0.4.4 → 1.1.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.
package/dist/index.js CHANGED
@@ -27,10 +27,14 @@ __export(index_exports, {
27
27
  ActionExecutionError: () => ActionExecutionError,
28
28
  AlibabaAdapter: () => AlibabaAdapter,
29
29
  AnthropicAdapter: () => AnthropicAdapter,
30
+ IdGenerator: () => IdGenerator,
30
31
  LEVEL1_TAG_MAP: () => LEVEL1_TAG_MAP,
31
32
  LlmError: () => LlmError,
32
33
  LlmValidationError: () => LlmValidationError,
33
34
  OpenAIAdapter: () => OpenAIAdapter,
35
+ PromptInjectionFilter: () => PromptInjectionFilter,
36
+ RemotePatternsLoader: () => RemotePatternsLoader,
37
+ TYPE_ABBREVIATIONS: () => TYPE_ABBREVIATIONS,
34
38
  VALID_ACTIONS: () => VALID_ACTIONS,
35
39
  VSL_SDK_VERSION: () => VSL_SDK_VERSION,
36
40
  VSL_VERSION: () => VSL_VERSION,
@@ -43,9 +47,12 @@ __export(index_exports, {
43
47
  computeContentHash: () => computeContentHash,
44
48
  computeCoordHash: () => computeCoordHash,
45
49
  createCacheStore: () => createCacheStore,
50
+ createFilter: () => createFilter,
51
+ createIdGenerator: () => createIdGenerator,
46
52
  diffVslDocuments: () => diffVslDocuments,
47
53
  executeAction: () => executeAction,
48
54
  extractDomTree: () => extractDomTree,
55
+ getGlobalFilter: () => getGlobalFilter,
49
56
  isAriaHidden: () => isAriaHidden,
50
57
  isVslDiff: () => isVslDiff,
51
58
  ownText: () => ownText,
@@ -59,6 +66,597 @@ __export(index_exports, {
59
66
  });
60
67
  module.exports = __toCommonJS(index_exports);
61
68
 
69
+ // src/utils/idGenerator.ts
70
+ var TYPE_ABBREVIATIONS = {
71
+ button: "btn",
72
+ input: "inp",
73
+ link: "link",
74
+ container: "cont",
75
+ image: "img",
76
+ select: "sel",
77
+ textarea: "txt",
78
+ heading: "h",
79
+ text: "txt",
80
+ file_input: "file",
81
+ modal: "modal",
82
+ tab: "tab",
83
+ dropdown_toggle: "dropdown",
84
+ footer: "footer",
85
+ scrollable_container: "scroll",
86
+ toolbar: "toolbar",
87
+ list: "list",
88
+ grid: "grid",
89
+ form_field: "form",
90
+ tab_bar: "tabs",
91
+ layout: "layout",
92
+ nav: "nav",
93
+ header: "header",
94
+ main: "main",
95
+ icon: "icon",
96
+ chart: "chart",
97
+ custom_widget: "widget",
98
+ unknown: "el"
99
+ };
100
+ var IdGenerator = class {
101
+ typeCounters = /* @__PURE__ */ new Map();
102
+ /**
103
+ * Generate ID for an element.
104
+ *
105
+ * @param elementId - Existing DOM id attribute (if present)
106
+ * @param vslType - VSL type of the element (for fallback abbreviation)
107
+ * @returns Generated ID string
108
+ */
109
+ generate(elementId2, vslType) {
110
+ if (elementId2 && elementId2.trim().length > 0) {
111
+ return elementId2;
112
+ }
113
+ const shortType = vslType ? TYPE_ABBREVIATIONS[vslType] || "el" : "el";
114
+ const count = (this.typeCounters.get(shortType) || 0) + 1;
115
+ this.typeCounters.set(shortType, count);
116
+ return `${shortType}_${count}`;
117
+ }
118
+ /** Reset counters (for testing or new snapshot). */
119
+ reset() {
120
+ this.typeCounters.clear();
121
+ }
122
+ /** Get current counter value for a type (for debugging). */
123
+ getCounter(type) {
124
+ if (type) {
125
+ return this.typeCounters.get(type) || 0;
126
+ }
127
+ let total = 0;
128
+ for (const count of this.typeCounters.values()) {
129
+ total += count;
130
+ }
131
+ return total;
132
+ }
133
+ };
134
+ function createIdGenerator() {
135
+ return new IdGenerator();
136
+ }
137
+
138
+ // src/security/promptInjectionFilter.ts
139
+ var import_node_fs2 = require("fs");
140
+ var import_node_path2 = require("path");
141
+ var import_node_os2 = require("os");
142
+
143
+ // src/security/remotePatternsLoader.ts
144
+ var import_node_fs = require("fs");
145
+ var import_node_path = require("path");
146
+ var import_node_os = require("os");
147
+ var DEFAULT_REMOTE_URL = "https://raw.githubusercontent.com/MaxZhadobin/visual-scene-language/vsl_mcp/patterns/latest.json";
148
+ var DEFAULT_CACHE_PATH = (0, import_node_path.join)((0, import_node_os.homedir)(), ".vsl", "cache", "remote-patterns.json");
149
+ var DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
150
+ var DEFAULT_TIMEOUT_MS = 5e3;
151
+ var RemotePatternsLoader = class {
152
+ remoteUrl;
153
+ cachePath;
154
+ cacheTtlMs;
155
+ enabled;
156
+ timeoutMs;
157
+ constructor(options = {}) {
158
+ this.remoteUrl = options.remoteUrl ?? DEFAULT_REMOTE_URL;
159
+ this.cachePath = options.cachePath ?? DEFAULT_CACHE_PATH;
160
+ this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
161
+ this.enabled = options.enabled ?? true;
162
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
163
+ }
164
+ /**
165
+ * Загружает remote patterns с fallback на кэш.
166
+ *
167
+ * Логика:
168
+ * 1. Если enabled === false — возвращаем empty
169
+ * 2. Проверяем кэш — если валиден (TTL не истёк), используем кэш
170
+ * 3. Пытаемся загрузить с remote URL
171
+ * 4. Если загрузка успешна — обновляем кэш, возвращаем remote patterns
172
+ * 5. Если загрузка не удалась — используем кэш (fallback)
173
+ * 6. Если кэша нет — возвращаем empty
174
+ */
175
+ async load() {
176
+ if (!this.enabled) {
177
+ return { patterns: [], source: "none", fallbackUsed: false };
178
+ }
179
+ const cachedData = this.readCache();
180
+ const cacheIsValid = this.isCacheValid(cachedData);
181
+ if (cacheIsValid) {
182
+ return {
183
+ patterns: cachedData.patterns,
184
+ source: "cache",
185
+ fallbackUsed: false
186
+ };
187
+ }
188
+ try {
189
+ const patterns = await this.fetchRemotePatterns();
190
+ this.writeCache(patterns);
191
+ return {
192
+ patterns,
193
+ source: "remote",
194
+ fallbackUsed: false
195
+ };
196
+ } catch (error) {
197
+ const errorMessage = error instanceof Error ? error.message : String(error);
198
+ if (cachedData) {
199
+ return {
200
+ patterns: cachedData.patterns,
201
+ source: "cache",
202
+ fallbackUsed: true,
203
+ error: errorMessage
204
+ };
205
+ }
206
+ return {
207
+ patterns: [],
208
+ source: "none",
209
+ fallbackUsed: true,
210
+ error: errorMessage
211
+ };
212
+ }
213
+ }
214
+ /**
215
+ * Загружает паттерны с remote URL.
216
+ */
217
+ async fetchRemotePatterns() {
218
+ const controller = new AbortController();
219
+ const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
220
+ try {
221
+ const response = await fetch(this.remoteUrl, {
222
+ signal: controller.signal,
223
+ headers: {
224
+ "Accept": "application/json",
225
+ "User-Agent": "VSL-Patterns-Loader/1.0"
226
+ }
227
+ });
228
+ if (!response.ok) {
229
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
230
+ }
231
+ const data = await response.json();
232
+ let patternsArray;
233
+ if (Array.isArray(data)) {
234
+ patternsArray = data;
235
+ } else if (typeof data === "object" && data !== null && Array.isArray(data.patterns)) {
236
+ patternsArray = data.patterns;
237
+ } else {
238
+ throw new Error("Invalid patterns format: expected array or { patterns: [] }");
239
+ }
240
+ const patterns = [];
241
+ for (const item of patternsArray) {
242
+ if (this.isValidPattern(item)) {
243
+ patterns.push(item);
244
+ }
245
+ }
246
+ return patterns;
247
+ } finally {
248
+ clearTimeout(timeoutId);
249
+ }
250
+ }
251
+ /**
252
+ * Проверяет валидность паттерна.
253
+ */
254
+ isValidPattern(item) {
255
+ if (typeof item !== "object" || item === null) return false;
256
+ const obj = item;
257
+ return typeof obj.id === "string" && typeof obj.pattern === "string" && typeof obj.type === "string" && (obj.type === "regex" || obj.type === "ml") && typeof obj.severity === "string" && ["low", "medium", "high", "critical"].includes(obj.severity) && typeof obj.action === "string" && ["strip", "log", "block"].includes(obj.action) && typeof obj.description === "string";
258
+ }
259
+ /**
260
+ * Читает кэш из файла.
261
+ */
262
+ readCache() {
263
+ if (!(0, import_node_fs.existsSync)(this.cachePath)) {
264
+ return null;
265
+ }
266
+ try {
267
+ const content = (0, import_node_fs.readFileSync)(this.cachePath, "utf-8");
268
+ const data = JSON.parse(content);
269
+ if (!data.metadata || !Array.isArray(data.patterns)) {
270
+ return null;
271
+ }
272
+ return data;
273
+ } catch {
274
+ return null;
275
+ }
276
+ }
277
+ /**
278
+ * Записывает паттерны в кэш.
279
+ */
280
+ writeCache(patterns) {
281
+ const cacheData = {
282
+ metadata: {
283
+ lastFetched: (/* @__PURE__ */ new Date()).toISOString(),
284
+ sourceUrl: this.remoteUrl,
285
+ patternCount: patterns.length
286
+ },
287
+ patterns
288
+ };
289
+ try {
290
+ const cacheDir = (0, import_node_path.dirname)(this.cachePath);
291
+ if (!(0, import_node_fs.existsSync)(cacheDir)) {
292
+ (0, import_node_fs.mkdirSync)(cacheDir, { recursive: true });
293
+ }
294
+ (0, import_node_fs.writeFileSync)(this.cachePath, JSON.stringify(cacheData, null, 2), "utf-8");
295
+ } catch (error) {
296
+ console.error(`[RemotePatternsLoader] Failed to write cache: ${error}`);
297
+ }
298
+ }
299
+ /**
300
+ * Проверяет валидность кэша (TTL не истёк).
301
+ */
302
+ isCacheValid(cachedData) {
303
+ if (!cachedData) return false;
304
+ try {
305
+ const lastFetched = new Date(cachedData.metadata.lastFetched).getTime();
306
+ const now = Date.now();
307
+ return now - lastFetched < this.cacheTtlMs;
308
+ } catch {
309
+ return false;
310
+ }
311
+ }
312
+ /**
313
+ * Очищает кэш (для тестирования).
314
+ */
315
+ clearCache() {
316
+ try {
317
+ if ((0, import_node_fs.existsSync)(this.cachePath)) {
318
+ (0, import_node_fs.writeFileSync)(this.cachePath, "", "utf-8");
319
+ }
320
+ } catch (error) {
321
+ console.error(`[RemotePatternsLoader] Failed to clear cache: ${error}`);
322
+ }
323
+ }
324
+ /**
325
+ * Получает информацию о кэше (для диагностики).
326
+ */
327
+ getCacheInfo() {
328
+ const cachedData = this.readCache();
329
+ if (!cachedData) {
330
+ return { exists: false };
331
+ }
332
+ return {
333
+ exists: true,
334
+ lastFetched: cachedData.metadata.lastFetched,
335
+ patternCount: cachedData.metadata.patternCount
336
+ };
337
+ }
338
+ /**
339
+ * Получает URL для загрузки remote patterns.
340
+ */
341
+ getRemoteUrl() {
342
+ return this.remoteUrl;
343
+ }
344
+ /**
345
+ * Устанавливает URL для загрузки remote patterns.
346
+ */
347
+ setRemoteUrl(url) {
348
+ this.remoteUrl = url;
349
+ }
350
+ };
351
+
352
+ // src/security/promptInjectionFilter.ts
353
+ var DEFAULT_LOG_DIR = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".vsl", "logs");
354
+ var DEFAULT_LOG_PATH = (0, import_node_path2.join)(DEFAULT_LOG_DIR, "security-audit.log");
355
+ var DEFAULT_BUNDLED_PATTERNS_PATH = (0, import_node_path2.join)(__dirname, "..", "..", "patterns", "patterns_v1.json");
356
+ var DEFAULT_REMOTE_PATTERNS_PATH = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".vsl", "cache", "remote-patterns.json");
357
+ var DEFAULT_CONFIDENCE_THRESHOLD = 0.7;
358
+ var DEFAULT_MAX_LOG_ENTRIES = 1e4;
359
+ var PromptInjectionFilter = class {
360
+ enabled;
361
+ confidenceThreshold;
362
+ domainWhitelist;
363
+ patterns = [];
364
+ logPath;
365
+ maxLogEntries;
366
+ logEntries = [];
367
+ remoteLoader = null;
368
+ constructor(options = {}) {
369
+ this.enabled = options.enabled ?? true;
370
+ this.confidenceThreshold = options.confidenceThreshold ?? DEFAULT_CONFIDENCE_THRESHOLD;
371
+ this.domainWhitelist = new Set(options.domainWhitelist ?? []);
372
+ this.logPath = options.logPath ?? DEFAULT_LOG_PATH;
373
+ this.maxLogEntries = options.maxLogEntries ?? DEFAULT_MAX_LOG_ENTRIES;
374
+ if (options.remoteLoader?.enabled !== false) {
375
+ this.remoteLoader = new RemotePatternsLoader({
376
+ remoteUrl: options.remoteLoader?.remoteUrl,
377
+ cachePath: options.remoteLoader?.cachePath ?? options.remotePatternsPath,
378
+ cacheTtlMs: options.remoteLoader?.cacheTtlMs,
379
+ enabled: options.remoteLoader?.enabled ?? true,
380
+ timeoutMs: options.remoteLoader?.timeoutMs
381
+ });
382
+ }
383
+ this.loadPatterns(options);
384
+ this.loadExistingLogs();
385
+ }
386
+ /**
387
+ * Загружает паттерны из всех источников (bundled, remote, custom).
388
+ */
389
+ loadPatterns(options) {
390
+ const allPatterns = [];
391
+ const bundledPath = options.bundledPatternsPath ?? DEFAULT_BUNDLED_PATTERNS_PATH;
392
+ if ((0, import_node_fs2.existsSync)(bundledPath)) {
393
+ try {
394
+ const content = (0, import_node_fs2.readFileSync)(bundledPath, "utf-8");
395
+ const bundled = JSON.parse(content);
396
+ allPatterns.push(...bundled);
397
+ } catch (error) {
398
+ console.error(`[PromptInjectionFilter] Failed to load bundled patterns: ${error}`);
399
+ }
400
+ }
401
+ const remotePath = options.remotePatternsPath ?? DEFAULT_REMOTE_PATTERNS_PATH;
402
+ if ((0, import_node_fs2.existsSync)(remotePath)) {
403
+ try {
404
+ const content = (0, import_node_fs2.readFileSync)(remotePath, "utf-8");
405
+ const remote = JSON.parse(content);
406
+ allPatterns.push(...remote);
407
+ } catch (error) {
408
+ console.error(`[PromptInjectionFilter] Failed to load remote patterns: ${error}`);
409
+ }
410
+ }
411
+ if (options.customPatterns && options.customPatterns.length > 0) {
412
+ allPatterns.push(...options.customPatterns);
413
+ }
414
+ this.patterns = allPatterns.filter((p) => p.type === "regex").map((p) => ({
415
+ ...p,
416
+ regex: new RegExp(p.pattern, "gi"),
417
+ // case-insensitive, global
418
+ confidenceThreshold: p.confidence ?? this.confidenceThreshold
419
+ }));
420
+ }
421
+ /**
422
+ * Асинхронно загружает remote patterns через RemotePatternsLoader.
423
+ * Вызывается опционально для обновления паттернов с CDN/GitHub.
424
+ *
425
+ * @returns Результат загрузки remote patterns
426
+ */
427
+ async loadRemotePatterns() {
428
+ if (!this.remoteLoader) {
429
+ return { patterns: 0, source: "none", fallbackUsed: false };
430
+ }
431
+ const result = await this.remoteLoader.load();
432
+ if (result.patterns.length > 0) {
433
+ const remoteCompiled = result.patterns.filter((p) => p.type === "regex").map((p) => ({
434
+ ...p,
435
+ regex: new RegExp(p.pattern, "gi"),
436
+ confidenceThreshold: p.confidence ?? this.confidenceThreshold
437
+ }));
438
+ this.patterns.push(...remoteCompiled);
439
+ }
440
+ return {
441
+ patterns: result.patterns.length,
442
+ source: result.source,
443
+ fallbackUsed: result.fallbackUsed,
444
+ error: result.error
445
+ };
446
+ }
447
+ /**
448
+ * Получает информацию о кэше remote patterns.
449
+ */
450
+ getRemoteCacheInfo() {
451
+ if (!this.remoteLoader) {
452
+ return { exists: false };
453
+ }
454
+ return this.remoteLoader.getCacheInfo();
455
+ }
456
+ /**
457
+ * Очищает кэш remote patterns.
458
+ */
459
+ clearRemoteCache() {
460
+ if (this.remoteLoader) {
461
+ this.remoteLoader.clearCache();
462
+ }
463
+ }
464
+ /**
465
+ * Загружает существующие логи из файла.
466
+ */
467
+ loadExistingLogs() {
468
+ if (!(0, import_node_fs2.existsSync)(this.logPath)) {
469
+ return;
470
+ }
471
+ try {
472
+ const content = (0, import_node_fs2.readFileSync)(this.logPath, "utf-8");
473
+ const lines = content.trim().split("\n").filter((line) => line.length > 0);
474
+ this.logEntries = lines.map((line) => JSON.parse(line));
475
+ } catch (error) {
476
+ console.error(`[PromptInjectionFilter] Failed to load existing logs: ${error}`);
477
+ this.logEntries = [];
478
+ }
479
+ }
480
+ /**
481
+ * Проверяет, находится ли URL в whitelist.
482
+ */
483
+ isWhitelisted(url) {
484
+ if (!url) return false;
485
+ try {
486
+ const hostname = new URL(url).hostname;
487
+ return this.domainWhitelist.has(hostname);
488
+ } catch {
489
+ return false;
490
+ }
491
+ }
492
+ /**
493
+ * Сканирует текст на наличие инъекций.
494
+ *
495
+ * @param text — текст для сканирования
496
+ * @param url — URL страницы (для whitelist и логирования)
497
+ * @returns Результат сканирования с очищенным текстом и списком обнаружений
498
+ */
499
+ scan(text, url) {
500
+ const startTime = performance.now();
501
+ if (!this.enabled || this.isWhitelisted(url)) {
502
+ return {
503
+ cleanText: text,
504
+ detections: [],
505
+ blocked: false,
506
+ scanTimeMs: performance.now() - startTime
507
+ };
508
+ }
509
+ const detections = [];
510
+ let cleanText = text;
511
+ let blocked = false;
512
+ for (const pattern of this.patterns) {
513
+ pattern.regex.lastIndex = 0;
514
+ let match;
515
+ while ((match = pattern.regex.exec(text)) !== null) {
516
+ const matchedText = match[0];
517
+ const index = match.index;
518
+ const contextStart = Math.max(0, index - 50);
519
+ const contextEnd = Math.min(text.length, index + matchedText.length + 50);
520
+ const context = text.slice(contextStart, contextEnd);
521
+ const detection = {
522
+ patternId: pattern.id,
523
+ description: pattern.description,
524
+ severity: pattern.severity,
525
+ action: pattern.action,
526
+ matchedText,
527
+ index,
528
+ context
529
+ };
530
+ detections.push(detection);
531
+ this.logDetection(detection, url);
532
+ if (pattern.action === "block") {
533
+ blocked = true;
534
+ cleanText = "";
535
+ break;
536
+ } else if (pattern.action === "strip") {
537
+ cleanText = cleanText.replace(matchedText, "");
538
+ }
539
+ if (match.index === pattern.regex.lastIndex) {
540
+ pattern.regex.lastIndex++;
541
+ }
542
+ }
543
+ }
544
+ if (detections.some((d) => d.action === "strip")) {
545
+ cleanText = cleanText.replace(/\s+/g, " ").trim();
546
+ }
547
+ return {
548
+ cleanText,
549
+ detections,
550
+ blocked,
551
+ scanTimeMs: performance.now() - startTime
552
+ };
553
+ }
554
+ /**
555
+ * Логирует обнаружение в security audit trail.
556
+ */
557
+ logDetection(detection, url) {
558
+ const entry = {
559
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
560
+ url,
561
+ patternId: detection.patternId,
562
+ description: detection.description,
563
+ severity: detection.severity,
564
+ action: detection.action,
565
+ matchedText: detection.matchedText,
566
+ context: detection.context
567
+ };
568
+ this.logEntries.push(entry);
569
+ if (this.logEntries.length > this.maxLogEntries) {
570
+ this.logEntries = this.logEntries.slice(-this.maxLogEntries);
571
+ }
572
+ this.writeLogEntry(entry);
573
+ }
574
+ /**
575
+ * Записывает одну запись в лог-файл.
576
+ */
577
+ writeLogEntry(entry) {
578
+ try {
579
+ const logDir = (0, import_node_path2.join)(this.logPath, "..");
580
+ if (!(0, import_node_fs2.existsSync)(logDir)) {
581
+ (0, import_node_fs2.mkdirSync)(logDir, { recursive: true });
582
+ }
583
+ (0, import_node_fs2.appendFileSync)(this.logPath, JSON.stringify(entry) + "\n", "utf-8");
584
+ } catch (error) {
585
+ console.error(`[PromptInjectionFilter] Failed to write log entry: ${error}`);
586
+ }
587
+ }
588
+ /**
589
+ * Получает все логи (для аудита).
590
+ */
591
+ getLogs() {
592
+ return [...this.logEntries];
593
+ }
594
+ /**
595
+ * Очищает логи (для тестирования).
596
+ */
597
+ clearLogs() {
598
+ this.logEntries = [];
599
+ try {
600
+ if ((0, import_node_fs2.existsSync)(this.logPath)) {
601
+ (0, import_node_fs2.writeFileSync)(this.logPath, "", "utf-8");
602
+ }
603
+ } catch (error) {
604
+ console.error(`[PromptInjectionFilter] Failed to clear logs: ${error}`);
605
+ }
606
+ }
607
+ /**
608
+ * Добавляет custom pattern динамически.
609
+ */
610
+ addCustomPattern(pattern) {
611
+ if (pattern.type !== "regex") {
612
+ console.warn(`[PromptInjectionFilter] Only regex patterns are supported, skipping: ${pattern.id}`);
613
+ return;
614
+ }
615
+ const compiled = {
616
+ ...pattern,
617
+ regex: new RegExp(pattern.pattern, "gi"),
618
+ confidenceThreshold: pattern.confidence ?? this.confidenceThreshold
619
+ };
620
+ this.patterns.push(compiled);
621
+ }
622
+ /**
623
+ * Удаляет custom pattern по ID.
624
+ */
625
+ removePattern(patternId) {
626
+ const initialLength = this.patterns.length;
627
+ this.patterns = this.patterns.filter((p) => p.id !== patternId);
628
+ return this.patterns.length < initialLength;
629
+ }
630
+ /**
631
+ * Получает количество загруженных паттернов.
632
+ */
633
+ getPatternCount() {
634
+ return this.patterns.length;
635
+ }
636
+ /**
637
+ * Включает/выключает фильтр.
638
+ */
639
+ setEnabled(enabled) {
640
+ this.enabled = enabled;
641
+ }
642
+ /**
643
+ * Проверяет, включён ли фильтр.
644
+ */
645
+ isEnabled() {
646
+ return this.enabled;
647
+ }
648
+ };
649
+ function createFilter(options) {
650
+ return new PromptInjectionFilter(options);
651
+ }
652
+ var globalFilter = null;
653
+ function getGlobalFilter() {
654
+ if (!globalFilter) {
655
+ globalFilter = new PromptInjectionFilter();
656
+ }
657
+ return globalFilter;
658
+ }
659
+
62
660
  // src/capture/domExtractor.ts
63
661
  var NON_RENDERABLE_TAGS = /* @__PURE__ */ new Set([
64
662
  "script",
@@ -90,6 +688,16 @@ function ownText(el) {
90
688
  }
91
689
  return raw.replace(/\s+/g, " ").trim();
92
690
  }
691
+ function filteredOwnText(el, url) {
692
+ const raw = ownText(el);
693
+ if (!raw) return raw;
694
+ const filter = getGlobalFilter();
695
+ if (!filter.isEnabled()) {
696
+ return raw;
697
+ }
698
+ const result = filter.scan(raw, url);
699
+ return result.cleanText;
700
+ }
93
701
  function extractAttributes(el) {
94
702
  const attributes = {};
95
703
  for (const attr of Array.from(el.attributes)) {
@@ -134,7 +742,7 @@ function toRect(rect) {
134
742
  function hasZeroBox(rect) {
135
743
  return rect.width <= 0 || rect.height <= 0;
136
744
  }
137
- function collectVisibleChildren(parent, parentPath) {
745
+ function collectVisibleChildren(parent, parentPath, url) {
138
746
  const result = [];
139
747
  Array.from(parent.children).forEach((child, index) => {
140
748
  const path = [...parentPath, index];
@@ -144,17 +752,17 @@ function collectVisibleChildren(parent, parentPath) {
144
752
  }
145
753
  const rect = toRect(child.getBoundingClientRect());
146
754
  if (isVisibilityHidden(child) || hasZeroBox(rect)) {
147
- result.push(...collectVisibleChildren(child, path));
755
+ result.push(...collectVisibleChildren(child, path, url));
148
756
  return;
149
757
  }
150
758
  result.push({
151
759
  tag,
152
760
  indexPath: path,
153
761
  rect,
154
- text: ownText(child) || null,
762
+ text: filteredOwnText(child, url) || null,
155
763
  attributes: extractAttributes(child),
156
764
  css: captureCss(child),
157
- children: collectVisibleChildren(child, path)
765
+ children: collectVisibleChildren(child, path, url)
158
766
  });
159
767
  });
160
768
  return result;
@@ -166,8 +774,8 @@ function defaultRoot() {
166
774
  }
167
775
  return body;
168
776
  }
169
- function extractDomTree(root = defaultRoot()) {
170
- return collectVisibleChildren(root, []);
777
+ function extractDomTree(root = defaultRoot(), url) {
778
+ return collectVisibleChildren(root, [], url);
171
779
  }
172
780
 
173
781
  // src/segmentation/level1.ts
@@ -192,7 +800,12 @@ var ARIA_ROLE_TYPE_MAP = {
192
800
  button: "button",
193
801
  dialog: "modal",
194
802
  tab: "tab",
195
- tabpanel: "container"
803
+ tabpanel: "container",
804
+ combobox: "input",
805
+ listbox: "select",
806
+ searchbox: "input",
807
+ spinbutton: "input",
808
+ slider: "input"
196
809
  };
197
810
  function resolveAriaRoleType(role) {
198
811
  if (!role) return null;
@@ -375,7 +988,7 @@ var INTERACTIVE_ATTRS = [
375
988
  ];
376
989
  function relative(value, dimension) {
377
990
  if (dimension <= 0) return 0;
378
- return Math.round(value / dimension * 1e4) / 1e4;
991
+ return Math.round(value / dimension * 100) / 100;
379
992
  }
380
993
  function defaultActions(t, st, attributes) {
381
994
  if (st === "disabled") return void 0;
@@ -443,10 +1056,10 @@ function extractVisualStyles(css) {
443
1056
  }
444
1057
  return hasContent ? style : void 0;
445
1058
  }
446
- function toVslObject(el, viewport, includedChildren, textBlocks, counter) {
1059
+ function toVslObject(el, viewport, includedChildren, textBlocks, counter, idGenerator) {
447
1060
  const t = el.t ?? "container";
448
1061
  const object = {
449
- id: `${el.tag}_${el.indexPath.join("_")}`,
1062
+ id: idGenerator.generate(el.attributes["id"], t),
450
1063
  t,
451
1064
  p: [relative(el.rect.x, viewport.width), relative(el.rect.y, viewport.height)],
452
1065
  s: [Math.round(el.rect.width), Math.round(el.rect.height)]
@@ -480,12 +1093,12 @@ function toVslObject(el, viewport, includedChildren, textBlocks, counter) {
480
1093
  }
481
1094
  return object;
482
1095
  }
483
- function convert(el, viewport, textBlocks, counter) {
484
- const includedChildren = el.ch.map((child) => convert(child, viewport, textBlocks, counter)).filter((obj) => obj !== null);
485
- if (el.t !== null) return toVslObject(el, viewport, includedChildren, textBlocks, counter);
1096
+ function convert(el, viewport, textBlocks, counter, idGenerator) {
1097
+ const includedChildren = el.ch.map((child) => convert(child, viewport, textBlocks, counter, idGenerator)).filter((obj) => obj !== null);
1098
+ if (el.t !== null) return toVslObject(el, viewport, includedChildren, textBlocks, counter, idGenerator);
486
1099
  const score = baseSemanticScore(el) + (includedChildren.length > 0 ? 1 : 0);
487
1100
  if (score >= 3 || includedChildren.length > 0) {
488
- return toVslObject(el, viewport, includedChildren, textBlocks, counter);
1101
+ return toVslObject(el, viewport, includedChildren, textBlocks, counter, idGenerator);
489
1102
  }
490
1103
  return null;
491
1104
  }
@@ -534,7 +1147,8 @@ function buildVslDocument(elements, options = {}) {
534
1147
  };
535
1148
  const textBlocks = /* @__PURE__ */ new Map();
536
1149
  const counter = { value: 0 };
537
- const objects = elements.map((el) => convert(el, viewport, textBlocks, counter)).filter((obj) => obj !== null);
1150
+ const idGenerator = new IdGenerator();
1151
+ const objects = elements.map((el) => convert(el, viewport, textBlocks, counter, idGenerator)).filter((obj) => obj !== null);
538
1152
  const vslDocument = {
539
1153
  vsl_version: VSL_VERSION,
540
1154
  canvas: buildCanvas(options, viewport),
@@ -1994,10 +2608,14 @@ var VSL_SDK_VERSION = "0.1.0";
1994
2608
  ActionExecutionError,
1995
2609
  AlibabaAdapter,
1996
2610
  AnthropicAdapter,
2611
+ IdGenerator,
1997
2612
  LEVEL1_TAG_MAP,
1998
2613
  LlmError,
1999
2614
  LlmValidationError,
2000
2615
  OpenAIAdapter,
2616
+ PromptInjectionFilter,
2617
+ RemotePatternsLoader,
2618
+ TYPE_ABBREVIATIONS,
2001
2619
  VALID_ACTIONS,
2002
2620
  VSL_SDK_VERSION,
2003
2621
  VSL_VERSION,
@@ -2010,9 +2628,12 @@ var VSL_SDK_VERSION = "0.1.0";
2010
2628
  computeContentHash,
2011
2629
  computeCoordHash,
2012
2630
  createCacheStore,
2631
+ createFilter,
2632
+ createIdGenerator,
2013
2633
  diffVslDocuments,
2014
2634
  executeAction,
2015
2635
  extractDomTree,
2636
+ getGlobalFilter,
2016
2637
  isAriaHidden,
2017
2638
  isVslDiff,
2018
2639
  ownText,