@modelnex/sdk 0.5.39 → 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,34 +3898,25 @@ 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
- );
3901
+ var DEFAULT_TEXT_INPUT_IDLE_COMMIT_MS = 700;
3902
+ var DEFAULT_SELECTION_INPUT_IDLE_COMMIT_MS = 250;
3903
+ function getManualInputCommitPolicy(target) {
3904
+ const tagName = String(target.tagName || "").toLowerCase();
3905
+ const role = String(target.role || "").toLowerCase();
3906
+ const inputType = String(target.type || "").toLowerCase();
3907
+ const isContentEditable = Boolean(target.isContentEditable);
3908
+ const isSelectionLike = tagName === "select" || role === "combobox" || ["checkbox", "radio", "range", "color", "date", "datetime-local", "month", "time", "week"].includes(inputType);
3909
+ return {
3910
+ idleCommitMs: isSelectionLike ? DEFAULT_SELECTION_INPUT_IDLE_COMMIT_MS : DEFAULT_TEXT_INPUT_IDLE_COMMIT_MS,
3911
+ commitOnBlur: !isSelectionLike || tagName === "select" || role === "combobox" || isContentEditable,
3912
+ commitOnChange: true
3913
+ };
3744
3914
  }
3745
3915
  function resolveWaitTargetElement(element) {
3746
- if (isValueBearingElement(element) || element.isContentEditable) {
3747
- return element;
3748
- }
3749
- if (element.getAttribute("role") === "textbox" || element.getAttribute("role") === "combobox") {
3750
- return element;
3751
- }
3752
- if (element instanceof HTMLLabelElement && element.control instanceof HTMLElement) {
3753
- return element.control;
3754
- }
3755
- const nestedControl = element.querySelector(
3756
- 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"], [role="textbox"], [role="combobox"]'
3757
- );
3758
- return nestedControl ?? element;
3916
+ return resolveEditableControlElement(element);
3759
3917
  }
3760
3918
  function readWaitTargetValue(element) {
3761
- if (isValueBearingElement(element)) {
3762
- return element.value.trim();
3763
- }
3764
- if (element.isContentEditable) {
3765
- return (element.textContent || "").trim();
3766
- }
3767
- return "";
3919
+ return readEditableControlValue(element, { maskPasswords: false });
3768
3920
  }
