@thinkingos/vsl-sdk 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.
package/dist/index.d.mts CHANGED
@@ -60,6 +60,18 @@ interface ElementCss {
60
60
  pointerEvents?: string;
61
61
  overflow?: string;
62
62
  height?: string;
63
+ /** Background color (computed) — для sty.bg в VslObject. */
64
+ backgroundColor?: string;
65
+ /** Text/foreground color (computed) — для sty.fg в VslObject. */
66
+ color?: string;
67
+ /** Border shorthand (computed) — для sty.border в VslObject. */
68
+ border?: string;
69
+ /** Border radius (computed) — для sty.radius в VslObject. */
70
+ borderRadius?: string;
71
+ /** Box shadow (computed) — для sty.shadow в VslObject. */
72
+ boxShadow?: string;
73
+ /** Font family (computed) — для sty.font.family в VslObject. */
74
+ fontFamily?: string;
63
75
  }
64
76
  /** Собственный текст элемента: только прямые текстовые узлы, whitespace-нормализация. */
65
77
  declare function ownText(el: Element): string;
@@ -76,7 +88,7 @@ declare function extractDomTree(root?: Element): ExtractedElement[];
76
88
  * Семантический тип объекта VSL (поле `t`): L1-теги + L2-роли (T1.1.3/T1.1.4),
77
89
  * L3-CSS (T1.5.1), L4-структурные (T1.5.2), L5-vision (T1.5.4).
78
90
  */
79
- type VslType = 'button' | 'input' | 'link' | 'nav' | 'header' | 'main' | 'container' | 'image' | 'select' | 'textarea' | 'modal' | 'tab' | 'heading' | 'footer' | 'scrollable_container' | 'toolbar' | 'list' | 'grid' | 'form_field' | 'tab_bar' | 'layout' | 'icon' | 'chart' | 'custom_widget' | 'unknown';
91
+ type VslType = 'button' | 'input' | 'file_input' | 'link' | 'nav' | 'header' | 'main' | 'container' | 'image' | 'select' | 'textarea' | 'modal' | 'tab' | 'heading' | 'footer' | 'scrollable_container' | 'toolbar' | 'list' | 'grid' | 'form_field' | 'tab_bar' | 'layout' | 'icon' | 'chart' | 'custom_widget' | 'unknown';
80
92
  /** Состояние объекта VSL (поле `st`) — из ARIA-атрибутов (T1.1.4). */
81
93
  type VslState = 'checked' | 'expanded' | 'disabled';
82
94
  /** Тип визуального фрагмента — выход классификации vision (§2.2.2 Шаг 2). */
@@ -89,6 +101,29 @@ interface VslFragmentMeta$1 {
89
101
  hash?: string;
90
102
  cached_at?: string;
91
103
  }
104
+ /**
105
+ * Визуальные стили объекта VSL (DESIGN_SYSTEM.md §4.3).
106
+ * Optional: только для визуально значимых элементов.
107
+ * Все значения — computed styles в момент snapshot.
108
+ */
109
+ interface VslStyle {
110
+ /** Background color (computed). */
111
+ bg?: string;
112
+ /** Text/foreground color (computed). */
113
+ fg?: string;
114
+ /** Border shorthand (computed). */
115
+ border?: string;
116
+ /** Border radius (computed). */
117
+ radius?: string;
118
+ /** Box shadow (computed). */
119
+ shadow?: string;
120
+ /** Font properties. */
121
+ font?: {
122
+ family?: string;
123
+ size?: string;
124
+ weight?: string;
125
+ };
126
+ }
92
127
  /**
93
128
  * Запись реестра visual_fragments документа (§2.2.2, ARCHITECTURE.md:L265-284).
94
129
  * ТОЛЬКО метаданные: base64-данные изображений живут в VisualFragmentStore
@@ -148,6 +183,12 @@ interface VslObject {
148
183
  vf?: string;
149
184
  /** Метаданные фрагмента (§4.4). */
