@modelnex/sdk 0.5.40 → 0.5.42

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,10 @@ 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 shouldPreferHighlightedWaitTargetForInputWait(options) {
3937
+ return options.inputLikeWait && options.resolvedTargetExists && !options.resolvedTargetMatchesPreferred && options.preferredTargetEditable;
3938
+ }
3939
+ function createManualWaitForTarget(rawTarget, eventName, step, options = {}) {
3830
3940
  const target = resolveWaitTargetElement(rawTarget);
3831
3941
  const completionTranscript = buildManualCompletionTranscript(step, eventName);
3832
3942
  const isInputLikeEvent = isInputLikeWait(eventName, step);
@@ -3868,6 +3978,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3868
3978
  }
3869
3979
  };
3870
3980
  const scheduleIdleCommit = () => {
3981
+ options.onObservedInput?.();
3871
3982
  const currentValue = readWaitTargetValue(target);
3872
3983
  if (!currentValue) {
3873
3984
  clearIdleCommit();
@@ -3880,6 +3991,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3880
3991
  }, commitPolicy.idleCommitMs);
3881
3992
  };
3882
3993
  const handleImmediateCommitEvent = () => {
3994
+ options.onObservedInput?.();
3883
3995
  const currentValue = readWaitTargetValue(target);
3884
3996
  if (currentValue) {
3885
3997
  clearIdleCommit();
@@ -3895,6 +4007,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3895
4007
  }
3896
4008
  if (typeof MutationObserver !== "undefined") {
3897
4009
  observer = new MutationObserver(() => {
4010
+ options.onObservedInput?.();
3898
4011
  scheduleIdleCommit();
3899
4012
  });
3900
4013
  observer.observe(target, {
@@ -4018,6 +4131,7 @@ function useTourPlayback({
4018
4131
  const voiceInputResolveRef = (0, import_react12.useRef)(null);
4019
4132
  const askOrFillRef = (0, import_react12.useRef)(null);
4020
4133
  const pendingManualWaitCleanupRef = (0, import_react12.useRef)(null);
4134
+ const pendingManualInputSyncRef = (0, import_react12.useRef)(null);
4021
4135
  const llmRespondingRef = (0, import_react12.useRef)(false);
4022
4136
  const interruptedForQuestionRef = (0, import_react12.useRef)(false);
4023
4137
  const pendingInputBufRef = (0, import_react12.useRef)(null);
@@ -4117,6 +4231,7 @@ function useTourPlayback({
4117
4231
  }, { devMode: devModeRef.current });
4118
4232
  runCleanup(pendingManualWaitCleanupRef.current);
4119
4233
  pendingManualWaitCleanupRef.current = null;
4234
+ clearPendingManualInputSync();
4120
4235
  if (voiceInputResolveRef.current) {
4121
4236
  const resolvePendingVoiceInput = voiceInputResolveRef.current;
4122
4237
  voiceInputResolveRef.current = null;
@@ -4503,24 +4618,34 @@ function useTourPlayback({
4503
4618
  const preferredWaitTarget = inputLikeWait ? batchPreferredWaitTarget ?? highlightedWaitTarget : highlightedWaitTarget;
4504
4619
  runCleanup(pendingManualWaitCleanupRef.current);
4505
4620
  pendingManualWaitCleanupRef.current = null;
4621
+ clearPendingManualInputSync();
4506
4622
  if (waitTargetHints) {
4507
4623
  let manualWaitTarget = await resolveTargetElement2(waitTargetHints, currentStep);
4508
- if (inputLikeWait && preferredWaitTarget && manualWaitTarget && manualWaitTarget !== preferredWaitTarget && !isEditableWaitTarget(manualWaitTarget) && isEditableWaitTarget(preferredWaitTarget)) {
4624
+ if (shouldPreferHighlightedWaitTargetForInputWait({
4625
+ inputLikeWait,
4626
+ resolvedTargetExists: Boolean(manualWaitTarget),
4627
+ resolvedTargetMatchesPreferred: manualWaitTarget === preferredWaitTarget,
4628
+ preferredTargetEditable: isEditableWaitTarget(preferredWaitTarget)
4629
+ })) {
4509
4630
  manualWaitTarget = preferredWaitTarget;
4510
- emitSdkDebugLog("[TourClient] wait_for_input preferred highlighted editable target", {
4631
+ emitSdkDebugLog("[TourClient] wait_for_input preferring highlighted editable target over recorded target", {
4511
4632
  stepIndex: stepIndexRef.current,
4512
4633
  event: waitEvent
4513
4634
  }, { devMode: devModeRef.current });
4514
4635
  }
4515
4636
  if (manualWaitTarget) {
4516
- const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep);
4637
+ const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep, {
4638
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4639
+ });
4517
4640
  manualWaitPromise = manualWait.promise;
4518
4641
  manualWaitKind = manualWait.kind;
4519
4642
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
4520
4643
  }
4521
4644
  }
4522
4645
  if (!manualWaitPromise && preferredWaitTarget) {
4523
- const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep);
4646
+ const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep, {
4647
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4648
+ });
4524
4649
  manualWaitPromise = manualWait.promise;
4525
4650
  manualWaitKind = manualWait.kind;
4526
4651
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4534,7 +4659,9 @@ function useTourPlayback({
4534
4659
  'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), [contenteditable="true"], [role="textbox"]'
4535
4660
  );
4536
4661
  if (firstInput) {
4537
- const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep);
4662
+ const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep, {
4663
+ onObservedInput: scheduleManualInputSync
4664
+ });
4538
4665
  manualWaitPromise = manualWait.promise;
4539
4666
  manualWaitKind = manualWait.kind;
4540
4667
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4580,20 +4707,33 @@ function useTourPlayback({
4580
4707
  Promise.race([voiceOrTextWaitPromise, manualWaitPromise].filter(Boolean)).then(async (userText) => {
4581
4708
  runCleanup(pendingManualWaitCleanupRef.current);
4582
4709
  pendingManualWaitCleanupRef.current = null;
4710
+ clearPendingManualInputSync();
4583
4711
  voiceInputResolveRef.current = null;
4584
4712
  setPlaybackState("executing");
4585
4713
  const transcript = userText.trim();
4586
4714
  if (!transcript) {
4587
4715
  return;
4588
4716
  }
4589
- const { waitForDomSettle: waitForDomSettle2 } = await Promise.resolve().then(() => (init_dom_sync(), dom_sync_exports));
4590
- await waitForDomSettle2({ timeoutMs: 1500, debounceMs: 200 });
4591
- await syncAOM();
4717
+ try {
4718
+ const { waitForDomSettle: waitForDomSettle2 } = await Promise.resolve().then(() => (init_dom_sync(), dom_sync_exports));
4719
+ await waitForDomSettle2({ timeoutMs: 1500, debounceMs: 200 });
4720
+ await syncAOM();
4721
+ } catch (error) {
4722
+ emitSdkDebugLog("[TourClient] Failed to sync DOM before sending wait_for_input transcript", {
4723
+ stepIndex: stepIndexRef.current,
4724
+ error: error instanceof Error ? error.message : String(error)
4725
+ }, { devMode: devModeRef.current });
4726
+ }
4592
4727
  emitIfOpen("tour:user_input", {
4593
4728
  transcript,
4594
4729
  runId: runIdRef.current,
4595
4730
  turnId: turnIdRef.current
4596
4731
  });
4732
+ }).catch((error) => {
4733
+ emitSdkDebugLog("[TourClient] wait_for_input listener rejected", {
4734
+ stepIndex: stepIndexRef.current,
4735
+ error: error instanceof Error ? error.message : String(error)
4736
+ }, { devMode: devModeRef.current });
4597
4737
  });
4598
4738
  return;
4599
4739
  }
@@ -4784,6 +4924,24 @@ function useTourPlayback({
4784
4924
  domSummary: captureDomSummary()
4785
4925
  });
4786
4926
  }, []);
4927
+ const scheduleManualInputSync = (0, import_react12.useCallback)(() => {
4928
+ if (pendingManualInputSyncRef.current) {
4929
+ clearTimeout(pendingManualInputSyncRef.current);
4930
+ }
4931
+ pendingManualInputSyncRef.current = setTimeout(async () => {
4932
+ pendingManualInputSyncRef.current = null;
4933
+ if (!isActiveRef.current) return;
4934
+ const { waitForDomSettle: waitForDomSettle2 } = await Promise.resolve().then(() => (init_dom_sync(), dom_sync_exports));
4935
+ await waitForDomSettle2({ timeoutMs: 600, debounceMs: 100 });
4936
+ await syncAOM();
4937
+ }, 150);
4938
+ }, [syncAOM]);
4939
+ const clearPendingManualInputSync = (0, import_react12.useCallback)(() => {
4940
+ if (pendingManualInputSyncRef.current) {
4941
+ clearTimeout(pendingManualInputSyncRef.current);
4942
+ pendingManualInputSyncRef.current = null;
4943
+ }
4944
+ }, []);
4787
4945
  const interruptExecution = (0, import_react12.useCallback)((transcript) => {
4788
4946
  if (!isSocketWritable(socketRef.current) || !isActiveRef.current) return false;
4789
4947
  if (!commandInFlightRef.current && !voice.isSpeaking) return false;
@@ -4822,6 +4980,7 @@ function useTourPlayback({
4822
4980
  activeCommandBatchIdRef.current = null;
4823
4981
  runCleanup(pendingManualWaitCleanupRef.current);
4824
4982
  pendingManualWaitCleanupRef.current = null;
4983
+ clearPendingManualInputSync();
4825
4984
  removeHighlight();
4826
4985
  removeCaption();
4827
4986
  voice.stopSpeaking();
@@ -4873,6 +5032,7 @@ function useTourPlayback({
4873
5032
  removeCaption();
4874
5033
  runCleanup(pendingManualWaitCleanupRef.current);
4875
5034
  pendingManualWaitCleanupRef.current = null;
5035
+ clearPendingManualInputSync();
4876
5036
  if (!skipRequestedRef.current && userProfile?.userId && tourRef.current) {
4877
5037
  markTourComplete(serverUrl, toursApiBaseRef.current, tourRef.current.id, userProfile.userId, experienceType, websiteId);
4878
5038
  }
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,10 @@ 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 shouldPreferHighlightedWaitTargetForInputWait(options) {
3560
+ return options.inputLikeWait && options.resolvedTargetExists && !options.resolvedTargetMatchesPreferred && options.preferredTargetEditable;
3561
+ }
3562
+ function createManualWaitForTarget(rawTarget, eventName, step, options = {}) {
3619
3563
  const target = resolveWaitTargetElement(rawTarget);
3620
3564
  const completionTranscript = buildManualCompletionTranscript(step, eventName);
3621
3565
  const isInputLikeEvent = isInputLikeWait(eventName, step);
@@ -3657,6 +3601,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3657
3601
  }
3658
3602
  };
3659
3603
  const scheduleIdleCommit = () => {
3604
+ options.onObservedInput?.();
3660
3605
  const currentValue = readWaitTargetValue(target);
3661
3606
  if (!currentValue) {
3662
3607
  clearIdleCommit();
@@ -3669,6 +3614,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3669
3614
  }, commitPolicy.idleCommitMs);
3670
3615
  };
3671
3616
  const handleImmediateCommitEvent = () => {
3617
+ options.onObservedInput?.();
3672
3618
  const currentValue = readWaitTargetValue(target);
3673
3619
  if (currentValue) {
3674
3620
  clearIdleCommit();
@@ -3684,6 +3630,7 @@ function createManualWaitForTarget(rawTarget, eventName, step) {
3684
3630
  }
3685
3631
  if (typeof MutationObserver !== "undefined") {
3686
3632
  observer = new MutationObserver(() => {
3633
+ options.onObservedInput?.();
3687
3634
  scheduleIdleCommit();
3688
3635
  });
3689
3636
  observer.observe(target, {
@@ -3807,6 +3754,7 @@ function useTourPlayback({
3807
3754
  const voiceInputResolveRef = useRef8(null);
3808
3755
  const askOrFillRef = useRef8(null);
3809
3756
  const pendingManualWaitCleanupRef = useRef8(null);
3757
+ const pendingManualInputSyncRef = useRef8(null);
3810
3758
  const llmRespondingRef = useRef8(false);
3811
3759
  const interruptedForQuestionRef = useRef8(false);
3812
3760
  const pendingInputBufRef = useRef8(null);
@@ -3906,6 +3854,7 @@ function useTourPlayback({
3906
3854
  }, { devMode: devModeRef.current });
3907
3855
  runCleanup(pendingManualWaitCleanupRef.current);
3908
3856
  pendingManualWaitCleanupRef.current = null;
3857
+ clearPendingManualInputSync();
3909
3858
  if (voiceInputResolveRef.current) {
3910
3859
  const resolvePendingVoiceInput = voiceInputResolveRef.current;
3911
3860
  voiceInputResolveRef.current = null;
@@ -3991,7 +3940,7 @@ function useTourPlayback({
3991
3940
  resolve: async () => {
3992
3941
  let targetEl = null;
3993
3942
  if (params.uid) {
3994
- const { getElementByUid } = await import("./aom-HDYNCIOY.mjs");
3943
+ const { getElementByUid } = await import("./aom-LJNCLNXL.mjs");
3995
3944
  targetEl = getElementByUid(params.uid);
3996
3945
  }
3997
3946
  if (!targetEl) {
@@ -4292,24 +4241,34 @@ function useTourPlayback({
4292
4241
  const preferredWaitTarget = inputLikeWait ? batchPreferredWaitTarget ?? highlightedWaitTarget : highlightedWaitTarget;
4293
4242
  runCleanup(pendingManualWaitCleanupRef.current);
4294
4243
  pendingManualWaitCleanupRef.current = null;
4244
+ clearPendingManualInputSync();
4295
4245
  if (waitTargetHints) {
4296
4246
  let manualWaitTarget = await resolveTargetElement2(waitTargetHints, currentStep);
4297
- if (inputLikeWait && preferredWaitTarget && manualWaitTarget && manualWaitTarget !== preferredWaitTarget && !isEditableWaitTarget(manualWaitTarget) && isEditableWaitTarget(preferredWaitTarget)) {
4247
+ if (shouldPreferHighlightedWaitTargetForInputWait({
4248
+ inputLikeWait,
4249
+ resolvedTargetExists: Boolean(manualWaitTarget),
4250
+ resolvedTargetMatchesPreferred: manualWaitTarget === preferredWaitTarget,
4251
+ preferredTargetEditable: isEditableWaitTarget(preferredWaitTarget)
4252
+ })) {
4298
4253
  manualWaitTarget = preferredWaitTarget;
4299
- emitSdkDebugLog("[TourClient] wait_for_input preferred highlighted editable target", {
4254
+ emitSdkDebugLog("[TourClient] wait_for_input preferring highlighted editable target over recorded target", {
4300
4255
  stepIndex: stepIndexRef.current,
4301
4256
  event: waitEvent
4302
4257
  }, { devMode: devModeRef.current });
4303
4258
  }
4304
4259
  if (manualWaitTarget) {
4305
- const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep);
4260
+ const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep, {
4261
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4262
+ });
4306
4263
  manualWaitPromise = manualWait.promise;
4307
4264
  manualWaitKind = manualWait.kind;
4308
4265
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
4309
4266
  }
4310
4267
  }
4311
4268
  if (!manualWaitPromise && preferredWaitTarget) {
4312
- const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep);
4269
+ const manualWait = createManualWaitForTarget(preferredWaitTarget, waitEvent, currentStep, {
4270
+ onObservedInput: inputLikeWait ? scheduleManualInputSync : null
4271
+ });
4313
4272
  manualWaitPromise = manualWait.promise;
4314
4273
  manualWaitKind = manualWait.kind;
4315
4274
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4323,7 +4282,9 @@ function useTourPlayback({
4323
4282
  'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), [contenteditable="true"], [role="textbox"]'
4324
4283
  );
4325
4284
  if (firstInput) {
4326
- const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep);
4285
+ const manualWait = createManualWaitForTarget(firstInput, waitEvent, currentStep, {
4286
+ onObservedInput: scheduleManualInputSync
4287
+ });
4327
4288
  manualWaitPromise = manualWait.promise;
4328
4289
  manualWaitKind = manualWait.kind;
4329
4290
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
@@ -4369,20 +4330,33 @@ function useTourPlayback({
4369
4330
  Promise.race([voiceOrTextWaitPromise, manualWaitPromise].filter(Boolean)).then(async (userText) => {
4370
4331
  runCleanup(pendingManualWaitCleanupRef.current);
4371
4332
  pendingManualWaitCleanupRef.current = null;
4333
+ clearPendingManualInputSync();
4372
4334
  voiceInputResolveRef.current = null;
4373
4335
  setPlaybackState("executing");
4374
4336
  const transcript = userText.trim();
4375
4337
  if (!transcript) {
4376
4338
  return;
4377
4339
  }
4378
- const { waitForDomSettle } = await import("./dom-sync-GABDEODR.mjs");
4379
- await waitForDomSettle({ timeoutMs: 1500, debounceMs: 200 });
4380
- await syncAOM();
4340
+ try {
4341
+ const { waitForDomSettle } = await import("./dom-sync-GABDEODR.mjs");
4342
+ await waitForDomSettle({ timeoutMs: 1500, debounceMs: 200 });
4343
+ await syncAOM();
4344
+ } catch (error) {
4345
+ emitSdkDebugLog("[TourClient] Failed to sync DOM before sending wait_for_input transcript", {
4346
+ stepIndex: stepIndexRef.current,
4347
+ error: error instanceof Error ? error.message : String(error)
4348
+ }, { devMode: devModeRef.current });
4349
+ }
4381
4350
  emitIfOpen("tour:user_input", {
4382
4351
  transcript,
4383
4352
  runId: runIdRef.current,
4384
4353
  turnId: turnIdRef.current
4385
4354
  });
4355
+ }).catch((error) => {
4356
+ emitSdkDebugLog("[TourClient] wait_for_input listener rejected", {
4357
+ stepIndex: stepIndexRef.current,
4358
+ error: error instanceof Error ? error.message : String(error)
4359
+ }, { devMode: devModeRef.current });
4386
4360
  });
4387
4361
  return;
4388
4362
  }
@@ -4451,7 +4425,7 @@ function useTourPlayback({
4451
4425
  void recordTourEvent(serverUrl, toursApiBaseRef.current, tour.id, userProfile.userId, "started", websiteId);
4452
4426
  }
4453
4427
  try {
4454
- const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-HDYNCIOY.mjs");
4428
+ const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-LJNCLNXL.mjs");
4455
4429
  const aom = generateMinifiedAOM2();
4456
4430
  if (socketRef.current === socket) {
4457
4431
  emitSocketEvent(socket, "tour:sync_dom", {
@@ -4565,7 +4539,7 @@ function useTourPlayback({
4565
4539
  }, [isActive, playbackState, voice.isListening, voice.isSpeaking]);
4566
4540
  const syncAOM = useCallback7(async () => {
4567
4541
  if (!isActiveRef.current) return;
4568
- const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-HDYNCIOY.mjs");
4542
+ const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-LJNCLNXL.mjs");
4569
4543
  const aom = generateMinifiedAOM2();
4570
4544
  emitSocketEvent(socketRef.current, "tour:sync_dom", {
4571
4545
  url: window.location.pathname + window.location.search + window.location.hash,
@@ -4573,6 +4547,24 @@ function useTourPlayback({
4573
4547
  domSummary: captureDomSummary()
4574
4548
  });
4575
4549
  }, []);
4550
+ const scheduleManualInputSync = useCallback7(() => {
4551
+ if (pendingManualInputSyncRef.current) {
4552
+ clearTimeout(pendingManualInputSyncRef.current);
4553
+ }
4554
+ pendingManualInputSyncRef.current = setTimeout(async () => {
4555
+ pendingManualInputSyncRef.current = null;
4556
+ if (!isActiveRef.current) return;
4557
+ const { waitForDomSettle } = await import("./dom-sync-GABDEODR.mjs");
4558
+ await waitForDomSettle({ timeoutMs: 600, debounceMs: 100 });
4559
+ await syncAOM();
4560
+ }, 150);
4561
+ }, [syncAOM]);
4562
+ const clearPendingManualInputSync = useCallback7(() => {
4563
+ if (pendingManualInputSyncRef.current) {
4564
+ clearTimeout(pendingManualInputSyncRef.current);
4565
+ pendingManualInputSyncRef.current = null;
4566
+ }
4567
+ }, []);
4576
4568
  const interruptExecution = useCallback7((transcript) => {
4577
4569
  if (!isSocketWritable(socketRef.current) || !isActiveRef.current) return false;
4578
4570
  if (!commandInFlightRef.current && !voice.isSpeaking) return false;
@@ -4611,6 +4603,7 @@ function useTourPlayback({
4611
4603
  activeCommandBatchIdRef.current = null;
4612
4604
  runCleanup(pendingManualWaitCleanupRef.current);
4613
4605
  pendingManualWaitCleanupRef.current = null;
4606
+ clearPendingManualInputSync();
4614
4607
  removeHighlight();
4615
4608
  removeCaption();
4616
4609
  voice.stopSpeaking();
@@ -4662,6 +4655,7 @@ function useTourPlayback({
4662
4655
  removeCaption();
4663
4656
  runCleanup(pendingManualWaitCleanupRef.current);
4664
4657
  pendingManualWaitCleanupRef.current = null;
4658
+ clearPendingManualInputSync();
4665
4659
  if (!skipRequestedRef.current && userProfile?.userId && tourRef.current) {
4666
4660
  markTourComplete(serverUrl, toursApiBaseRef.current, tourRef.current.id, userProfile.userId, experienceType, websiteId);
4667
4661
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelnex/sdk",
3
- "version": "0.5.40",
3
+ "version": "0.5.42",
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
- };