3769
3921
  function buildManualCompletionTranscript(step, eventName) {
3770
3922
  const targetLabel = step?.element?.textContaining || step?.ask || step?.goal || step?.narration || "the highlighted step";
@@ -3781,10 +3933,16 @@ function isEditableWaitTarget(element) {
3781
3933
  const target = resolveWaitTargetElement(element);
3782
3934
  return isValueBearingElement(target) || target.isContentEditable || target.getAttribute("role") === "textbox" || target.getAttribute("role") === "combobox";
3783
3935
  }
3784
- function createManualWaitForTarget(rawTarget, eventName, step) {
3936
+ function createManualWaitForTarget(rawTarget, eventName, step, options = {}) {
3785
3937
  const target = resolveWaitTargetElement(rawTarget);
3786
3938
  const completionTranscript = buildManualCompletionTranscript(step, eventName);
3787
3939
  const isInputLikeEvent = isInputLikeWait(eventName, step);
3940
+ const commitPolicy = getManualInputCommitPolicy({
3941
+ tagName: target.tagName,
3942
+ role: target.getAttribute("role"),
3943
+ type: target.type ?? null,
3944
+ isContentEditable: target.isContentEditable
3945
+ });
3788
3946
  if (isInputLikeEvent && readWaitTargetValue(target)) {
3789
3947
  const initialValue = readWaitTargetValue(target);
3790
3948
  return {
@@ -3796,6 +3954,8 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3796
3954
  let cleanup = () => void 0;
3797
3955
  const promise = new Promise((resolve) => {
3798
3956
  const listeners2 = [];
3957
+ let observer = null;
3958
+ let idleTimer = null;
3799
3959
  let settled = false;
3800
3960
  const finish = () => {
3801
3961
  if (settled) return;
@@ -3808,20 +3968,64 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3808
3968
  listeners2.push({ node, type, handler });
3809
3969
  };
3810
3970
  if (isInputLikeEvent) {
3811
- const handleInputLikeEvent = () => {
3971
+ const clearIdleCommit = () => {
3972
+ if (idleTimer) {
3973
+ clearTimeout(idleTimer);
3974
+ idleTimer = null;
3975
+ }
3976
+ };
3977
+ const scheduleIdleCommit = () => {
3978
+ options.onObservedInput?.();
3979
+ const currentValue = readWaitTargetValue(target);
3980
+ if (!currentValue) {
3981
+ clearIdleCommit();
3982
+ return;
3983
+ }
3984
+ clearIdleCommit();
3985
+ idleTimer = setTimeout(() => {
3986
+ idleTimer = null;
3987
+ finish();
3988
+ }, commitPolicy.idleCommitMs);
3989
+ };
3990
+ const handleImmediateCommitEvent = () => {
3991
+ options.onObservedInput?.();
3812
3992
  const currentValue = readWaitTargetValue(target);
3813
3993
  if (currentValue) {
3994
+ clearIdleCommit();
3814
3995
  finish();
3815
3996
  }
3816
3997
  };
3817
- addListener(target, "input", handleInputLikeEvent);
3818
- addListener(target, "change", handleInputLikeEvent);
3819
- addListener(target, "blur", handleInputLikeEvent);
3998
+ addListener(target, "input", scheduleIdleCommit);
3999
+ if (commitPolicy.commitOnChange) {
4000
+ addListener(target, "change", handleImmediateCommitEvent);
4001
+ }
4002
+ if (commitPolicy.commitOnBlur) {
4003
+ addListener(target, "blur", handleImmediateCommitEvent);
4004
+ }
4005
+ if (typeof MutationObserver !== "undefined") {
4006
+ observer = new MutationObserver(() => {
4007
+ options.onObservedInput?.();
4008
+ scheduleIdleCommit();
4009
+ });
4010
+ observer.observe(target, {
4011
+ subtree: true,
4012
+ childList: true,
4013
+ characterData: true,
4014
+ attributes: true,
4015
+ attributeFilter: ["value", "aria-valuetext", "aria-valuenow", "aria-activedescendant"]
4016
+ });
4017
+ }
3820
4018
  } else {
3821
4019
  const handleActionEvent = () => finish();
3822
4020
  addListener(target, eventName, handleActionEvent);
3823
4021
  }
3824
4022
  cleanup = () => {
4023
+ if (idleTimer) {
4024
+ clearTimeout(idleTimer);
4025
+ idleTimer = null;
4026
+ }
4027
+ observer?.disconnect();
4028
+ observer = null;
3825
4029
  listeners2.forEach(({ node, type, handler }) => node.removeEventListener(type, handler));
3826
4030
  listeners2.length = 0;
3827
4031
  };
@@ -3924,6 +4128,7 @@ function useTourPlayback({
3924
4128
  const voiceInputResolveRef = (0, import_react12.useRef)(null);
3925
4129
  const askOrFillRef = (0, import_react12.useRef)(null);
3926
4130
  const pendingManualWaitCleanupRef = (0, import_react12.useRef)(null);
4131
+ const pendingManualInputSyncRef = (0, import_react12.useRef)(null);
3927
4132
  const llmRespondingRef = (0, import_react12.useRef)(false);
3928
4133
  const interruptedForQuestionRef = (0, import_react12.useRef)(false);
3929
4134
  const pendingInputBufRef = (0, import_react12.useRef)(null);
@@ -4023,6 +4228,7 @@ function useTourPlayback({
4023
4228
  }, { devMode: devModeRef.current });
4024
4229
  runCleanup(pendingManualWaitCleanupRef.current);
4025
4230
  pendingManualWaitCleanupRef.current = null;
4231
+ clearPendingManualInputSync();
4026
4232
  if (voiceInputResolveRef.current) {
4027
4233
  const resolvePendingVoiceInput = voiceInputResolveRef.current;
4028
4234
  voiceInputResolveRef.current = null;
@@ -4409,6 +4615,7 @@ function useTourPlayback({
4409
4615
  const preferredWaitTarget = inputLikeWait ? batchPreferredWaitTarget ?? highlightedWaitTarget : highlightedWaitTarget;
4410
4616
  runCleanup(pendingManualWaitCleanupRef.current);
4411
4617
  pendingManualWaitCleanupRef.current = null;
4618
+ clearPendingManualInputSync();
4412
4619
  if (waitTargetHints) {
4413
4620
  let manualWaitTarget = await resolveTargetElement2(waitTargetHints, currentStep);
4414
4621
  if (inputLikeWait && preferredWaitTarget && manualWaitTarget && manualWaitTarget !== preferredWaitTarget && !isEditableWaitTarget(manualWaitTarget) && isEditableWaitTarget(preferredWaitTarget)) {
@@ -4419,14 +4626,18 @@ function useTourPlayback({
4419
4626
  }, { devMode: devModeRef.current });
4420
4627
  }
4421
4628
  if (manualWaitTarget) {
4422
- const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep);
4629
+ const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep, {
4630
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4631
+ });
4423
4632
  manualWaitPromise = manualWait.promise;
4424
4633
  manualWaitKind = manualWait.kind;
4425
4634
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
4426
4635
  }
4427
4636
  }
4428
4637
  if (!manualWaitPromise && preferredWaitTarget) {
4429
- const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep);
4638
+ const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep, {
4639
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4640
+ });
4430
4641
  manualWaitPromise = manualWait.promise;
4431
4642
  manualWaitKind = manualWait.kind;
4432
4643
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4440,7 +4651,9 @@ function useTourPlayback({
4440
4651
  'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), [contenteditable="true"], [role="textbox"]'
4441
4652
  );
4442
4653
  if (firstInput) {
4443
- const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep);
4654
+ const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep, {
4655
+ onObservedInput: scheduleManualInputSync
4656
+ });
4444
4657
  manualWaitPromise = manualWait.promise;
4445
4658
  manualWaitKind = manualWait.kind;
4446
4659
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4486,6 +4699,7 @@ function useTourPlayback({
4486
4699
  Promise.race([voiceOrTextWaitPromise, manualWaitPromise].filter(Boolean)).then(async (userText) => {
4487
4700
  runCleanup(pendingManualWaitCleanupRef.current);
4488
4701
  pendingManualWaitCleanupRef.current = null;
4702
+ clearPendingManualInputSync();
4489
4703
  voiceInputResolveRef.current = null;
4490
4704
  setPlaybackState("executing");
4491
4705
  const transcript = userText.trim();
@@ -4690,6 +4904,24 @@ function useTourPlayback({
4690
4904
  domSummary: captureDomSummary()
4691
4905
  });
4692
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
+ }, []);
4693
4925
  const interruptExecution = (0, import_react12.useCallback)((transcript) => {
4694
4926
  if (!isSocketWritable(socketRef.current) || !isActiveRef.current) return false;
4695
4927
  if (!commandInFlightRef.current && !voice.isSpeaking) return false;
@@ -4728,6 +4960,7 @@ function useTourPlayback({
4728
4960
  activeCommandBatchIdRef.current = null;
4729
4961
  runCleanup(pendingManualWaitCleanupRef.current);
4730
4962
  pendingManualWaitCleanupRef.current = null;
4963
+ clearPendingManualInputSync();
4731
4964
  removeHighlight();
4732
4965
  removeCaption();
4733
4966
  voice.stopSpeaking();
@@ -4779,6 +5012,7 @@ function useTourPlayback({
4779
5012
  removeCaption();
4780
5013
  runCleanup(pendingManualWaitCleanupRef.current);
4781
5014
  pendingManualWaitCleanupRef.current = null;
5015
+ clearPendingManualInputSync();
4782
5016
  if (!skipRequestedRef.current && userProfile?.userId && tourRef.current) {
4783
5017
  markTourComplete(serverUrl, toursApiBaseRef.current, tourRef.current.id, userProfile.userId, experienceType, websiteId);
4784
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,34 +3521,25 @@ 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
- );
3524
+ var DEFAULT_TEXT_INPUT_IDLE_COMMIT_MS = 700;
3525
+ var DEFAULT_SELECTION_INPUT_IDLE_COMMIT_MS = 250;
3526
+ function getManualInputCommitPolicy(target) {
3527
+ const tagName = String(target.tagName || "").toLowerCase();
3528
+ const role = String(target.role || "").toLowerCase();
3529
+ const inputType = String(target.type || "").toLowerCase();
3530
+ const isContentEditable = Boolean(target.isContentEditable);
3531
+ const isSelectionLike = tagName === "select" || role === "combobox" || ["checkbox", "radio", "range", "color", "date", "datetime-local", "month", "time", "week"].includes(inputType);
3532
+ return {
3533
+ idleCommitMs: isSelectionLike ? DEFAULT_SELECTION_INPUT_IDLE_COMMIT_MS : DEFAULT_TEXT_INPUT_IDLE_COMMIT_MS,
3534
+ commitOnBlur: !isSelectionLike || tagName === "select" || role === "combobox" || isContentEditable,
3535
+ commitOnChange: true
3536
+ };
3533
3537
  }
3534
3538
  function resolveWaitTargetElement(element) {
3535
- if (isValueBearingElement(element) || element.isContentEditable) {
3536
- return element;
3537
- }
3538
- if (element.getAttribute("role") === "textbox" || element.getAttribute("role") === "combobox") {
3539
- return element;
3540
- }
3541
- if (element instanceof HTMLLabelElement && element.control instanceof HTMLElement) {
3542
- return element.control;
3543
- }
3544
- const nestedControl = element.querySelector(
3545
- 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"], [role="textbox"], [role="combobox"]'
3546
- );
3547
- return nestedControl ?? element;
3539
+ return resolveEditableControlElement(element);
3548
3540
  }
3549
3541
  function readWaitTargetValue(element) {
3550
- if (isValueBearingElement(element)) {
3551
- return element.value.trim();
3552
- }
3553
- if (element.isContentEditable) {
3554
- return (element.textContent || "").trim();
3555
- }
3556
- return "";
3542
+ return readEditableControlValue(element, { maskPasswords: false });
3557
3543
  }
3558
3544
  function buildManualCompletionTranscript(step, eventName) {
3559
3545
  const targetLabel = step?.element?.textContaining || step?.ask || step?.goal || step?.narration || "the highlighted step";
@@ -3570,10 +3556,16 @@ function isEditableWaitTarget(element) {
3570
3556
  const target = resolveWaitTargetElement(element);
3571
3557
  return isValueBearingElement(target) || target.isContentEditable || target.getAttribute("role") === "textbox" || target.getAttribute("role") === "combobox";
3572
3558
  }
3573
- function createManualWaitForTarget(rawTarget, eventName, step) {
3559
+ function createManualWaitForTarget(rawTarget, eventName, step, options = {}) {
3574
3560
  const target = resolveWaitTargetElement(rawTarget);
3575
3561
  const completionTranscript = buildManualCompletionTranscript(step, eventName);
3576
3562
  const isInputLikeEvent = isInputLikeWait(eventName, step);
3563
+ const commitPolicy = getManualInputCommitPolicy({
3564
+ tagName: target.tagName,
3565
+ role: target.getAttribute("role"),
3566
+ type: target.type ?? null,
3567
+ isContentEditable: target.isContentEditable
3568
+ });
3577
3569
  if (isInputLikeEvent && readWaitTargetValue(target)) {
3578
3570
  const initialValue = readWaitTargetValue(target);
3579
3571
  return {
@@ -3585,6 +3577,8 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3585
3577
  let cleanup = () => void 0;
3586
3578
  const promise = new Promise((resolve) => {
3587
3579
  const listeners2 = [];
3580
+ let observer = null;
3581
+ let idleTimer = null;
3588
3582
  let settled = false;
3589
3583
  const finish = () => {
3590
3584
  if (settled) return;
@@ -3597,20 +3591,64 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3597
3591
  listeners2.push({ node, type, handler });
3598
3592
  };
3599
3593
  if (isInputLikeEvent) {
3600
- const handleInputLikeEvent = () => {
3594
+ const clearIdleCommit = () => {
3595
+ if (idleTimer) {
3596
+ clearTimeout(idleTimer);
3597
+ idleTimer = null;
3598
+ }
3599
+ };
3600
+ const scheduleIdleCommit = () => {
3601
+ options.onObservedInput?.();
3602
+ const currentValue = readWaitTargetValue(target);
3603
+ if (!currentValue) {
3604
+ clearIdleCommit();
3605
+ return;
3606
+ }
3607
+ clearIdleCommit();
3608
+ idleTimer = setTimeout(() => {
3609
+ idleTimer = null;
3610
+ finish();
3611
+ }, commitPolicy.idleCommitMs);
3612
+ };
3613
+ const handleImmediateCommitEvent = () => {
3614
+ options.onObservedInput?.();
3601
3615
  const currentValue = readWaitTargetValue(target);
3602
3616
  if (currentValue) {
3617
+ clearIdleCommit();
3603
3618
  finish();
3604
3619
  }
3605
3620
  };
3606
- addListener(target, "input", handleInputLikeEvent);
3607
- addListener(target, "change", handleInputLikeEvent);
3608
- addListener(target, "blur", handleInputLikeEvent);
3621
+ addListener(target, "input", scheduleIdleCommit);
3622
+ if (commitPolicy.commitOnChange) {
3623
+ addListener(target, "change", handleImmediateCommitEvent);
3624
+ }
3625
+ if (commitPolicy.commitOnBlur) {
3626
+ addListener(target, "blur", handleImmediateCommitEvent);
3627
+ }
3628
+ if (typeof MutationObserver !== "undefined") {
3629
+ observer = new MutationObserver(() => {
3630
+ options.onObservedInput?.();
3631
+ scheduleIdleCommit();
3632
+ });
3633
+ observer.observe(target, {
3634
+ subtree: true,
3635
+ childList: true,
3636
+ characterData: true,
3637
+ attributes: true,
3638
+ attributeFilter: ["value", "aria-valuetext", "aria-valuenow", "aria-activedescendant"]
3639
+ });
3640
+ }
3609
3641
  } else {
3610
3642
  const handleActionEvent = () => finish();
3611
3643
  addListener(target, eventName, handleActionEvent);
3612
3644
  }
3613
3645
  cleanup = () => {
3646
+ if (idleTimer) {
3647
+ clearTimeout(idleTimer);
3648
+ idleTimer = null;
3649
+ }
3650
+ observer?.disconnect();
3651
+ observer = null;
3614
3652
  listeners2.forEach(({ node, type, handler }) => node.removeEventListener(type, handler));
3615
3653
  listeners2.length = 0;
3616
3654
  };
@@ -3713,6 +3751,7 @@ function useTourPlayback({
3713
3751
  const voiceInputResolveRef = useRef8(null);
3714
3752
  const askOrFillRef = useRef8(null);
3715
3753
  const pendingManualWaitCleanupRef = useRef8(null);
3754
+ const pendingManualInputSyncRef = useRef8(null);
3716
3755
  const llmRespondingRef = useRef8(false);
3717
3756
  const interruptedForQuestionRef = useRef8(false);
3718
3757
  const pendingInputBufRef = useRef8(null);
@@ -3812,6 +3851,7 @@ function useTourPlayback({
3812
3851
  }, { devMode: devModeRef.current });
3813
3852
  runCleanup(pendingManualWaitCleanupRef.current);
3814
3853
  pendingManualWaitCleanupRef.current = null;
3854
+ clearPendingManualInputSync();
3815
3855
  if (voiceInputResolveRef.current) {
3816
3856
  const resolvePendingVoiceInput = voiceInputResolveRef.current;
3817
3857
  voiceInputResolveRef.current = null;
@@ -3897,7 +3937,7 @@ function useTourPlayback({
3897
3937
  resolve: async () => {
3898
3938
  let targetEl = null;
3899
3939
  if (params.uid) {
3900
- const { getElementByUid } = await import("./aom-HDYNCIOY.mjs");
3940
+ const { getElementByUid } = await import("./aom-LJNCLNXL.mjs");
3901
3941
  targetEl = getElementByUid(params.uid);
3902
3942
  }
3903
3943
  if (!targetEl) {
@@ -4198,6 +4238,7 @@ function useTourPlayback({
4198
4238
  const preferredWaitTarget = inputLikeWait ? batchPreferredWaitTarget ?? highlightedWaitTarget : highlightedWaitTarget;
4199
4239
  runCleanup(pendingManualWaitCleanupRef.current);
4200
4240
  pendingManualWaitCleanupRef.current = null;
4241
+ clearPendingManualInputSync();
4201
4242
  if (waitTargetHints) {
4202
4243
  let manualWaitTarget = await resolveTargetElement2(waitTargetHints, currentStep);
4203
4244
  if (inputLikeWait && preferredWaitTarget && manualWaitTarget && manualWaitTarget !== preferredWaitTarget && !isEditableWaitTarget(manualWaitTarget) && isEditableWaitTarget(preferredWaitTarget)) {
@@ -4208,14 +4249,18 @@ function useTourPlayback({
4208
4249
  }, { devMode: devModeRef.current });
4209
4250
  }
4210
4251
  if (manualWaitTarget) {
4211
- const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep);
4252
+ const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep, {
4253
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4254
+ });
4212
4255
  manualWaitPromise = manualWait.promise;
4213
4256
  manualWaitKind = manualWait.kind;
4214
4257
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
4215
4258
  }
4216
4259
  }
4217
4260
  if (!manualWaitPromise && preferredWaitTarget) {
4218
- const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep);
4261
+ const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep, {
4262
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4263
+ });
4219
4264
  manualWaitPromise = manualWait.promise;
4220
4265
  manualWaitKind = manualWait.kind;
4221
4266
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4229,7 +4274,9 @@ function useTourPlayback({
4229
4274
  'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), [contenteditable="true"], [role="textbox"]'
4230
4275
  );
4231
4276
  if (firstInput) {
4232
- const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep);
4277
+ const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep, {
4278
+ onObservedInput: scheduleManualInputSync
4279
+ });
4233
4280
  manualWaitPromise = manualWait.promise;
4234
4281
  manualWaitKind = manualWait.kind;
4235
4282
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4275,6 +4322,7 @@ function useTourPlayback({
4275
4322
  Promise.race([voiceOrTextWaitPromise, manualWaitPromise].filter(Boolean)).then(async (userText) => {
4276
4323
  runCleanup(pendingManualWaitCleanupRef.current);
4277
4324
  pendingManualWaitCleanupRef.current = null;
4325
+ clearPendingManualInputSync();
4278
4326
  voiceInputResolveRef.current = null;
4279
4327
  setPlaybackState("executing");
4280
4328
  const transcript = userText.trim();
@@ -4357,7 +4405,7 @@ function useTourPlayback({
4357
4405
  void recordTourEvent(serverUrl, toursApiBaseRef.current, tour.id, userProfile.userId, "started", websiteId);
4358
4406
  }
4359
4407
  try {
4360
- const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-HDYNCIOY.mjs");
4408
+ const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-LJNCLNXL.mjs");
4361
4409
  const aom = generateMinifiedAOM2();
4362
4410
  if (socketRef.current === socket) {
4363
4411
  emitSocketEvent(socket, "tour:sync_dom", {
@@ -4471,7 +4519,7 @@ function useTourPlayback({
4471
4519
  }, [isActive, playbackState, voice.isListening, voice.isSpeaking]);
4472
4520
  const syncAOM = useCallback7(async () => {
4473
4521
  if (!isActiveRef.current) return;
4474
- const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-HDYNCIOY.mjs");
4522
+ const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-LJNCLNXL.mjs");
4475
4523
  const aom = generateMinifiedAOM2();
4476
4524
  emitSocketEvent(socketRef.current, "tour:sync_dom", {
4477
4525
  url: window.location.pathname + window.location.search + window.location.hash,
@@ -4479,6 +4527,24 @@ function useTourPlayback({
4479
4527
  domSummary: captureDomSummary()
4480
4528
  });
4481
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
+ }, []);
4482
4548
  const interruptExecution = useCallback7((transcript) => {
4483
4549
  if (!isSocketWritable(socketRef.current) || !isActiveRef.current) return false;
4484
4550
  if (!commandInFlightRef.current && !voice.isSpeaking) return false;
@@ -4517,6 +4583,7 @@ function useTourPlayback({
4517
4583
  activeCommandBatchIdRef.current = null;
4518
4584
  runCleanup(pendingManualWaitCleanupRef.current);
4519
4585
  pendingManualWaitCleanupRef.current = null;
4586
+ clearPendingManualInputSync();
4520
4587
  removeHighlight();
4521
4588
  removeCaption();
4522
4589
  voice.stopSpeaking();
@@ -4568,6 +4635,7 @@ function useTourPlayback({
4568
4635
  removeCaption();
4569
4636
  runCleanup(pendingManualWaitCleanupRef.current);
4570
4637
  pendingManualWaitCleanupRef.current = null;
4638
+ clearPendingManualInputSync();
4571
4639
  if (!skipRequestedRef.current && userProfile?.userId && tourRef.current) {
4572
4640
  markTourComplete(serverUrl, toursApiBaseRef.current, tourRef.current.id, userProfile.userId, experienceType, websiteId);
4573
4641
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelnex/sdk",
3
- "version": "0.5.39",
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
- };