150
185
  vf_meta?: VslFragmentMeta$1;
186
+ /** Визуальные стили (DESIGN_SYSTEM.md §4.3). Optional: только для визуально значимых элементов. */
187
+ sty?: VslStyle;
188
+ /** Accept attribute для file_input (например, '.pdf,.docx'). */
189
+ accept?: string;
190
+ /** Multiple attribute для file_input — разрешена загрузка нескольких файлов. */
191
+ multiple?: boolean;
151
192
  }
152
193
  interface VslDocument {
153
194
  vsl_version: string;
@@ -250,6 +291,8 @@ interface SegmentedElement {
250
291
  txt: string | null;
251
292
  /** Состояние из ARIA (checked/expanded/disabled) или null. */
252
293
  st: VslState | null;
294
+ /** CSS-подмножество для визуальных стилей (передаётся в Builder для sty). */
295
+ css?: ElementCss;
253
296
  ch: SegmentedElement[];
254
297
  /** Ссылка на визуальный фрагмент (заполняется enrichWithVision, T1.5.4). */
255
298
  vf?: string;
@@ -802,32 +845,6 @@ interface EnrichWithVisionDeps {
802
845
  extractor: FragmentExtractor;
803
846
  }
804
847
 
805
- /**
806
- * VSL Snapshot Session (T1.2.4, ROADMAP.md M1.2; ARCHITECTURE.md §2.4/§5.2/§6.3).
807
- *
808
- * Stateful-фасад над пайплайном capture→segment→build→cache→diff:
809
- * - первый вызов или смена URL → полный VslDocument; кэш очищается и
810
- * заполняется объектами документа; счётчик версий сбрасывается в 1;
811
- * - тот же URL → VslDiff последнего документа против нового (формат §6.2):
812
- * diff_version = version + 1, base_version = version;
813
- * - viewport изменился при том же URL → дополнительно
814
- * store.invalidateCoordinates() — «сброс координат» §5.2, НЕ полный сброс.
815
- *
816
- * Состояние {lastUrl, lastViewport, lastDocument, version} — в памяти
817
- * экземпляра; VslDocument (канон §4) версиями НЕ расширяется.
818
- *
819
- * Политика кэша — store отражает текущий документ: full → clear + заполнение;
820
- * diff → удалённые invalidate, добавленные (поддеревья) и изменённые set из
821
- * нового документа; неизменённые остаются кэшированными (после resize — с
822
- * обнулённым coordHash: наблюдаемый эффект invalidateCoordinates).
823
- *
824
- * input.url/viewport/timestamp/background передаются в BuildOptions ЯВНО —
825
- * дефолты builder (window.innerWidth/innerHeight/location.href/document.title)
826
- * вычислялись бы от глобалов: недетерминированны в тестах и недоступны в Node;
827
- * title — из input, иначе root.ownerDocument.title (портативный фолбэк — фикс
828
- * packaging-smoke demo:cache-diff).
829
- */
830
-
831
848
  /** Результат snapshot(): полный документ (1-й вызов / URL change) или дифф. */
832
849
  type SnapshotResult = VslDocument | VslDiff;
833
850
  /** Type guard: различение диффа и полного документа (контракт T1.2.4). */
@@ -866,11 +883,26 @@ declare class VslSnapshotSession {
866
883
  private lastDocument;
867
884
  private version;
868
885
  constructor(store?: CacheStore);
886
+ /**
887
+ * Сбрасывает состояние сессии (URL, viewport, документ, версия).
888
+ * Следующий вызов snapshot() вернёт полный документ (как первый вызов).
889
+ */
890
+ clear(): void;
891
+ /** Возвращает последний полный документ (не diff). */
892
+ getLastDocument(): VslDocument | null;
893
+ /** Текущая версия документа (инкрементируется при каждом diff). */
894
+ getVersion(): number;
869
895
  /**
870
896
  * Строит VSL из DOM поддерева root (extractDomTree → segmentTree →
871
897
  * buildVslDocument) и возвращает полный документ либо дифф (см. шапку).
872
898
  */
873
899
  snapshot(root: Element, input: SnapshotInput): SnapshotResult;
900
+ /**
901
+ * Вариант snapshot() для pre-extracted elements (MCP сервер, Node.js контекст).
902
+ * Принимает ExtractedElement[] напрямую (без DOM Element), проходит тот же
903
+ * pipeline: segmentTree → buildVslDocument → cache/diff.
904
+ */
905
+ snapshotFromElements(extractedElements: readonly ExtractedElement[], input: SnapshotInput): SnapshotResult;
874
906
  /**
875
907
  * Vision-вариант конвейера (T1.5.5): segmentTree → enrichWithVision (Level 5
876
908
  * fallback — аннотация t + vf/vf_meta на элементах ДО сборки) → buildVslDocument
@@ -879,8 +911,18 @@ declare class VslSnapshotSession {
879
911
  * (base64) возвращаются ОТДЕЛЬНО — в документе только метаданные (DEC-015).
880
912
  */
881
913
  snapshotWithVision(root: Element, input: SnapshotInput, vision: EnrichWithVisionDeps): Promise<VisionSnapshotResult>;
914
+ /**
915
+ * Vision-вариант для pre-extracted elements (MCP сервер, Node.js контекст).
916
+ * Полный pipeline: segmentTree → enrichWithVision → buildVslDocument → cache/diff.
917
+ */
918
+ snapshotWithVisionFromElements(extractedElements: readonly ExtractedElement[], input: SnapshotInput, vision: EnrichWithVisionDeps): Promise<VisionSnapshotResult>;
882
919
  /** BuildOptions из input + портативный title (логика бывшего snapshot()). */
883
920
  private buildOptions;
921
+ /**
922
+ * BuildOptions из input без DOM Element (для snapshotFromElements).
923
+ * Title берётся только из input (нет фолбэка на root.ownerDocument.title).
924
+ */
925
+ private buildOptionsFromInput;
884
926
  /** Сборка документа + кэш/дифф (логика бывшего snapshot(), см. шапку модуля). */
885
927
  private commit;
886
928
  }
@@ -1055,7 +1097,7 @@ declare const ACTION_TOOL_SCHEMA: {
1055
1097
  action: {
1056
1098
  type: string;
1057
1099
  description: string;
1058
- enum: ("select" | "click" | "close" | "type" | "check" | "uncheck" | "clear" | "scroll" | "hover" | "focus" | "blur" | "drag" | "drop" | "submit" | "reset" | "open" | "expand" | "collapse" | "wait" | "navigate" | "go_back" | "go_forward" | "refresh" | "download")[];
1100
+ enum: ("select" | "type" | "click" | "close" | "check" | "uncheck" | "clear" | "scroll" | "hover" | "focus" | "blur" | "drag" | "drop" | "submit" | "reset" | "open" | "expand" | "collapse" | "wait" | "navigate" | "go_back" | "go_forward" | "refresh" | "download")[];
1059
1101
  };
1060
1102
  target_id: {
1061
1103
  type: string;
package/dist/index.d.ts CHANGED
@@ -60,6 +60,18 @@ interface ElementCss {
60
60
  pointerEvents?: string;
61
61
  overflow?: string;
62
62
  height?: string;
63
+ /** Background color (computed) — для sty.bg в VslObject. */
64
+ backgroundColor?: string;
65
+ /** Text/foreground color (computed) — для sty.fg в VslObject. */
66
+ color?: string;
67
+ /** Border shorthand (computed) — для sty.border в VslObject. */
68
+ border?: string;
69
+ /** Border radius (computed) — для sty.radius в VslObject. */
70
+ borderRadius?: string;
71
+ /** Box shadow (computed) — для sty.shadow в VslObject. */
72
+ boxShadow?: string;
73
+ /** Font family (computed) — для sty.font.family в VslObject. */
74
+ fontFamily?: string;
63
75
  }
64
76
  /** Собственный текст элемента: только прямые текстовые узлы, whitespace-нормализация. */
65
77
  declare function ownText(el: Element): string;
@@ -76,7 +88,7 @@ declare function extractDomTree(root?: Element): ExtractedElement[];
76
88
  * Семантический тип объекта VSL (поле `t`): L1-теги + L2-роли (T1.1.3/T1.1.4),
77
89
  * L3-CSS (T1.5.1), L4-структурные (T1.5.2), L5-vision (T1.5.4).
78
90
  */
79
- type VslType = 'button' | 'input' | 'link' | 'nav' | 'header' | 'main' | 'container' | 'image' | 'select' | 'textarea' | 'modal' | 'tab' | 'heading' | 'footer' | 'scrollable_container' | 'toolbar' | 'list' | 'grid' | 'form_field' | 'tab_bar' | 'layout' | 'icon' | 'chart' | 'custom_widget' | 'unknown';
91
+ type VslType = 'button' | 'input' | 'file_input' | 'link' | 'nav' | 'header' | 'main' | 'container' | 'image' | 'select' | 'textarea' | 'modal' | 'tab' | 'heading' | 'footer' | 'scrollable_container' | 'toolbar' | 'list' | 'grid' | 'form_field' | 'tab_bar' | 'layout' | 'icon' | 'chart' | 'custom_widget' | 'unknown';
80
92
  /** Состояние объекта VSL (поле `st`) — из ARIA-атрибутов (T1.1.4). */
81
93
  type VslState = 'checked' | 'expanded' | 'disabled';
82
94
  /** Тип визуального фрагмента — выход классификации vision (§2.2.2 Шаг 2). */
@@ -89,6 +101,29 @@ interface VslFragmentMeta$1 {
89
101
  hash?: string;
90
102
  cached_at?: string;
91
103
  }
104
+ /**
105
+ * Визуальные стили объекта VSL (DESIGN_SYSTEM.md §4.3).
106
+ * Optional: только для визуально значимых элементов.
107
+ * Все значения — computed styles в момент snapshot.
108
+ */
109
+ interface VslStyle {
110
+ /** Background color (computed). */
111
+ bg?: string;
112
+ /** Text/foreground color (computed). */
113
+ fg?: string;
114
+ /** Border shorthand (computed). */
115
+ border?: string;
116
+ /** Border radius (computed). */
117
+ radius?: string;
118
+ /** Box shadow (computed). */
119
+ shadow?: string;
120
+ /** Font properties. */
121
+ font?: {
122
+ family?: string;
123
+ size?: string;
124
+ weight?: string;
125
+ };
126
+ }
92
127
  /**
93
128
  * Запись реестра visual_fragments документа (§2.2.2, ARCHITECTURE.md:L265-284).
94
129
  * ТОЛЬКО метаданные: base64-данные изображений живут в VisualFragmentStore
@@ -148,6 +183,12 @@ interface VslObject {
148
183
  vf?: string;
149
184
  /** Метаданные фрагмента (§4.4). */
150
185
  vf_meta?: VslFragmentMeta$1;
186
+ /** Визуальные стили (DESIGN_SYSTEM.md §4.3). Optional: только для визуально значимых элементов. */
187
+ sty?: VslStyle;
188
+ /** Accept attribute для file_input (например, '.pdf,.docx'). */
189
+ accept?: string;
190
+ /** Multiple attribute для file_input — разрешена загрузка нескольких файлов. */
191
+ multiple?: boolean;
151
192
  }
152
193
  interface VslDocument {
153
194
  vsl_version: string;
@@ -250,6 +291,8 @@ interface SegmentedElement {
250
291
  txt: string | null;
251
292
  /** Состояние из ARIA (checked/expanded/disabled) или null. */
252
293
  st: VslState | null;
294
+ /** CSS-подмножество для визуальных стилей (передаётся в Builder для sty). */
295
+ css?: ElementCss;
253
296
  ch: SegmentedElement[];
254
297
  /** Ссылка на визуальный фрагмент (заполняется enrichWithVision, T1.5.4). */
255
298
  vf?: string;
@@ -802,32 +845,6 @@ interface EnrichWithVisionDeps {
802
845
  extractor: FragmentExtractor;
803
846
  }
804
847
 
805
- /**
806
- * VSL Snapshot Session (T1.2.4, ROADMAP.md M1.2; ARCHITECTURE.md §2.4/§5.2/§6.3).
807
- *
808
- * Stateful-фасад над пайплайном capture→segment→build→cache→diff:
809
- * - первый вызов или смена URL → полный VslDocument; кэш очищается и
810
- * заполняется объектами документа; счётчик версий сбрасывается в 1;
811
- * - тот же URL → VslDiff последнего документа против нового (формат §6.2):
812
- * diff_version = version + 1, base_version = version;
813
- * - viewport изменился при том же URL → дополнительно
814
- * store.invalidateCoordinates() — «сброс координат» §5.2, НЕ полный сброс.
815
- *
816
- * Состояние {lastUrl, lastViewport, lastDocument, version} — в памяти
817
- * экземпляра; VslDocument (канон §4) версиями НЕ расширяется.
818
- *
819
- * Политика кэша — store отражает текущий документ: full → clear + заполнение;
820
- * diff → удалённые invalidate, добавленные (поддеревья) и изменённые set из
821
- * нового документа; неизменённые остаются кэшированными (после resize — с
822
- * обнулённым coordHash: наблюдаемый эффект invalidateCoordinates).
823
- *
824
- * input.url/viewport/timestamp/background передаются в BuildOptions ЯВНО —
825
- * дефолты builder (window.innerWidth/innerHeight/location.href/document.title)
826
- * вычислялись бы от глобалов: недетерминированны в тестах и недоступны в Node;
827
- * title — из input, иначе root.ownerDocument.title (портативный фолбэк — фикс
828
- * packaging-smoke demo:cache-diff).
829
- */
830
-
831
848
  /** Результат snapshot(): полный документ (1-й вызов / URL change) или дифф. */
832
849
  type SnapshotResult = VslDocument | VslDiff;
833
850
  /** Type guard: различение диффа и полного документа (контракт T1.2.4). */
@@ -866,11 +883,26 @@ declare class VslSnapshotSession {
866
883
  private lastDocument;
867
884
  private version;
868
885
  constructor(store?: CacheStore);
886
+ /**
887
+ * Сбрасывает состояние сессии (URL, viewport, документ, версия).
888
+ * Следующий вызов snapshot() вернёт полный документ (как первый вызов).
889
+ */
890
+ clear(): void;
891
+ /** Возвращает последний полный документ (не diff). */
892
+ getLastDocument(): VslDocument | null;
893
+ /** Текущая версия документа (инкрементируется при каждом diff). */
894
+ getVersion(): number;
869
895
  /**
870
896
  * Строит VSL из DOM поддерева root (extractDomTree → segmentTree →
871
897
  * buildVslDocument) и возвращает полный документ либо дифф (см. шапку).
872
898
  */
873
899
  snapshot(root: Element, input: SnapshotInput): SnapshotResult;
900
+ /**
901
+ * Вариант snapshot() для pre-extracted elements (MCP сервер, Node.js контекст).
902
+ * Принимает ExtractedElement[] напрямую (без DOM Element), проходит тот же
903
+ * pipeline: segmentTree → buildVslDocument → cache/diff.
904
+ */
905
+ snapshotFromElements(extractedElements: readonly ExtractedElement[], input: SnapshotInput): SnapshotResult;
874
906
  /**
875
907
  * Vision-вариант конвейера (T1.5.5): segmentTree → enrichWithVision (Level 5
876
908
  * fallback — аннотация t + vf/vf_meta на элементах ДО сборки) → buildVslDocument
@@ -879,8 +911,18 @@ declare class VslSnapshotSession {
879
911
  * (base64) возвращаются ОТДЕЛЬНО — в документе только метаданные (DEC-015).
880
912
  */
881
913
  snapshotWithVision(root: Element, input: SnapshotInput, vision: EnrichWithVisionDeps): Promise<VisionSnapshotResult>;
914
+ /**
915
+ * Vision-вариант для pre-extracted elements (MCP сервер, Node.js контекст).
916
+ * Полный pipeline: segmentTree → enrichWithVision → buildVslDocument → cache/diff.
917
+ */
918
+ snapshotWithVisionFromElements(extractedElements: readonly ExtractedElement[], input: SnapshotInput, vision: EnrichWithVisionDeps): Promise<VisionSnapshotResult>;
882
919
  /** BuildOptions из input + портативный title (логика бывшего snapshot()). */
883
920
  private buildOptions;
921
+ /**
922
+ * BuildOptions из input без DOM Element (для snapshotFromElements).
923
+ * Title берётся только из input (нет фолбэка на root.ownerDocument.title).
924
+ */
925
+ private buildOptionsFromInput;
884
926
  /** Сборка документа + кэш/дифф (логика бывшего snapshot(), см. шапку модуля). */
885
927
  private commit;
886
928
  }
@@ -1055,7 +1097,7 @@ declare const ACTION_TOOL_SCHEMA: {
1055
1097
  action: {
1056
1098
  type: string;
1057
1099
  description: string;
1058
- enum: ("select" | "click" | "close" | "type" | "check" | "uncheck" | "clear" | "scroll" | "hover" | "focus" | "blur" | "drag" | "drop" | "submit" | "reset" | "open" | "expand" | "collapse" | "wait" | "navigate" | "go_back" | "go_forward" | "refresh" | "download")[];
1100
+ enum: ("select" | "type" | "click" | "close" | "check" | "uncheck" | "clear" | "scroll" | "hover" | "focus" | "blur" | "drag" | "drop" | "submit" | "reset" | "open" | "expand" | "collapse" | "wait" | "navigate" | "go_back" | "go_forward" | "refresh" | "download")[];
1059
1101
  };
1060
1102
  target_id: {
1061
1103
  type: string;
package/dist/index.js CHANGED
@@ -109,7 +109,13 @@ var CSS_PROPERTIES = [
109
109
  ["opacity", "opacity"],
110
110
  ["pointerEvents", "pointer-events"],
111
111
  ["overflow", "overflow"],
112
- ["height", "height"]
112
+ ["height", "height"],
113
+ ["backgroundColor", "background-color"],
114
+ ["color", "color"],
115
+ ["border", "border"],
116
+ ["borderRadius", "border-radius"],
117
+ ["boxShadow", "box-shadow"],
118
+ ["fontFamily", "font-family"]
113
119
  ];
114
120
  function captureCss(el) {
115
121
  const view = viewOf(el);
@@ -319,14 +325,16 @@ function isAriaHidden(attributes) {
319
325
  }
320
326
  function segmentOne(el) {
321
327
  const { attributes } = el;
328
+ const isFileInput = el.tag === "input" && attributes["type"] === "file";
322
329
  return {
323
330
  tag: el.tag,
324
331
  indexPath: el.indexPath,
325
332
  rect: el.rect,
326
333
  attributes,
327
- t: resolveLevel1Type(el.tag) ?? resolveAriaRoleType(attributes["role"]) ?? resolveLevel3Type(el.css, attributes),
334
+ t: isFileInput ? "file_input" : resolveLevel1Type(el.tag) ?? resolveAriaRoleType(attributes["role"]) ?? resolveLevel3Type(el.css, attributes),
328
335
  txt: attributes["aria-label"] ?? el.text,
329
336
  st: resolveSt(attributes),
337
+ css: el.css,
330
338
  ch: []
331
339
  };
332
340
  }
@@ -379,6 +387,8 @@ function defaultActions(t, st, attributes) {
379
387
  return ["click", "select"];
380
388
  case "textarea":
381
389
  return ["click", "type", "clear"];
390
+ case "file_input":
391
+ return ["click", "upload"];
382
392
  default:
383
393
  return void 0;
384
394
  }
@@ -391,6 +401,39 @@ function baseSemanticScore(el) {
391
401
  if (el.txt) score += 1;
392
402
  return score;
393
403
  }
404
+ function extractVisualStyles(css) {
405
+ if (!css) return void 0;
406
+ const style = {};
407
+ let hasContent = false;
408
+ if (css.backgroundColor && css.backgroundColor !== "transparent" && !css.backgroundColor.includes("rgba(0, 0, 0, 0)")) {
409
+ style.bg = css.backgroundColor;
410
+ hasContent = true;
411
+ }
412
+ if (css.color && css.color !== "rgb(0, 0, 0)") {
413
+ style.fg = css.color;
414
+ hasContent = true;
415
+ }
416
+ if (css.border && css.border !== "none" && !css.border.startsWith("0px")) {
417
+ style.border = css.border;
418
+ hasContent = true;
419
+ }
420
+ if (css.borderRadius && css.borderRadius !== "0px") {
421
+ style.radius = css.borderRadius;
422
+ hasContent = true;
423
+ }
424
+ if (css.boxShadow && css.boxShadow !== "none") {
425
+ style.shadow = css.boxShadow;
426
+ hasContent = true;
427
+ }
428
+ if (css.fontFamily || css.fontSize || css.fontWeight) {
429
+ style.font = {};
430
+ if (css.fontFamily) style.font.family = css.fontFamily;
431
+ if (css.fontSize) style.font.size = css.fontSize;
432
+ if (css.fontWeight) style.font.weight = css.fontWeight;
433
+ hasContent = true;
434
+ }
435
+ return hasContent ? style : void 0;
436
+ }
394
437
  function toVslObject(el, viewport, includedChildren, textBlocks, counter) {
395
438
  const t = el.t ?? "container";
396
439
  const object = {
@@ -417,6 +460,12 @@ function toVslObject(el, viewport, includedChildren, textBlocks, counter) {
417
460
  if (includedChildren.length > 0) object.ch = includedChildren;
418
461
  if (el.vf !== void 0) object.vf = el.vf;
419
462
  if (el.vf_meta !== void 0) object.vf_meta = el.vf_meta;
463
+ const sty = extractVisualStyles(el.css);
464
+ if (sty !== void 0) object.sty = sty;
465
+ if (t === "file_input") {
466
+ if (el.attributes["accept"] !== void 0) object.accept = el.attributes["accept"];
467
+ if (el.attributes["multiple"] !== void 0) object.multiple = true;
468
+ }
420
469
  return object;
421
470
  }
422
471
  function convert(el, viewport, textBlocks, counter) {
@@ -603,6 +652,8 @@ function objectsEqual(a, b) {
603
652
  return computeContentHash(a) === computeContentHash(b) && computeCoordHash(a) === computeCoordHash(b);
604
653
  }
605
654
  function tuple2Equal(a, b) {
655
+ if (a === null || a === void 0) return b === null || b === void 0;
656
+ if (b === null || b === void 0) return false;
606
657
  return a[0] === b[0] && a[1] === b[1];
607
658
  }
608
659
  function stringArrayEqual(a, b) {
@@ -739,6 +790,25 @@ var VslSnapshotSession = class {
739
790
  constructor(store = createCacheStore()) {
740
791
  this.store = store;
741
792
  }
793
+ /**
794
+ * Сбрасывает состояние сессии (URL, viewport, документ, версия).
795
+ * Следующий вызов snapshot() вернёт полный документ (как первый вызов).
796
+ */
797
+ clear() {
798
+ this.store.clear();
799
+ this.lastUrl = null;
800
+ this.lastViewport = null;
801
+ this.lastDocument = null;
802
+ this.version = 0;
803
+ }
804
+ /** Возвращает последний полный документ (не diff). */
805
+ getLastDocument() {
806
+ return this.lastDocument;
807
+ }
808
+ /** Текущая версия документа (инкрементируется при каждом diff). */
809
+ getVersion() {
810
+ return this.version;
811
+ }
742
812
  /**
743
813
  * Строит VSL из DOM поддерева root (extractDomTree → segmentTree →
744
814
  * buildVslDocument) и возвращает полный документ либо дифф (см. шапку).
@@ -747,6 +817,15 @@ var VslSnapshotSession = class {
747
817
  const elements = segmentTree(extractDomTree(root));
748
818
  return this.commit(elements, this.buildOptions(root, input), input);
749
819
  }
820
+ /**
821
+ * Вариант snapshot() для pre-extracted elements (MCP сервер, Node.js контекст).
822
+ * Принимает ExtractedElement[] напрямую (без DOM Element), проходит тот же
823
+ * pipeline: segmentTree → buildVslDocument → cache/diff.
824
+ */
825
+ snapshotFromElements(extractedElements, input) {
826
+ const elements = segmentTree(extractedElements);
827
+ return this.commit(elements, this.buildOptionsFromInput(input), input);
828
+ }
750
829
  /**
751
830
  * Vision-вариант конвейера (T1.5.5): segmentTree → enrichWithVision (Level 5
752
831
  * fallback — аннотация t + vf/vf_meta на элементах ДО сборки) → buildVslDocument
@@ -760,6 +839,16 @@ var VslSnapshotSession = class {
760
839
  const snapshot = this.commit(elements, this.buildOptions(root, input), input);
761
840
  return { snapshot, fragments: visionResult.fragments };
762
841
  }
842
+ /**
843
+ * Vision-вариант для pre-extracted elements (MCP сервер, Node.js контекст).
844
+ * Полный pipeline: segmentTree → enrichWithVision → buildVslDocument → cache/diff.
845
+ */
846
+ async snapshotWithVisionFromElements(extractedElements, input, vision) {
847
+ const elements = segmentTree(extractedElements);
848
+ const visionResult = await enrichWithVision(elements, vision);
849
+ const snapshot = this.commit(elements, this.buildOptionsFromInput(input), input);
850
+ return { snapshot, fragments: visionResult.fragments };
851
+ }
763
852
  /** BuildOptions из input + портативный title (логика бывшего snapshot()). */
764
853
  buildOptions(root, input) {
765
854
  const options = {
@@ -771,6 +860,20 @@ var VslSnapshotSession = class {
771
860
  if (input.background !== void 0) options.background = input.background;
772
861
  return options;
773
862
  }
863
+ /**
864
+ * BuildOptions из input без DOM Element (для snapshotFromElements).
865
+ * Title берётся только из input (нет фолбэка на root.ownerDocument.title).
866
+ */
867
+ buildOptionsFromInput(input) {
868
+ const options = {
869
+ viewport: { width: input.viewport.width, height: input.viewport.height },
870
+ url: input.url
871
+ };
872
+ if (input.title !== void 0) options.title = input.title;
873
+ if (input.timestamp !== void 0) options.timestamp = input.timestamp;
874
+ if (input.background !== void 0) options.background = input.background;
875
+ return options;
876
+ }
774
877
  /** Сборка документа + кэш/дифф (логика бывшего snapshot(), см. шапку модуля). */
775
878
  commit(elements, options, input) {
776
879
  const nextDocument = buildVslDocument(elements, options);