@modelnex/sdk 0.5.40 → 0.5.41

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.
@@ -2,7 +2,7 @@ import {
2
2
  clearAOMMap,
3
3
  generateMinifiedAOM,
4
4
  getElementByUid
5
- } from "./chunk-N65UEB6X.mjs";
5
+ } from "./chunk-H4LUY7LI.mjs";
6
6
  export {
7
7
  clearAOMMap,
8
8
  generateMinifiedAOM,
@@ -0,0 +1,243 @@
1
+ // src/utils/editable-controls.ts
2
+ var EDITABLE_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox"]);
3
+ var EDITABLE_CONTROL_SELECTOR = [
4
+ 'input:not([type="hidden"])',
5
+ "textarea",
6
+ "select",
7
+ '[contenteditable="true"]',
8
+ '[role="textbox"]',
9
+ '[role="combobox"]'
10
+ ].join(", ");
11
+ function getTagName(element) {
12
+ return String(element?.tagName || "").toLowerCase();
13
+ }
14
+ function getRole(element) {
15
+ const role = element && typeof element.getAttribute === "function" ? element.getAttribute("role") : null;
16
+ return String(role || "").toLowerCase();
17
+ }
18
+ function getLabelControl(element) {
19
+ const labelCtor = typeof HTMLLabelElement !== "undefined" ? HTMLLabelElement : null;
20
+ if (labelCtor && element instanceof labelCtor && element.control instanceof HTMLElement) {
21
+ return element.control;
22
+ }
23
+ return null;
24
+ }
25
+ function isValueBearingElement(element) {
26
+ if (!element) return false;
27
+ const inputCtor = typeof HTMLInputElement !== "undefined" ? HTMLInputElement : null;
28
+ if (inputCtor && element instanceof inputCtor) return true;
29
+ const textareaCtor = typeof HTMLTextAreaElement !== "undefined" ? HTMLTextAreaElement : null;
30
+ if (textareaCtor && element instanceof textareaCtor) return true;
31
+ const selectCtor = typeof HTMLSelectElement !== "undefined" ? HTMLSelectElement : null;
32
+ if (selectCtor && element instanceof selectCtor) return true;
33
+ return ["input", "textarea", "select"].includes(getTagName(element));
34
+ }
35
+ function findEditableControlInShadowRoot(root) {
36
+ if (!root) return null;
37
+ if (root.shadowRoot) {
38
+ const found = root.shadowRoot.querySelector(EDITABLE_CONTROL_SELECTOR);
39
+ if (found) {
40
+ return resolveEditableControlElement(found);
41
+ }
42
+ }
43
+ for (const child of Array.from(root.children)) {
44
+ if (child instanceof HTMLElement) {
45
+ const found = findEditableControlInShadowRoot(child);
46
+ if (found) return found;
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+ function resolveEditableControlElement(element) {
52
+ const labelControl = getLabelControl(element);
53
+ if (labelControl) {
54
+ return resolveEditableControlElement(labelControl);
55
+ }
56
+ if (isValueBearingElement(element) || element.isContentEditable) {
57
+ return element;
58
+ }
59
+ const role = getRole(element);
60
+ if (EDITABLE_ROLES.has(role)) {
61
+ const nested2 = element.querySelector(EDITABLE_CONTROL_SELECTOR);
62
+ if (nested2 && nested2 !== element) {
63
+ return resolveEditableControlElement(nested2);
64
+ }
65
+ const shadowNested2 = findEditableControlInShadowRoot(element);
66
+ return shadowNested2 ?? element;
67
+ }
68
+ const nested = element.querySelector(EDITABLE_CONTROL_SELECTOR);
69
+ if (nested) {
70
+ return resolveEditableControlElement(nested);
71
+ }
72
+ const shadowNested = findEditableControlInShadowRoot(element);
73
+ return shadowNested ?? element;
74
+ }
75
+ function readEditableControlValue(element, options = {}) {
76
+ const target = resolveEditableControlElement(element);
77
+ const maskPasswords = options.maskPasswords !== false;
78
+ if (isValueBearingElement(target)) {
79
+ const type = String(target.type || "").toLowerCase();
80
+ if (maskPasswords && type === "password") {
81
+ return "***";
82
+ }
83
+ return String(target.value || "").trim().slice(0, 100);
84
+ }
85
+ const genericValue = target.value;
86
+ if (typeof genericValue === "string") {
87
+ return genericValue.trim().slice(0, 100);
88
+ }
89
+ if (typeof genericValue === "number" && Number.isFinite(genericValue)) {
90
+ return String(genericValue).trim().slice(0, 100);
91
+ }
92
+ if (target.isContentEditable) {
93
+ return String(target.textContent || "").trim().slice(0, 100);
94
+ }
95
+ const ariaValueText = target.getAttribute("aria-valuetext");
96
+ if (ariaValueText?.trim()) {
97
+ return ariaValueText.trim().slice(0, 100);
98
+ }
99
+ const ariaValueNow = target.getAttribute("aria-valuenow");
100
+ if (ariaValueNow?.trim()) {
101
+ return ariaValueNow.trim().slice(0, 100);
102
+ }
103
+ return String(target.textContent || "").trim().slice(0, 100);
104
+ }
105
+ function readEditableControlPlaceholder(element) {
106
+ const target = resolveEditableControlElement(element);
107
+ if (typeof target.placeholder === "string") {
108
+ return String(target.placeholder || "").trim().slice(0, 100);
109
+ }
110
+ return String(target.getAttribute("placeholder") || "").trim().slice(0, 100);
111
+ }
112
+ function readEditableControlName(element) {
113
+ const target = resolveEditableControlElement(element);
114
+ const explicitName = target.name;
115
+ if (typeof explicitName === "string" && explicitName.trim()) {
116
+ return explicitName.trim().slice(0, 100);
117
+ }
118
+ return String(
119
+ target.getAttribute("name") || target.getAttribute("id") || target.getAttribute("aria-label") || ""
120
+ ).trim().slice(0, 100);
121
+ }
122
+ function readEditableControlType(element) {
123
+ const target = resolveEditableControlElement(element);
124
+ const role = getRole(target);
125
+ if (role) return role;
126
+ const explicitType = target.type;
127
+ if (typeof explicitType === "string" && explicitType.trim()) {
128
+ return explicitType.trim().toLowerCase().slice(0, 50);
129
+ }
130
+ const attributeType = target.getAttribute("type");
131
+ if (attributeType?.trim()) {
132
+ return attributeType.trim().toLowerCase().slice(0, 50);
133
+ }
134
+ return getTagName(target);
135
+ }
136
+ function isEditableControlDisabled(element) {
137
+ const target = resolveEditableControlElement(element);
138
+ const ariaDisabled = String(target.getAttribute("aria-disabled") || "").toLowerCase();
139
+ if (ariaDisabled === "true") return true;
140
+ return Boolean(target.disabled);
141
+ }
142
+ function collectEditableControls(scope) {
143
+ const seen = /* @__PURE__ */ new Set();
144
+ const controls = [];
145
+ for (const rawElement of Array.from(scope.querySelectorAll(EDITABLE_CONTROL_SELECTOR))) {
146
+ const resolved = resolveEditableControlElement(rawElement);
147
+ if (seen.has(resolved)) continue;
148
+ if (isValueBearingElement(resolved) && String(resolved.type || "").toLowerCase() === "hidden") {
149
+ continue;
150
+ }
151
+ seen.add(resolved);
152
+ controls.push(resolved);
153
+ }
154
+ return controls;
155
+ }
156
+
157
+ // src/utils/aom.ts
158
+ var uidMap = /* @__PURE__ */ new Map();
159
+ var nextUid = 1;
160
+ function generateMinifiedAOM() {
161
+ uidMap.clear();
162
+ nextUid = 1;
163
+ const interactiveSet = /* @__PURE__ */ new Set();
164
+ const interactiveCandidates = [
165
+ ...Array.from(document.querySelectorAll(
166
+ 'button, a, input, select, textarea, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"], [role="textbox"], [role="combobox"], [contenteditable="true"]'
167
+ )),
168
+ ...collectEditableControls(document)
169
+ ];
170
+ const nodes = [];
171
+ interactiveCandidates.forEach((candidate) => {
172
+ const el = resolveEditableControlElement(candidate);
173
+ if (interactiveSet.has(el)) {
174
+ return;
175
+ }
176
+ interactiveSet.add(el);
177
+ if (!el.offsetParent && (el.offsetWidth === 0 || el.offsetHeight === 0)) {
178
+ return;
179
+ }
180
+ if (el.closest("#modelnex-studio-root") || el.closest("#modelnex-active-agent-root")) {
181
+ return;
182
+ }
183
+ const uid = `node:${nextUid++}`;
184
+ uidMap.set(uid, el);
185
+ let text = (el.textContent || "").replace(/\s+/g, " ").trim();
186
+ const ariaLabel = el.getAttribute("aria-label");
187
+ const placeholder = el.getAttribute("placeholder");
188
+ const value = readEditableControlValue(el, { maskPasswords: false }) || void 0;
189
+ let role = el.tagName.toLowerCase();
190
+ if (el.hasAttribute("role")) {
191
+ role = el.getAttribute("role");
192
+ } else if (role === "a") {
193
+ role = "link";
194
+ } else if (el.tagName.toLowerCase() === "input") {
195
+ const inputType = el.type;
196
+ role = inputType ? `input[${inputType}]` : "input";
197
+ }
198
+ const node = { uid, role };
199
+ const displayLabel = ariaLabel || text || placeholder;
200
+ if (displayLabel) {
201
+ node.text = displayLabel.substring(0, 100);
202
+ }
203
+ const controlName = readEditableControlName(el);
204
+ if (controlName) {
205
+ node.name = controlName;
206
+ }
207
+ if (value) {
208
+ node.value = value.substring(0, 100);
209
+ }
210
+ if (el instanceof HTMLAnchorElement && el.href) {
211
+ try {
212
+ const url = new URL(el.href);
213
+ node.href = url.pathname + url.search + url.hash;
214
+ } catch {
215
+ node.href = el.getAttribute("href");
216
+ }
217
+ }
218
+ nodes.push(node);
219
+ });
220
+ return { nodes };
221
+ }
222
+ function getElementByUid(uid) {
223
+ return uidMap.get(uid) || null;
224
+ }
225
+ function clearAOMMap() {
226
+ uidMap.clear();
227
+ nextUid = 1;
228
+ }
229
+
230
+ export {
231
+ isValueBearingElement,
232
+ findEditableControlInShadowRoot,
233
+ resolveEditableControlElement,
234
+ readEditableControlValue,
235
+ readEditableControlPlaceholder,
236
+ readEditableControlName,
237
+ readEditableControlType,
238
+ isEditableControlDisabled,
239
+ collectEditableControls,
240
+ generateMinifiedAOM,
241
+ getElementByUid,
242
+ clearAOMMap
243
+ };
package/dist/index.js CHANGED
@@ -30,6 +30,168 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
+ // src/utils/editable-controls.ts
34
+ function getTagName(element) {
35
+ return String(element?.tagName || "").toLowerCase();
36
+ }
37
+ function getRole(element) {
38
+ const role = element && typeof element.getAttribute === "function" ? element.getAttribute("role") : null;
39
+ return String(role || "").toLowerCase();
40
+ }
41
+ function getLabelControl(element) {
42
+ const labelCtor = typeof HTMLLabelElement !== "undefined" ? HTMLLabelElement : null;
43
+ if (labelCtor && element instanceof labelCtor && element.control instanceof HTMLElement) {
44
+ return element.control;
45
+ }
46
+ return null;
47
+ }
48
+ function isValueBearingElement(element) {
49
+ if (!element) return false;
50
+ const inputCtor = typeof HTMLInputElement !== "undefined" ? HTMLInputElement : null;
51
+ if (inputCtor && element instanceof inputCtor) return true;
52
+ const textareaCtor = typeof HTMLTextAreaElement !== "undefined" ? HTMLTextAreaElement : null;
53
+ if (textareaCtor && element instanceof textareaCtor) return true;
54
+ const selectCtor = typeof HTMLSelectElement !== "undefined" ? HTMLSelectElement : null;
55
+ if (selectCtor && element instanceof selectCtor) return true;
56
+ return ["input", "textarea", "select"].includes(getTagName(element));
57
+ }
58
+ function findEditableControlInShadowRoot(root) {
59
+ if (!root) return null;
60
+ if (root.shadowRoot) {
61
+ const found = root.shadowRoot.querySelector(EDITABLE_CONTROL_SELECTOR);
62
+ if (found) {
63
+ return resolveEditableControlElement(found);
64
+ }
65
+ }
66
+ for (const child of Array.from(root.children)) {
67
+ if (child instanceof HTMLElement) {
68
+ const found = findEditableControlInShadowRoot(child);
69
+ if (found) return found;
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+ function resolveEditableControlElement(element) {
75
+ const labelControl = getLabelControl(element);
76
+ if (labelControl) {
77
+ return resolveEditableControlElement(labelControl);
78
+ }
79
+ if (isValueBearingElement(element) || element.isContentEditable) {
80
+ return element;
81
+ }
82
+ const role = getRole(element);
83
+ if (EDITABLE_ROLES.has(role)) {
84
+ const nested2 = element.querySelector(EDITABLE_CONTROL_SELECTOR);
85
+ if (nested2 && nested2 !== element) {
86
+ return resolveEditableControlElement(nested2);
87
+ }
88
+ const shadowNested2 = findEditableControlInShadowRoot(element);
89
+ return shadowNested2 ?? element;
90
+ }
91
+ const nested = element.querySelector(EDITABLE_CONTROL_SELECTOR);
92
+ if (nested) {
93
+ return resolveEditableControlElement(nested);
94
+ }
95
+ const shadowNested = findEditableControlInShadowRoot(element);
96
+ return shadowNested ?? element;
97
+ }
98
+ function readEditableControlValue(element, options = {}) {
99
+ const target = resolveEditableControlElement(element);
100
+ const maskPasswords = options.maskPasswords !== false;
101
+ if (isValueBearingElement(target)) {
102
+ const type = String(target.type || "").toLowerCase();
103
+ if (maskPasswords && type === "password") {
104
+ return "***";
105
+ }
106
+ return String(target.value || "").trim().slice(0, 100);
107
+ }
108
+ const genericValue = target.value;
109
+ if (typeof genericValue === "string") {
110
+ return genericValue.trim().slice(0, 100);
111
+ }
112
+ if (typeof genericValue === "number" && Number.isFinite(genericValue)) {
113
+ return String(genericValue).trim().slice(0, 100);
114
+ }
115
+ if (target.isContentEditable) {
116
+ return String(target.textContent || "").trim().slice(0, 100);
117
+ }
118
+ const ariaValueText = target.getAttribute("aria-valuetext");
119
+ if (ariaValueText?.trim()) {
120
+ return ariaValueText.trim().slice(0, 100);
121
+ }
122
+ const ariaValueNow = target.getAttribute("aria-valuenow");
123
+ if (ariaValueNow?.trim()) {
124
+ return ariaValueNow.trim().slice(0, 100);
125
+ }
126
+ return String(target.textContent || "").trim().slice(0, 100);
127
+ }
128
+ function readEditableControlPlaceholder(element) {
129
+ const target = resolveEditableControlElement(element);
130
+ if (typeof target.placeholder === "string") {
131
+ return String(target.placeholder || "").trim().slice(0, 100);
132
+ }
133
+ return String(target.getAttribute("placeholder") || "").trim().slice(0, 100);
134
+ }
135
+ function readEditableControlName(element) {
136
+ const target = resolveEditableControlElement(element);
137
+ const explicitName = target.name;
138
+ if (typeof explicitName === "string" && explicitName.trim()) {
139
+ return explicitName.trim().slice(0, 100);
140
+ }
141
+ return String(
142
+ target.getAttribute("name") || target.getAttribute("id") || target.getAttribute("aria-label") || ""
143
+ ).trim().slice(0, 100);
144
+ }
145
+ function readEditableControlType(element) {
146
+ const target = resolveEditableControlElement(element);
147
+ const role = getRole(target);
148
+ if (role) return role;
149
+ const explicitType = target.type;
150
+ if (typeof explicitType === "string" && explicitType.trim()) {
151
+ return explicitType.trim().toLowerCase().slice(0, 50);
152
+ }
153
+ const attributeType = target.getAttribute("type");
154
+ if (attributeType?.trim()) {
155
+ return attributeType.trim().toLowerCase().slice(0, 50);
156
+ }
157
+ return getTagName(target);
158
+ }
159
+ function isEditableControlDisabled(element) {
160
+ const target = resolveEditableControlElement(element);
161
+ const ariaDisabled = String(target.getAttribute("aria-disabled") || "").toLowerCase();
162
+ if (ariaDisabled === "true") return true;
163
+ return Boolean(target.disabled);
164
+ }
165
+ function collectEditableControls(scope) {
166
+ const seen = /* @__PURE__ */ new Set();
167
+ const controls = [];
168
+ for (const rawElement of Array.from(scope.querySelectorAll(EDITABLE_CONTROL_SELECTOR))) {
169
+ const resolved = resolveEditableControlElement(rawElement);
170
+ if (seen.has(resolved)) continue;
171
+ if (isValueBearingElement(resolved) && String(resolved.type || "").toLowerCase() === "hidden") {
172
+ continue;
173
+ }
174
+ seen.add(resolved);
175
+ controls.push(resolved);
176
+ }
177
+ return controls;
178
+ }
179
+ var EDITABLE_ROLES, EDITABLE_CONTROL_SELECTOR;
180
+ var init_editable_controls = __esm({
181
+ "src/utils/editable-controls.ts"() {
182
+ "use strict";
183
+ EDITABLE_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox"]);
184
+ EDITABLE_CONTROL_SELECTOR = [
185
+ 'input:not([type="hidden"])',
186
+ "textarea",
187
+ "select",
188
+ '[contenteditable="true"]',
189
+ '[role="textbox"]',
190
+ '[role="combobox"]'
191
+ ].join(", ");
192
+ }
193
+ });
194
+
33
195
  // src/utils/aom.ts
34
196
  var aom_exports = {};
35
197
  __export(aom_exports, {
@@ -40,11 +202,20 @@ __export(aom_exports, {
40
202
  function generateMinifiedAOM() {
41
203
  uidMap.clear();
42
204
  nextUid = 1;
43
- const interactives = document.querySelectorAll(
44
- 'button, a, input, select, textarea, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"]'
45
- );
205
+ const interactiveSet = /* @__PURE__ */ new Set();
206
+ const interactiveCandidates = [
207
+ ...Array.from(document.querySelectorAll(
208
+ 'button, a, input, select, textarea, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"], [role="textbox"], [role="combobox"], [contenteditable="true"]'
209
+ )),
210
+ ...collectEditableControls(document)
211
+ ];
46
212
  const nodes = [];
47
- interactives.forEach((el) => {
213
+ interactiveCandidates.forEach((candidate) => {
214
+ const el = resolveEditableControlElement(candidate);
215
+ if (interactiveSet.has(el)) {
216
+ return;
217
+ }
218
+ interactiveSet.add(el);
48
219
  if (!el.offsetParent && (el.offsetWidth === 0 || el.offsetHeight === 0)) {
49
220
  return;
50
221
  }
@@ -56,25 +227,24 @@ function generateMinifiedAOM() {
56
227
  let text = (el.textContent || "").replace(/\s+/g, " ").trim();
57
228
  const ariaLabel = el.getAttribute("aria-label");
58
229
  const placeholder = el.getAttribute("placeholder");
59
- let value = void 0;
60
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
61
- value = el.value;
62
- }
230
+ const value = readEditableControlValue(el, { maskPasswords: false }) || void 0;
63
231
  let role = el.tagName.toLowerCase();
64
232
  if (el.hasAttribute("role")) {
65
233
  role = el.getAttribute("role");
66
234
  } else if (role === "a") {
67
235
  role = "link";
68
- } else if (el instanceof HTMLInputElement) {
69
- role = el.type ? `input[${el.type}]` : "input";
236
+ } else if (el.tagName.toLowerCase() === "input") {
237
+ const inputType = el.type;
238
+ role = inputType ? `input[${inputType}]` : "input";
70
239
  }
71
240
  const node = { uid, role };
72
241
  const displayLabel = ariaLabel || text || placeholder;
73
242
  if (displayLabel) {
74
243
  node.text = displayLabel.substring(0, 100);
75
244
  }
76
- if (el.getAttribute("name")) {
77
- node.name = el.getAttribute("name");
245
+ const controlName = readEditableControlName(el);
246
+ if (controlName) {
247
+ node.name = controlName;
78
248
  }
79
249
  if (value) {
80
250
  node.value = value.substring(0, 100);
@@ -102,6 +272,7 @@ var uidMap, nextUid;
102
272
  var init_aom = __esm({
103
273
  "src/utils/aom.ts"() {
104
274
  "use strict";
275
+ init_editable_controls();
105
276
  uidMap = /* @__PURE__ */ new Map();
106
277
  nextUid = 1;
107
278
  }
@@ -616,6 +787,7 @@ function useAutoExtract(devMode) {
616
787
  }
617
788
 
618
789
  // src/utils/dom-summary.ts
790
+ init_editable_controls();
619
791
  function textOf(el) {
620
792
  return el?.textContent?.trim().slice(0, 200) ?? "";
621
793
  }
@@ -642,8 +814,9 @@ function safeQueryAll(selector) {
642
814
  }
643
815
  }
644
816
  function findInputLabel(el) {
645
- if (el.labels && el.labels.length > 0) {
646
- const labelText = el.labels[0].textContent?.trim();
817
+ const labels = el.labels;
818
+ if (labels && labels.length > 0) {
819
+ const labelText = labels[0].textContent?.trim();
647
820
  if (labelText) return labelText.slice(0, 80);
648
821
  }
649
822
  const ariaLabel = el.getAttribute("aria-label");
@@ -661,11 +834,12 @@ function findInputLabel(el) {
661
834
  }
662
835
  const closestCell = el.closest('td, th, div[role="cell"], div[role="columnheader"]');
663
836
  if (closestCell) {
837
+ const inputType = String(el.type || "").toLowerCase();
664
838
  if (closestCell.tagName === "TH" || closestCell.getAttribute("role") === "columnheader") {
665
- if (el.type === "checkbox") return "Select All";
839
+ if (inputType === "checkbox") return "Select All";
666
840
  }
667
841
  const row = el.closest('tr, div[role="row"]');
668
- if (row && el.type === "checkbox") {
842
+ if (row && inputType === "checkbox") {
669
843
  const cells = row.querySelectorAll('td, div[role="cell"]');
670
844
  for (const cell of cells) {
671
845
  if (cell === closestCell) continue;
@@ -696,11 +870,11 @@ function captureDomSummary(tags) {
696
870
  const mapInput = (input) => {
697
871
  const el = input;
698
872
  return {
699
- name: el.name || el.id || el.getAttribute("aria-label") || "",
700
- type: el.type || el.tagName.toLowerCase(),
701
- value: el.type === "password" ? "***" : (el.value || "").slice(0, 100),
702
- disabled: el.disabled,
703
- placeholder: el.placeholder || "",
873
+ name: readEditableControlName(el),
874
+ type: readEditableControlType(el),
875
+ value: readEditableControlValue(el),
876
+ disabled: isEditableControlDisabled(el),
877
+ placeholder: readEditableControlPlaceholder(el),
704
878
  fingerprint: generateFingerprint(el),
705
879
  label: findInputLabel(el)
706
880
  };
@@ -708,17 +882,14 @@ function captureDomSummary(tags) {
708
882
  const formElements = safeQueryAll("form").slice(0, 5);
709
883
  const formInputSet = /* @__PURE__ */ new Set();
710
884
  const forms = formElements.map((form) => {
711
- const inputs = Array.from(form.querySelectorAll("input, textarea, select")).slice(0, 15);
885
+ const inputs = collectEditableControls(form).slice(0, 15);
712
886
  inputs.forEach((el) => formInputSet.add(el));
713
887
  return {
714
888
  id: form.id || form.getAttribute("name") || null,
715
889
  inputs: inputs.map(mapInput)
716
890
  };
717
891
  });
718
- const standaloneInputs = safeQueryAll("input, textarea, select").filter((el) => !el.closest("form") && !formInputSet.has(el)).filter((el) => {
719
- const type = el.type;
720
- return type !== "hidden";
721
- }).slice(0, 20).map(mapInput);
892
+ const standaloneInputs = collectEditableControls(document).filter((el) => !el.closest("form") && !formInputSet.has(el)).slice(0, 20).map(mapInput);
722
893
  if (standaloneInputs.length > 0) {
723
894
  forms.push({ id: "__standalone__", inputs: standaloneInputs });
724
895
  }
@@ -3358,6 +3529,9 @@ function compactTourForTransport(tour) {
3358
3529
  };
3359
3530
  }
3360
3531
 
3532
+ // src/hooks/useTourPlayback.ts
3533
+ init_editable_controls();
3534
+
3361
3535
  // src/utils/tour-playback-guards.ts
3362
3536
  var playbackOwners = /* @__PURE__ */ new Map();
3363
3537
  function shouldExecuteTourCommandBatch(isPlaybackActive) {
@@ -3699,8 +3873,8 @@ async function performInteractiveFill(target, value) {
3699
3873
  inputTarget.dispatchEvent(new Event("change", { bubbles: true }));
3700
3874
  inputTarget.blur();
3701
3875
  } else {
3702
- const shadowInput = findInputInShadowRoot(target);
3703
- if (shadowInput) {
3876
+ const shadowInput = findEditableControlInShadowRoot(target);
3877
+ if (shadowInput && isValueBearingElement(shadowInput)) {
3704
3878
  const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
3705
3879
  if (nativeSetter) nativeSetter.call(shadowInput, value);
3706
3880
  else shadowInput.value = value;
@@ -3714,19 +3888,6 @@ async function performInteractiveFill(target, value) {
3714
3888
  await new Promise((resolve) => setTimeout(resolve, 150));
3715
3889
  inputTarget.classList.remove("modelnex-agent-type");
3716
3890
  }
3717
- function findInputInShadowRoot(root) {
3718
- if (root.shadowRoot) {
3719
- const found = root.shadowRoot.querySelector('input:not([type="hidden"]):not([disabled]), textarea:not([disabled])');
3720
- if (found) return found;
3721
- }
3722
- for (const child of Array.from(root.children)) {
3723
- if (child instanceof HTMLElement) {
3724
- const found = findInputInShadowRoot(child);
3725
- if (found) return found;
3726
- }
3727
- }
3728
- return null;
3729
- }
3730
3891
  function isTerminalAction(action) {
3731
3892
  return [
3732
3893
  "click_element",
@@ -3737,11 +3898,6 @@ function isTerminalAction(action) {
3737
3898
  "end_tour"
3738
3899
  ].includes(action?.type);
3739
3900
  }
3740
- function isValueBearingElement(element) {
3741
- return Boolean(
3742
- element && (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)
3743
- );
3744
- }
3745
3901
  var DEFAULT_TEXT_INPUT_IDLE_COMMIT_MS = 700;
3746
3902
  var DEFAULT_SELECTION_INPUT_IDLE_COMMIT_MS = 250;
3747
3903
  function getManualInputCommitPolicy(target) {
@@ -3757,59 +3913,10 @@ function getManualInputCommitPolicy(target) {
3757
3913
  };
3758
3914
  }
3759
3915
  function resolveWaitTargetElement(element) {
3760
- if (isValueBearingElement(element) || element.isContentEditable) {
3761
- return element;
3762
- }
3763
- if (element.getAttribute("role") === "textbox" || element.getAttribute("role") === "combobox") {
3764
- return element;
3765
- }
3766
- if (element instanceof HTMLLabelElement && element.control instanceof HTMLElement) {
3767
- return element.control;
3768
- }
3769
- const nestedControl = element.querySelector(
3770
- 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"], [role="textbox"], [role="combobox"]'
3771
- );
3772
- return nestedControl ?? element;
3916
+ return resolveEditableControlElement(element);
3773
3917
  }
3774
3918
  function readWaitTargetValue(element) {
3775
- if (isValueBearingElement(element)) {
3776
- return element.value.trim();
3777
- }
3778
- const genericValue = element.value;
3779
- if (typeof genericValue === "string") {
3780
- return genericValue.trim();
3781
- }
3782
- if (typeof genericValue === "number" && Number.isFinite(genericValue)) {
3783
- return String(genericValue).trim();
3784
- }
3785
- if (element.isContentEditable) {
3786
- return (element.textContent || "").trim();
3787
- }
3788
- const ariaValueText = element.getAttribute("aria-valuetext");
3789
- if (typeof ariaValueText === "string" && ariaValueText.trim()) {
3790
- return ariaValueText.trim();
3791
- }
3792
- const ariaValueNow = element.getAttribute("aria-valuenow");
3793
- if (typeof ariaValueNow === "string" && ariaValueNow.trim()) {
3794
- return ariaValueNow.trim();
3795
- }
3796
- if (element.getAttribute("role") === "textbox" || element.getAttribute("role") === "combobox") {
3797
- const nestedControl = element.querySelector(
3798
- 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"], [role="textbox"], [role="combobox"]'
3799
- );
3800
- if (nestedControl && nestedControl !== element) {
3801
- const nestedValue = readWaitTargetValue(nestedControl);
3802
- if (nestedValue) {
3803
- return nestedValue;
3804
- }
3805
- }
3806
- return (element.textContent || "").trim();
3807
- }
3808
- const shadowInput = findInputInShadowRoot(element);
3809
- if (shadowInput) {
3810
- return shadowInput.value.trim();
3811
- }
3812
- return "";
3919
+ return readEditableControlValue(element, { maskPasswords: false });
3813
3920
  }
3814
3921
  function buildManualCompletionTranscript(step, eventName) {
3815
3922
  const targetLabel = step?.element?.textContaining || step?.ask || step?.goal || step?.narration || "the highlighted step";
@@ -3826,7 +3933,7 @@ function isEditableWaitTarget(element) {
3826
3933
  const target = resolveWaitTargetElement(element);
3827
3934
  return isValueBearingElement(target) || target.isContentEditable || target.getAttribute("role") === "textbox" || target.getAttribute("role") === "combobox";
3828
3935
  }
3829
- function createManualWaitForTarget(rawTarget, eventName, step) {
3936
+ function createManualWaitForTarget(rawTarget, eventName, step, options = {}) {
3830
3937
  const target = resolveWaitTargetElement(rawTarget);
3831
3938
  const completionTranscript = buildManualCompletionTranscript(step, eventName);
3832
3939
  const isInputLikeEvent = isInputLikeWait(eventName, step);
@@ -3868,6 +3975,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3868
3975
  }
3869
3976
  };
3870
3977
  const scheduleIdleCommit = () => {
3978
+ options.onObservedInput?.();
3871
3979
  const currentValue = readWaitTargetValue(target);
3872
3980
  if (!currentValue) {
3873
3981
  clearIdleCommit();
@@ -3880,6 +3988,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3880
3988
  }, commitPolicy.idleCommitMs);
3881
3989
  };
3882
3990
  const handleImmediateCommitEvent = () => {
3991
+ options.onObservedInput?.();
3883
3992
  const currentValue = readWaitTargetValue(target);
3884
3993
  if (currentValue) {
3885
3994
  clearIdleCommit();
@@ -3895,6 +4004,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3895
4004
  }
3896
4005
  if (typeof MutationObserver !== "undefined") {
3897
4006
  observer = new MutationObserver(() => {
4007
+ options.onObservedInput?.();
3898
4008
  scheduleIdleCommit();
3899
4009
  });
3900
4010
  observer.observe(target, {
@@ -4018,6 +4128,7 @@ function useTourPlayback({
4018
4128
  const voiceInputResolveRef = (0, import_react12.useRef)(null);
4019
4129
  const askOrFillRef = (0, import_react12.useRef)(null);
4020
4130
  const pendingManualWaitCleanupRef = (0, import_react12.useRef)(null);
4131
+ const pendingManualInputSyncRef = (0, import_react12.useRef)(null);
4021
4132
  const llmRespondingRef = (0, import_react12.useRef)(false);
4022
4133
  const interruptedForQuestionRef = (0, import_react12.useRef)(false);
4023
4134
  const pendingInputBufRef = (0, import_react12.useRef)(null);
@@ -4117,6 +4228,7 @@ function useTourPlayback({
4117
4228
  }, { devMode: devModeRef.current });
4118
4229
  runCleanup(pendingManualWaitCleanupRef.current);
4119
4230
  pendingManualWaitCleanupRef.current = null;
4231
+ clearPendingManualInputSync();
4120
4232
  if (voiceInputResolveRef.current) {
4121
4233
  const resolvePendingVoiceInput = voiceInputResolveRef.current;
4122
4234
  voiceInputResolveRef.current = null;
@@ -4503,6 +4615,7 @@ function useTourPlayback({
4503
4615
  const preferredWaitTarget = inputLikeWait ? batchPreferredWaitTarget ?? highlightedWaitTarget : highlightedWaitTarget;
4504
4616
  runCleanup(pendingManualWaitCleanupRef.current);
4505
4617
  pendingManualWaitCleanupRef.current = null;
4618
+ clearPendingManualInputSync();
4506
4619
  if (waitTargetHints) {
4507
4620
  let manualWaitTarget = await resolveTargetElement2(waitTargetHints, currentStep);
4508
4621
  if (inputLikeWait && preferredWaitTarget && manualWaitTarget && manualWaitTarget !== preferredWaitTarget && !isEditableWaitTarget(manualWaitTarget) && isEditableWaitTarget(preferredWaitTarget)) {
@@ -4513,14 +4626,18 @@ function useTourPlayback({
4513
4626
  }, { devMode: devModeRef.current });
4514
4627
  }
4515
4628
  if (manualWaitTarget) {
4516
- const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep);
4629
+ const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep, {
4630
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4631
+ });
4517
4632
  manualWaitPromise = manualWait.promise;
4518
4633
  manualWaitKind = manualWait.kind;
4519
4634
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
4520
4635
  }
4521
4636
  }
4522
4637
  if (!manualWaitPromise && preferredWaitTarget) {
4523
- const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep);
4638
+ const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep, {
4639
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4640
+ });
4524
4641
  manualWaitPromise = manualWait.promise;
4525
4642
  manualWaitKind = manualWait.kind;
4526
4643
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4534,7 +4651,9 @@ function useTourPlayback({
4534
4651
  'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), [contenteditable="true"], [role="textbox"]'
4535
4652
  );
4536
4653
  if (firstInput) {
4537
- const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep);
4654
+ const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep, {
4655
+ onObservedInput: scheduleManualInputSync
4656
+ });
4538
4657
  manualWaitPromise = manualWait.promise;
4539
4658
  manualWaitKind = manualWait.kind;
4540
4659
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4580,6 +4699,7 @@ function useTourPlayback({
4580
4699
  Promise.race([voiceOrTextWaitPromise, manualWaitPromise].filter(Boolean)).then(async (userText) => {
4581
4700
  runCleanup(pendingManualWaitCleanupRef.current);
4582
4701
  pendingManualWaitCleanupRef.current = null;
4702
+ clearPendingManualInputSync();
4583
4703
  voiceInputResolveRef.current = null;
4584
4704
  setPlaybackState("executing");
4585
4705
  const transcript = userText.trim();
@@ -4784,6 +4904,24 @@ function useTourPlayback({
4784
4904
  domSummary: captureDomSummary()
4785
4905
  });
4786
4906
  }, []);
4907
+ const scheduleManualInputSync = (0, import_react12.useCallback)(() => {
4908
+ if (pendingManualInputSyncRef.current) {
4909
+ clearTimeout(pendingManualInputSyncRef.current);
4910
+ }
4911
+ pendingManualInputSyncRef.current = setTimeout(async () => {
4912
+ pendingManualInputSyncRef.current = null;
4913
+ if (!isActiveRef.current) return;
4914
+ const { waitForDomSettle: waitForDomSettle2 } = await Promise.resolve().then(() => (init_dom_sync(), dom_sync_exports));
4915
+ await waitForDomSettle2({ timeoutMs: 600, debounceMs: 100 });
4916
+ await syncAOM();
4917
+ }, 150);
4918
+ }, [syncAOM]);
4919
+ const clearPendingManualInputSync = (0, import_react12.useCallback)(() => {
4920
+ if (pendingManualInputSyncRef.current) {
4921
+ clearTimeout(pendingManualInputSyncRef.current);
4922
+ pendingManualInputSyncRef.current = null;
4923
+ }
4924
+ }, []);
4787
4925
  const interruptExecution = (0, import_react12.useCallback)((transcript) => {
4788
4926
  if (!isSocketWritable(socketRef.current) || !isActiveRef.current) return false;
4789
4927
  if (!commandInFlightRef.current && !voice.isSpeaking) return false;
@@ -4822,6 +4960,7 @@ function useTourPlayback({
4822
4960
  activeCommandBatchIdRef.current = null;
4823
4961
  runCleanup(pendingManualWaitCleanupRef.current);
4824
4962
  pendingManualWaitCleanupRef.current = null;
4963
+ clearPendingManualInputSync();
4825
4964
  removeHighlight();
4826
4965
  removeCaption();
4827
4966
  voice.stopSpeaking();
@@ -4873,6 +5012,7 @@ function useTourPlayback({
4873
5012
  removeCaption();
4874
5013
  runCleanup(pendingManualWaitCleanupRef.current);
4875
5014
  pendingManualWaitCleanupRef.current = null;
5015
+ clearPendingManualInputSync();
4876
5016
  if (!skipRequestedRef.current && userProfile?.userId && tourRef.current) {
4877
5017
  markTourComplete(serverUrl, toursApiBaseRef.current, tourRef.current.id, userProfile.userId, experienceType, websiteId);
4878
5018
  }
package/dist/index.mjs CHANGED
@@ -1,6 +1,15 @@
1
1
  import {
2
- generateMinifiedAOM
3
- } from "./chunk-N65UEB6X.mjs";
2
+ collectEditableControls,
3
+ findEditableControlInShadowRoot,
4
+ generateMinifiedAOM,
5
+ isEditableControlDisabled,
6
+ isValueBearingElement,
7
+ readEditableControlName,
8
+ readEditableControlPlaceholder,
9
+ readEditableControlType,
10
+ readEditableControlValue,
11
+ resolveEditableControlElement
12
+ } from "./chunk-H4LUY7LI.mjs";
4
13
 
5
14
  // src/index.ts
6
15
  import React8, { useState as useState15, useCallback as useCallback14, useEffect as useEffect19, useMemo as useMemo5 } from "react";
@@ -431,8 +440,9 @@ function safeQueryAll(selector) {
431
440
  }
432
441
  }
433
442
  function findInputLabel(el) {
434
- if (el.labels && el.labels.length > 0) {
435
- const labelText = el.labels[0].textContent?.trim();
443
+ const labels = el.labels;
444
+ if (labels && labels.length > 0) {
445
+ const labelText = labels[0].textContent?.trim();
436
446
  if (labelText) return labelText.slice(0, 80);
437
447
  }
438
448
  const ariaLabel = el.getAttribute("aria-label");
@@ -450,11 +460,12 @@ function findInputLabel(el) {
450
460
  }
451
461
  const closestCell = el.closest('td, th, div[role="cell"], div[role="columnheader"]');
452
462
  if (closestCell) {
463
+ const inputType = String(el.type || "").toLowerCase();
453
464
  if (closestCell.tagName === "TH" || closestCell.getAttribute("role") === "columnheader") {
454
- if (el.type === "checkbox") return "Select All";
465
+ if (inputType === "checkbox") return "Select All";
455
466
  }
456
467
  const row = el.closest('tr, div[role="row"]');
457
- if (row && el.type === "checkbox") {
468
+ if (row && inputType === "checkbox") {
458
469
  const cells = row.querySelectorAll('td, div[role="cell"]');
459
470
  for (const cell of cells) {
460
471
  if (cell === closestCell) continue;
@@ -485,11 +496,11 @@ function captureDomSummary(tags) {
485
496
  const mapInput = (input) => {
486
497
  const el = input;
487
498
  return {
488
- name: el.name || el.id || el.getAttribute("aria-label") || "",
489
- type: el.type || el.tagName.toLowerCase(),
490
- value: el.type === "password" ? "***" : (el.value || "").slice(0, 100),
491
- disabled: el.disabled,
492
- placeholder: el.placeholder || "",
499
+ name: readEditableControlName(el),
500
+ type: readEditableControlType(el),
501
+ value: readEditableControlValue(el),
502
+ disabled: isEditableControlDisabled(el),
503
+ placeholder: readEditableControlPlaceholder(el),
493
504
  fingerprint: generateFingerprint(el),
494
505
  label: findInputLabel(el)
495
506
  };
@@ -497,17 +508,14 @@ function captureDomSummary(tags) {
497
508
  const formElements = safeQueryAll("form").slice(0, 5);
498
509
  const formInputSet = /* @__PURE__ */ new Set();
499
510
  const forms = formElements.map((form) => {
500
- const inputs = Array.from(form.querySelectorAll("input, textarea, select")).slice(0, 15);
511
+ const inputs = collectEditableControls(form).slice(0, 15);
501
512
  inputs.forEach((el) => formInputSet.add(el));
502
513
  return {
503
514
  id: form.id || form.getAttribute("name") || null,
504
515
  inputs: inputs.map(mapInput)
505
516
  };
506
517
  });
507
- const standaloneInputs = safeQueryAll("input, textarea, select").filter((el) => !el.closest("form") && !formInputSet.has(el)).filter((el) => {
508
- const type = el.type;
509
- return type !== "hidden";
510
- }).slice(0, 20).map(mapInput);
518
+ const standaloneInputs = collectEditableControls(document).filter((el) => !el.closest("form") && !formInputSet.has(el)).slice(0, 20).map(mapInput);
511
519
  if (standaloneInputs.length > 0) {
512
520
  forms.push({ id: "__standalone__", inputs: standaloneInputs });
513
521
  }
@@ -3488,8 +3496,8 @@ async function performInteractiveFill(target, value) {
3488
3496
  inputTarget.dispatchEvent(new Event("change", { bubbles: true }));
3489
3497
  inputTarget.blur();
3490
3498
  } else {
3491
- const shadowInput = findInputInShadowRoot(target);
3492
- if (shadowInput) {
3499
+ const shadowInput = findEditableControlInShadowRoot(target);
3500
+ if (shadowInput && isValueBearingElement(shadowInput)) {
3493
3501
  const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
3494
3502
  if (nativeSetter) nativeSetter.call(shadowInput, value);
3495
3503
  else shadowInput.value = value;
@@ -3503,19 +3511,6 @@ async function performInteractiveFill(target, value) {
3503
3511
  await new Promise((resolve) => setTimeout(resolve, 150));
3504
3512
  inputTarget.classList.remove("modelnex-agent-type");
3505
3513
  }
3506
- function findInputInShadowRoot(root) {
3507
- if (root.shadowRoot) {
3508
- const found = root.shadowRoot.querySelector('input:not([type="hidden"]):not([disabled]), textarea:not([disabled])');
3509
- if (found) return found;
3510
- }
3511
- for (const child of Array.from(root.children)) {
3512
- if (child instanceof HTMLElement) {
3513
- const found = findInputInShadowRoot(child);
3514
- if (found) return found;
3515
- }
3516
- }
3517
- return null;
3518
- }
3519
3514
  function isTerminalAction(action) {
3520
3515
  return [
3521
3516
  "click_element",
@@ -3526,11 +3521,6 @@ function isTerminalAction(action) {
3526
3521
  "end_tour"
3527
3522
  ].includes(action?.type);
3528
3523
  }
3529
- function isValueBearingElement(element) {
3530
- return Boolean(
3531
- element && (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)
3532
- );
3533
- }
3534
3524
  var DEFAULT_TEXT_INPUT_IDLE_COMMIT_MS = 700;
3535
3525
  var DEFAULT_SELECTION_INPUT_IDLE_COMMIT_MS = 250;
3536
3526
  function getManualInputCommitPolicy(target) {
@@ -3546,59 +3536,10 @@ function getManualInputCommitPolicy(target) {
3546
3536
  };
3547
3537
  }
3548
3538
  function resolveWaitTargetElement(element) {
3549
- if (isValueBearingElement(element) || element.isContentEditable) {
3550
- return element;
3551
- }
3552
- if (element.getAttribute("role") === "textbox" || element.getAttribute("role") === "combobox") {
3553
- return element;
3554
- }
3555
- if (element instanceof HTMLLabelElement && element.control instanceof HTMLElement) {
3556
- return element.control;
3557
- }
3558
- const nestedControl = element.querySelector(
3559
- 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"], [role="textbox"], [role="combobox"]'
3560
- );
3561
- return nestedControl ?? element;
3539
+ return resolveEditableControlElement(element);
3562
3540
  }
3563
3541
  function readWaitTargetValue(element) {
3564
- if (isValueBearingElement(element)) {
3565
- return element.value.trim();
3566
- }
3567
- const genericValue = element.value;
3568
- if (typeof genericValue === "string") {
3569
- return genericValue.trim();
3570
- }
3571
- if (typeof genericValue === "number" && Number.isFinite(genericValue)) {
3572
- return String(genericValue).trim();
3573
- }
3574
- if (element.isContentEditable) {
3575
- return (element.textContent || "").trim();
3576
- }
3577
- const ariaValueText = element.getAttribute("aria-valuetext");
3578
- if (typeof ariaValueText === "string" && ariaValueText.trim()) {
3579
- return ariaValueText.trim();
3580
- }
3581
- const ariaValueNow = element.getAttribute("aria-valuenow");
3582
- if (typeof ariaValueNow === "string" && ariaValueNow.trim()) {
3583
- return ariaValueNow.trim();
3584
- }
3585
- if (element.getAttribute("role") === "textbox" || element.getAttribute("role") === "combobox") {
3586
- const nestedControl = element.querySelector(
3587
- 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"], [role="textbox"], [role="combobox"]'
3588
- );
3589
- if (nestedControl && nestedControl !== element) {
3590
- const nestedValue = readWaitTargetValue(nestedControl);
3591
- if (nestedValue) {
3592
- return nestedValue;
3593
- }
3594
- }
3595
- return (element.textContent || "").trim();
3596
- }
3597
- const shadowInput = findInputInShadowRoot(element);
3598
- if (shadowInput) {
3599
- return shadowInput.value.trim();
3600
- }
3601
- return "";
3542
+ return readEditableControlValue(element, { maskPasswords: false });
3602
3543
  }
3603
3544
  function buildManualCompletionTranscript(step, eventName) {
3604
3545
  const targetLabel = step?.element?.textContaining || step?.ask || step?.goal || step?.narration || "the highlighted step";
@@ -3615,7 +3556,7 @@ function isEditableWaitTarget(element) {
3615
3556
  const target = resolveWaitTargetElement(element);
3616
3557
  return isValueBearingElement(target) || target.isContentEditable || target.getAttribute("role") === "textbox" || target.getAttribute("role") === "combobox";
3617
3558
  }
3618
- function createManualWaitForTarget(rawTarget, eventName, step) {
3559
+ function createManualWaitForTarget(rawTarget, eventName, step, options = {}) {
3619
3560
  const target = resolveWaitTargetElement(rawTarget);
3620
3561
  const completionTranscript = buildManualCompletionTranscript(step, eventName);
3621
3562
  const isInputLikeEvent = isInputLikeWait(eventName, step);
@@ -3657,6 +3598,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3657
3598
  }
3658
3599
  };
3659
3600
  const scheduleIdleCommit = () => {
3601
+ options.onObservedInput?.();
3660
3602
  const currentValue = readWaitTargetValue(target);
3661
3603
  if (!currentValue) {
3662
3604
  clearIdleCommit();
@@ -3669,6 +3611,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3669
3611
  }, commitPolicy.idleCommitMs);
3670
3612
  };
3671
3613
  const handleImmediateCommitEvent = () => {
3614
+ options.onObservedInput?.();
3672
3615
  const currentValue = readWaitTargetValue(target);
3673
3616
  if (currentValue) {
3674
3617
  clearIdleCommit();
@@ -3684,6 +3627,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3684
3627
  }
3685
3628
  if (typeof MutationObserver !== "undefined") {
3686
3629
  observer = new MutationObserver(() => {
3630
+ options.onObservedInput?.();
3687
3631
  scheduleIdleCommit();
3688
3632
  });
3689
3633
  observer.observe(target, {
@@ -3807,6 +3751,7 @@ function useTourPlayback({
3807
3751
  const voiceInputResolveRef = useRef8(null);
3808
3752
  const askOrFillRef = useRef8(null);
3809
3753
  const pendingManualWaitCleanupRef = useRef8(null);
3754
+ const pendingManualInputSyncRef = useRef8(null);
3810
3755
  const llmRespondingRef = useRef8(false);
3811
3756
  const interruptedForQuestionRef = useRef8(false);
3812
3757
  const pendingInputBufRef = useRef8(null);
@@ -3906,6 +3851,7 @@ function useTourPlayback({
3906
3851
  }, { devMode: devModeRef.current });
3907
3852
  runCleanup(pendingManualWaitCleanupRef.current);
3908
3853
  pendingManualWaitCleanupRef.current = null;
3854
+ clearPendingManualInputSync();
3909
3855
  if (voiceInputResolveRef.current) {
3910
3856
  const resolvePendingVoiceInput = voiceInputResolveRef.current;
3911
3857
  voiceInputResolveRef.current = null;
@@ -3991,7 +3937,7 @@ function useTourPlayback({
3991
3937
  resolve: async () => {
3992
3938
  let targetEl = null;
3993
3939
  if (params.uid) {
3994
- const { getElementByUid } = await import("./aom-HDYNCIOY.mjs");
3940
+ const { getElementByUid } = await import("./aom-LJNCLNXL.mjs");
3995
3941
  targetEl = getElementByUid(params.uid);
3996
3942
  }
3997
3943
  if (!targetEl) {
@@ -4292,6 +4238,7 @@ function useTourPlayback({
4292
4238
  const preferredWaitTarget = inputLikeWait ? batchPreferredWaitTarget ?? highlightedWaitTarget : highlightedWaitTarget;
4293
4239
  runCleanup(pendingManualWaitCleanupRef.current);
4294
4240
  pendingManualWaitCleanupRef.current = null;
4241
+ clearPendingManualInputSync();
4295
4242
  if (waitTargetHints) {
4296
4243
  let manualWaitTarget = await resolveTargetElement2(waitTargetHints, currentStep);
4297
4244
  if (inputLikeWait && preferredWaitTarget && manualWaitTarget && manualWaitTarget !== preferredWaitTarget && !isEditableWaitTarget(manualWaitTarget) && isEditableWaitTarget(preferredWaitTarget)) {
@@ -4302,14 +4249,18 @@ function useTourPlayback({
4302
4249
  }, { devMode: devModeRef.current });
4303
4250
  }
4304
4251
  if (manualWaitTarget) {
4305
- const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep);
4252
+ const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep, {
4253
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4254
+ });
4306
4255
  manualWaitPromise = manualWait.promise;
4307
4256
  manualWaitKind = manualWait.kind;
4308
4257
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
4309
4258
  }
4310
4259
  }
4311
4260
  if (!manualWaitPromise && preferredWaitTarget) {
4312
- const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep);
4261
+ const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep, {
4262
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4263
+ });
4313
4264
  manualWaitPromise = manualWait.promise;
4314
4265
  manualWaitKind = manualWait.kind;
4315
4266
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4323,7 +4274,9 @@ function useTourPlayback({
4323
4274
  'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), [contenteditable="true"], [role="textbox"]'
4324
4275
  );
4325
4276
  if (firstInput) {
4326
- const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep);
4277
+ const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep, {
4278
+ onObservedInput: scheduleManualInputSync
4279
+ });
4327
4280
  manualWaitPromise = manualWait.promise;
4328
4281
  manualWaitKind = manualWait.kind;
4329
4282
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4369,6 +4322,7 @@ function useTourPlayback({
4369
4322
  Promise.race([voiceOrTextWaitPromise, manualWaitPromise].filter(Boolean)).then(async (userText) => {
4370
4323
  runCleanup(pendingManualWaitCleanupRef.current);
4371
4324
  pendingManualWaitCleanupRef.current = null;
4325
+ clearPendingManualInputSync();
4372
4326
  voiceInputResolveRef.current = null;
4373
4327
  setPlaybackState("executing");
4374
4328
  const transcript = userText.trim();
@@ -4451,7 +4405,7 @@ function useTourPlayback({
4451
4405
  void recordTourEvent(serverUrl, toursApiBaseRef.current, tour.id, userProfile.userId, "started", websiteId);
4452
4406
  }
4453
4407
  try {
4454
- const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-HDYNCIOY.mjs");
4408
+ const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-LJNCLNXL.mjs");
4455
4409
  const aom = generateMinifiedAOM2();
4456
4410
  if (socketRef.current === socket) {
4457
4411
  emitSocketEvent(socket, "tour:sync_dom", {
@@ -4565,7 +4519,7 @@ function useTourPlayback({
4565
4519
  }, [isActive, playbackState, voice.isListening, voice.isSpeaking]);
4566
4520
  const syncAOM = useCallback7(async () => {
4567
4521
  if (!isActiveRef.current) return;
4568
- const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-HDYNCIOY.mjs");
4522
+ const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-LJNCLNXL.mjs");
4569
4523
  const aom = generateMinifiedAOM2();
4570
4524
  emitSocketEvent(socketRef.current, "tour:sync_dom", {
4571
4525
  url: window.location.pathname + window.location.search + window.location.hash,
@@ -4573,6 +4527,24 @@ function useTourPlayback({
4573
4527
  domSummary: captureDomSummary()
4574
4528
  });
4575
4529
  }, []);
4530
+ const scheduleManualInputSync = useCallback7(() => {
4531
+ if (pendingManualInputSyncRef.current) {
4532
+ clearTimeout(pendingManualInputSyncRef.current);
4533
+ }
4534
+ pendingManualInputSyncRef.current = setTimeout(async () => {
4535
+ pendingManualInputSyncRef.current = null;
4536
+ if (!isActiveRef.current) return;
4537
+ const { waitForDomSettle } = await import("./dom-sync-GABDEODR.mjs");
4538
+ await waitForDomSettle({ timeoutMs: 600, debounceMs: 100 });
4539
+ await syncAOM();
4540
+ }, 150);
4541
+ }, [syncAOM]);
4542
+ const clearPendingManualInputSync = useCallback7(() => {
4543
+ if (pendingManualInputSyncRef.current) {
4544
+ clearTimeout(pendingManualInputSyncRef.current);
4545
+ pendingManualInputSyncRef.current = null;
4546
+ }
4547
+ }, []);
4576
4548
  const interruptExecution = useCallback7((transcript) => {
4577
4549
  if (!isSocketWritable(socketRef.current) || !isActiveRef.current) return false;
4578
4550
  if (!commandInFlightRef.current && !voice.isSpeaking) return false;
@@ -4611,6 +4583,7 @@ function useTourPlayback({
4611
4583
  activeCommandBatchIdRef.current = null;
4612
4584
  runCleanup(pendingManualWaitCleanupRef.current);
4613
4585
  pendingManualWaitCleanupRef.current = null;
4586
+ clearPendingManualInputSync();
4614
4587
  removeHighlight();
4615
4588
  removeCaption();
4616
4589
  voice.stopSpeaking();
@@ -4662,6 +4635,7 @@ function useTourPlayback({
4662
4635
  removeCaption();
4663
4636
  runCleanup(pendingManualWaitCleanupRef.current);
4664
4637
  pendingManualWaitCleanupRef.current = null;
4638
+ clearPendingManualInputSync();
4665
4639
  if (!skipRequestedRef.current && userProfile?.userId && tourRef.current) {
4666
4640
  markTourComplete(serverUrl, toursApiBaseRef.current, tourRef.current.id, userProfile.userId, experienceType, websiteId);
4667
4641
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelnex/sdk",
3
- "version": "0.5.40",
3
+ "version": "0.5.41",
4
4
  "description": "React SDK for natural language control of web apps via AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -1,70 +0,0 @@
1
- // src/utils/aom.ts
2
- var uidMap = /* @__PURE__ */ new Map();
3
- var nextUid = 1;
4
- function generateMinifiedAOM() {
5
- uidMap.clear();
6
- nextUid = 1;
7
- const interactives = document.querySelectorAll(
8
- 'button, a, input, select, textarea, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"]'
9
- );
10
- const nodes = [];
11
- interactives.forEach((el) => {
12
- if (!el.offsetParent && (el.offsetWidth === 0 || el.offsetHeight === 0)) {
13
- return;
14
- }
15
- if (el.closest("#modelnex-studio-root") || el.closest("#modelnex-active-agent-root")) {
16
- return;
17
- }
18
- const uid = `node:${nextUid++}`;
19
- uidMap.set(uid, el);
20
- let text = (el.textContent || "").replace(/\s+/g, " ").trim();
21
- const ariaLabel = el.getAttribute("aria-label");
22
- const placeholder = el.getAttribute("placeholder");
23
- let value = void 0;
24
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
25
- value = el.value;
26
- }
27
- let role = el.tagName.toLowerCase();
28
- if (el.hasAttribute("role")) {
29
- role = el.getAttribute("role");
30
- } else if (role === "a") {
31
- role = "link";
32
- } else if (el instanceof HTMLInputElement) {
33
- role = el.type ? `input[${el.type}]` : "input";
34
- }
35
- const node = { uid, role };
36
- const displayLabel = ariaLabel || text || placeholder;
37
- if (displayLabel) {
38
- node.text = displayLabel.substring(0, 100);
39
- }
40
- if (el.getAttribute("name")) {
41
- node.name = el.getAttribute("name");
42
- }
43
- if (value) {
44
- node.value = value.substring(0, 100);
45
- }
46
- if (el instanceof HTMLAnchorElement && el.href) {
47
- try {
48
- const url = new URL(el.href);
49
- node.href = url.pathname + url.search + url.hash;
50
- } catch {
51
- node.href = el.getAttribute("href");
52
- }
53
- }
54
- nodes.push(node);
55
- });
56
- return { nodes };
57
- }
58
- function getElementByUid(uid) {
59
- return uidMap.get(uid) || null;
60
- }
61
- function clearAOMMap() {
62
- uidMap.clear();
63
- nextUid = 1;
64
- }
65
-
66
- export {
67
- generateMinifiedAOM,
68
- getElementByUid,
69
- clearAOMMap
70
- };