@modelnex/sdk 0.5.44 → 0.5.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -30,200 +30,636 @@ 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();
33
+ // src/utils/dev-logging.ts
34
+ function isSdkDebugEnabled(devMode) {
35
+ if (devMode) return true;
36
+ if (typeof window !== "undefined" && Boolean(window.MODELNEX_DEBUG)) {
37
+ return true;
38
+ }
39
+ return process.env.NODE_ENV === "development";
40
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;
41
+ function emitSdkDebugLog(message, payload, options) {
42
+ if (!isSdkDebugEnabled(options?.devMode)) return;
43
+ if (payload && Object.keys(payload).length > 0) {
44
+ console.log(message, payload);
45
+ } else {
46
+ console.log(message);
47
+ }
48
+ if (options?.dispatchEvent && typeof window !== "undefined") {
49
+ window.dispatchEvent(new CustomEvent("modelnex-debug", { detail: { msg: message, data: payload } }));
45
50
  }
46
- return null;
47
51
  }
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));
52
+ function sanitizeActionList(actions) {
53
+ if (!Array.isArray(actions) || actions.length === 0) return void 0;
54
+ return actions.map(({ actionId }) => ({ actionId }));
57
55
  }
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
- }
56
+ function sanitizeAgentDebug(debug) {
57
+ if (!debug) return void 0;
58
+ const actions = sanitizeActionList(debug.actions);
59
+ const traces = Array.isArray(debug.traces) && debug.traces.length > 0 ? debug.traces.map((trace) => ({
60
+ step: trace.step,
61
+ actions: sanitizeActionList(trace.actions) ?? [],
62
+ results: Array.isArray(trace.results) && trace.results.length > 0 ? trace.results.map(({ actionId, success }) => ({ actionId, success })) : void 0
63
+ })) : void 0;
64
+ if ((!actions || actions.length === 0) && (!traces || traces.length === 0)) {
65
+ return void 0;
65
66
  }
66
- for (const child of Array.from(root.children)) {
67
- if (child instanceof HTMLElement) {
68
- const found = findEditableControlInShadowRoot(child);
69
- if (found) return found;
67
+ return {
68
+ ...actions ? { actions } : {},
69
+ ...traces ? { traces } : {}
70
+ };
71
+ }
72
+ function sanitizeChatMessages(messages, devMode) {
73
+ const includeDebug = isSdkDebugEnabled(devMode);
74
+ return messages.map((message) => {
75
+ if (message.role !== "assistant") {
76
+ return message;
70
77
  }
71
- }
72
- return null;
78
+ return {
79
+ ...message,
80
+ debug: includeDebug ? sanitizeAgentDebug(message.debug) : void 0
81
+ };
82
+ });
73
83
  }
74
- function resolveEditableControlElement(element) {
75
- const labelControl = getLabelControl(element);
76
- if (labelControl) {
77
- return resolveEditableControlElement(labelControl);
84
+ var init_dev_logging = __esm({
85
+ "src/utils/dev-logging.ts"() {
86
+ "use strict";
78
87
  }
79
- if (isValueBearingElement(element) || element.isContentEditable) {
80
- return element;
88
+ });
89
+
90
+ // src/auto-extract.ts
91
+ function simpleHash(str) {
92
+ let hash = 0;
93
+ for (let i = 0; i < str.length; i++) {
94
+ const char = str.charCodeAt(i);
95
+ hash = (hash << 5) - hash + char;
96
+ hash |= 0;
81
97
  }
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);
98
+ return (hash >>> 0).toString(16).padStart(8, "0");
99
+ }
100
+ function isStableDomId(id) {
101
+ if (!id) return false;
102
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
103
+ const REACT_USE_ID_RE = /^(?:radix-)?:[rR][^\s]*$/;
104
+ if (REACT_USE_ID_RE.test(id)) return false;
105
+ if (/^\d+$/.test(id)) return false;
106
+ if (UUID_RE.test(id)) return false;
107
+ return true;
108
+ }
109
+ function getNearestHeading(el) {
110
+ const shellContainer = el.closest('nav, aside, header, [role="navigation"], [role="complementary"], [role="banner"]');
111
+ const boundary = shellContainer || document.body;
112
+ let current = el;
113
+ while (current && current !== boundary) {
114
+ let sibling = current.previousElementSibling;
115
+ while (sibling) {
116
+ if (/^H[1-6]$/.test(sibling.tagName)) {
117
+ return sibling.textContent?.trim().slice(0, 100) || null;
118
+ }
119
+ const innerHeading = sibling.querySelector("h1, h2, h3, h4, h5, h6");
120
+ if (innerHeading) {
121
+ return innerHeading.textContent?.trim().slice(0, 100) || null;
122
+ }
123
+ sibling = sibling.previousElementSibling;
87
124
  }
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);
125
+ const parentHeading = current.parentElement?.querySelector("h1, h2, h3, h4, h5, h6");
126
+ if (parentHeading && !current.contains(parentHeading)) {
127
+ return parentHeading.textContent?.trim().slice(0, 100) || null;
128
+ }
129
+ current = current.parentElement;
94
130
  }
95
- const shadowNested = findEditableControlInShadowRoot(element);
96
- return shadowNested ?? element;
131
+ return null;
97
132
  }
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 "***";
133
+ function getParentContainer(el) {
134
+ let current = el.parentElement;
135
+ while (current && current !== document.body) {
136
+ const role = current.getAttribute("role");
137
+ const tag = current.tagName.toLowerCase();
138
+ const state = current.getAttribute("data-state");
139
+ if (role === "dialog" || role === "alertdialog" || tag === "dialog") {
140
+ const title = current.querySelector('h2, h3, [class*="title"]');
141
+ const titleText = title?.textContent?.trim().slice(0, 60) || "";
142
+ return titleText ? `dialog:${titleText}` : "dialog";
105
143
  }
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);
144
+ if (role === "menu" || role === "listbox") {
145
+ return role;
146
+ }
147
+ if (current.hasAttribute("popover") || state === "open" && current.getAttribute("data-radix-popper-content-wrapper") !== null) {
148
+ return "popover";
149
+ }
150
+ if (current.getAttribute("data-radix-menu-content") !== null || role === "menubar") {
151
+ return "dropdown-menu";
152
+ }
153
+ if (role === "tabpanel") {
154
+ const label = current.getAttribute("aria-label") || current.getAttribute("aria-labelledby");
155
+ return label ? `tabpanel:${label.slice(0, 40)}` : "tabpanel";
156
+ }
157
+ if (tag === "nav" || role === "navigation") {
158
+ const label = current.getAttribute("aria-label");
159
+ return label ? `navigation:${label.slice(0, 40)}` : "navigation";
160
+ }
161
+ if (tag === "aside") {
162
+ return "sidebar";
163
+ }
164
+ if (tag === "header") {
165
+ return "header";
166
+ }
167
+ current = current.parentElement;
111
168
  }
112
- if (typeof genericValue === "number" && Number.isFinite(genericValue)) {
113
- return String(genericValue).trim().slice(0, 100);
169
+ return null;
170
+ }
171
+ function getRowContext(el) {
172
+ let current = el.parentElement;
173
+ while (current && current !== document.body) {
174
+ const tag = current.tagName.toLowerCase();
175
+ if (tag === "tr" || tag === "li" || current.getAttribute("role") === "row" || current.getAttribute("role") === "listitem") {
176
+ const rowText = current.textContent?.trim().replace(/\s+/g, " ").slice(0, 120) || "";
177
+ return rowText;
178
+ }
179
+ current = current.parentElement;
114
180
  }
115
- if (target.isContentEditable) {
116
- return String(target.textContent || "").trim().slice(0, 100);
181
+ return "";
182
+ }
183
+ function getNearestAncestorId(el) {
184
+ let current = el.parentElement;
185
+ while (current && current !== document.body) {
186
+ const aid = current.id;
187
+ if (isStableDomId(aid)) {
188
+ return aid;
189
+ }
190
+ const ancestorTestId = current.getAttribute("data-testid");
191
+ if (ancestorTestId) return `[data-testid="${ancestorTestId}"]`;
192
+ current = current.parentElement;
117
193
  }
118
- const ariaValueText = target.getAttribute("aria-valuetext");
119
- if (ariaValueText?.trim()) {
120
- return ariaValueText.trim().slice(0, 100);
194
+ return null;
195
+ }
196
+ function generateFingerprint(el) {
197
+ const tag = el.tagName.toLowerCase();
198
+ const testId = el.getAttribute("data-testid");
199
+ const id = el.id;
200
+ const name = el.getAttribute("name");
201
+ const ariaLabel = el.getAttribute("aria-label");
202
+ const type = el.type || "";
203
+ const text = el.textContent?.trim().slice(0, 80) || "";
204
+ if (testId) return `tid:${testId}`;
205
+ if (isStableDomId(id)) return `id:${id}`;
206
+ if (name) return `name:${tag}:${name}`;
207
+ if (ariaLabel) {
208
+ const rowCtx2 = getRowContext(el);
209
+ if (rowCtx2) {
210
+ return `aria:${simpleHash(ariaLabel + ":" + rowCtx2)}:${ariaLabel.slice(0, 40)}`;
211
+ }
212
+ return `aria:${simpleHash(ariaLabel)}:${ariaLabel.slice(0, 40)}`;
121
213
  }
122
- const ariaValueNow = target.getAttribute("aria-valuenow");
123
- if (ariaValueNow?.trim()) {
124
- return ariaValueNow.trim().slice(0, 100);
214
+ if (tag === "a") {
215
+ const href = el.getAttribute("href");
216
+ if (href && href !== "#" && !href.startsWith("javascript:")) {
217
+ return `href:${simpleHash(href + ":" + text)}:${text.slice(0, 30) || href.slice(0, 30)}`;
218
+ }
125
219
  }
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);
220
+ const ancestorId = getNearestAncestorId(el);
221
+ if (ancestorId) {
222
+ return `anc:${simpleHash(ancestorId + ":" + tag + ":" + text)}:${text.slice(0, 30) || tag}`;
132
223
  }
133
- return String(target.getAttribute("placeholder") || "").trim().slice(0, 100);
224
+ const heading = getNearestHeading(el) || "";
225
+ const rowCtx = getRowContext(el);
226
+ const raw = `${tag}:${type}:${text}:${heading}:${rowCtx}`;
227
+ return `hash:${simpleHash(raw)}:${text.slice(0, 30) || tag}`;
134
228
  }
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);
229
+ function classifyRole(el) {
230
+ const tag = el.tagName.toLowerCase();
231
+ const role = el.getAttribute("role");
232
+ if (tag === "button" || role === "button") return "button";
233
+ if (tag === "a") return "link";
234
+ if (tag === "input") return "input";
235
+ if (tag === "textarea") return "textarea";
236
+ if (tag === "select") return "select";
237
+ if (tag === "form") return "form";
238
+ if (tag === "label") return "label";
239
+ if (role === "link") return "link";
240
+ if (role === "menuitem") return "menuitem";
241
+ if (role === "menuitemcheckbox") return "menuitem";
242
+ if (role === "menuitemradio") return "menuitem";
243
+ if (role === "tab") return "tab";
244
+ if (role === "checkbox") return "checkbox";
245
+ if (role === "radio") return "radio";
246
+ if (role === "switch") return "switch";
247
+ if (role === "slider") return "slider";
248
+ if (role === "combobox") return "combobox";
249
+ if (role === "option") return "option";
250
+ if (role === "treeitem") return "treeitem";
251
+ if (role === "gridcell") return "gridcell";
252
+ if (el.hasAttribute("tabindex") && el.getAttribute("tabindex") !== "-1") return "interactive";
253
+ if (el.hasAttribute("cmdk-item")) return "menuitem";
254
+ return null;
144
255
  }
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);
256
+ function isVisible(el) {
257
+ if (el.offsetParent === null) {
258
+ const computed = window.getComputedStyle(el);
259
+ if (computed.position !== "fixed" && computed.position !== "sticky") {
260
+ if (computed.display === "none" || computed.visibility === "hidden") {
261
+ return false;
262
+ }
263
+ const rect2 = el.getBoundingClientRect();
264
+ if (rect2.width === 0 && rect2.height === 0) return false;
265
+ }
152
266
  }
153
- const attributeType = target.getAttribute("type");
154
- if (attributeType?.trim()) {
155
- return attributeType.trim().toLowerCase().slice(0, 50);
267
+ const style = window.getComputedStyle(el);
268
+ if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
269
+ return false;
156
270
  }
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);
271
+ const rect = el.getBoundingClientRect();
272
+ return rect.width > 0 && rect.height > 0;
164
273
  }
165
- function collectEditableControls(scope) {
274
+ function extractInteractiveElements() {
275
+ const elements = [];
166
276
  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);
277
+ try {
278
+ const nodes = document.querySelectorAll(INTERACTIVE_SELECTOR);
279
+ nodes.forEach((node) => {
280
+ const el = node;
281
+ if (!isVisible(el)) return;
282
+ if (el.closest(".modelnex-chat-panel, .modelnex-studio-overlay, [data-modelnex-internal]")) return;
283
+ const role = classifyRole(el);
284
+ if (!role) return;
285
+ let fingerprint = generateFingerprint(el);
286
+ if (seen.has(fingerprint)) {
287
+ let idx = 2;
288
+ while (seen.has(`${fingerprint}#${idx}`)) idx++;
289
+ fingerprint = `${fingerprint}#${idx}`;
290
+ }
291
+ seen.add(fingerprint);
292
+ const rect = el.getBoundingClientRect();
293
+ const text = el.textContent?.trim().slice(0, 200) || "";
294
+ elements.push({
295
+ fingerprint,
296
+ tagName: el.tagName.toLowerCase(),
297
+ text,
298
+ role,
299
+ rect: {
300
+ top: rect.top,
301
+ left: rect.left,
302
+ width: rect.width,
303
+ height: rect.height
304
+ },
305
+ attributes: {
306
+ id: el.id || void 0,
307
+ name: el.getAttribute("name") || void 0,
308
+ type: el.type || void 0,
309
+ ariaLabel: el.getAttribute("aria-label") || void 0,
310
+ dataTestId: el.getAttribute("data-testid") || void 0,
311
+ href: el.href || void 0,
312
+ placeholder: el.placeholder || void 0
313
+ },
314
+ nearestHeading: getNearestHeading(el),
315
+ parentContainer: getParentContainer(el),
316
+ disabled: el.disabled || el.getAttribute("aria-disabled") === "true",
317
+ element: el
318
+ });
319
+ });
320
+ } catch (err) {
321
+ console.warn("[ModelNex] Auto-extraction error:", err);
176
322
  }
177
- return controls;
323
+ return elements;
178
324
  }
179
- var EDITABLE_ROLES, EDITABLE_CONTROL_SELECTOR;
180
- var init_editable_controls = __esm({
181
- "src/utils/editable-controls.ts"() {
325
+ function useAutoExtract(devMode) {
326
+ const [elements, setElements] = (0, import_react2.useState)([]);
327
+ const timerRef = (0, import_react2.useRef)(null);
328
+ const lastSnapshotRef = (0, import_react2.useRef)("");
329
+ const scan = (0, import_react2.useCallback)(() => {
330
+ if (typeof document === "undefined") return;
331
+ const extracted = extractInteractiveElements();
332
+ const snapshot = JSON.stringify(extracted.map((e) => ({
333
+ fingerprint: e.fingerprint,
334
+ role: e.role,
335
+ text: e.text,
336
+ rect: e.rect,
337
+ disabled: e.disabled,
338
+ nearestHeading: e.nearestHeading,
339
+ parentContainer: e.parentContainer
340
+ })));
341
+ if (snapshot === lastSnapshotRef.current) return;
342
+ lastSnapshotRef.current = snapshot;
343
+ emitSdkDebugLog("[ModelNex AutoExtract] Scan complete", {
344
+ elementCount: extracted.length
345
+ }, { devMode });
346
+ setElements(extracted);
347
+ }, [devMode]);
348
+ (0, import_react2.useEffect)(() => {
349
+ const initialTimer = setTimeout(scan, 300);
350
+ const observer = new MutationObserver((mutations) => {
351
+ const hasRelevantMutation = mutations.some((mutation) => {
352
+ const target = mutation.target;
353
+ if (target?.closest?.(".modelnex-chat-panel, .modelnex-studio-overlay, [data-modelnex-internal]")) {
354
+ return false;
355
+ }
356
+ return true;
357
+ });
358
+ if (!hasRelevantMutation) return;
359
+ if (timerRef.current) clearTimeout(timerRef.current);
360
+ timerRef.current = setTimeout(scan, 500);
361
+ });
362
+ observer.observe(document.body, {
363
+ childList: true,
364
+ subtree: true,
365
+ attributes: true,
366
+ attributeFilter: ["disabled", "aria-disabled", "hidden", "style", "class"]
367
+ });
368
+ const handlePositionChange = () => {
369
+ if (timerRef.current) clearTimeout(timerRef.current);
370
+ timerRef.current = setTimeout(scan, 200);
371
+ };
372
+ window.addEventListener("scroll", handlePositionChange, { passive: true });
373
+ window.addEventListener("resize", handlePositionChange, { passive: true });
374
+ return () => {
375
+ clearTimeout(initialTimer);
376
+ if (timerRef.current) clearTimeout(timerRef.current);
377
+ observer.disconnect();
378
+ window.removeEventListener("scroll", handlePositionChange);
379
+ window.removeEventListener("resize", handlePositionChange);
380
+ };
381
+ }, [scan]);
382
+ return elements;
383
+ }
384
+ var import_react2, INTERACTIVE_SELECTOR;
385
+ var init_auto_extract = __esm({
386
+ "src/auto-extract.ts"() {
182
387
  "use strict";
183
- EDITABLE_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox"]);
184
- EDITABLE_CONTROL_SELECTOR = [
388
+ import_react2 = require("react");
389
+ init_dev_logging();
390
+ INTERACTIVE_SELECTOR = [
391
+ "button",
392
+ "a",
393
+ // All anchor tags (SPA links may not have href)
185
394
  'input:not([type="hidden"])',
186
395
  "textarea",
187
396
  "select",
188
- '[contenteditable="true"]',
189
- '[role="textbox"]',
190
- '[role="combobox"]'
397
+ "label[for]",
398
+ '[role="button"]',
399
+ '[role="link"]',
400
+ '[role="menuitem"]',
401
+ '[role="menuitemcheckbox"]',
402
+ '[role="menuitemradio"]',
403
+ '[role="tab"]',
404
+ '[role="checkbox"]',
405
+ '[role="radio"]',
406
+ '[role="switch"]',
407
+ '[role="slider"]',
408
+ '[role="combobox"]',
409
+ '[role="option"]',
410
+ '[role="treeitem"]',
411
+ '[role="gridcell"]',
412
+ '[tabindex]:not([tabindex="-1"])',
413
+ "[data-discover]",
414
+ // React Router links
415
+ "[cmdk-item]",
416
+ // cmdk menu items
417
+ "form"
191
418
  ].join(", ");
192
419
  }
193
420
  });
194
421
 
195
- // src/utils/aom.ts
196
- var aom_exports = {};
197
- __export(aom_exports, {
198
- clearAOMMap: () => clearAOMMap,
199
- generateMinifiedAOM: () => generateMinifiedAOM,
200
- getElementByUid: () => getElementByUid
201
- });
202
- function generateMinifiedAOM() {
203
- uidMap.clear();
204
- nextUid = 1;
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
- ];
212
- const nodes = [];
213
- interactiveCandidates.forEach((candidate) => {
214
- const el = resolveEditableControlElement(candidate);
215
- if (interactiveSet.has(el)) {
216
- return;
422
+ // src/utils/editable-controls.ts
423
+ function getTagName(element) {
424
+ return String(element?.tagName || "").toLowerCase();
425
+ }
426
+ function getRole(element) {
427
+ const role = element && typeof element.getAttribute === "function" ? element.getAttribute("role") : null;
428
+ return String(role || "").toLowerCase();
429
+ }
430
+ function getLabelControl(element) {
431
+ const labelCtor = typeof HTMLLabelElement !== "undefined" ? HTMLLabelElement : null;
432
+ if (labelCtor && element instanceof labelCtor && element.control instanceof HTMLElement) {
433
+ return element.control;
434
+ }
435
+ return null;
436
+ }
437
+ function isValueBearingElement(element) {
438
+ if (!element) return false;
439
+ const inputCtor = typeof HTMLInputElement !== "undefined" ? HTMLInputElement : null;
440
+ if (inputCtor && element instanceof inputCtor) return true;
441
+ const textareaCtor = typeof HTMLTextAreaElement !== "undefined" ? HTMLTextAreaElement : null;
442
+ if (textareaCtor && element instanceof textareaCtor) return true;
443
+ const selectCtor = typeof HTMLSelectElement !== "undefined" ? HTMLSelectElement : null;
444
+ if (selectCtor && element instanceof selectCtor) return true;
445
+ return ["input", "textarea", "select"].includes(getTagName(element));
446
+ }
447
+ function findEditableControlInShadowRoot(root) {
448
+ if (!root) return null;
449
+ if (root.shadowRoot) {
450
+ const found = root.shadowRoot.querySelector(EDITABLE_CONTROL_SELECTOR);
451
+ if (found) {
452
+ return resolveEditableControlElement(found);
217
453
  }
218
- interactiveSet.add(el);
219
- if (!el.offsetParent && (el.offsetWidth === 0 || el.offsetHeight === 0)) {
220
- return;
454
+ }
455
+ for (const child of Array.from(root.children)) {
456
+ if (child instanceof HTMLElement) {
457
+ const found = findEditableControlInShadowRoot(child);
458
+ if (found) return found;
221
459
  }
222
- if (el.closest("#modelnex-studio-root") || el.closest("#modelnex-active-agent-root")) {
460
+ }
461
+ return null;
462
+ }
463
+ function resolveEditableControlElement(element) {
464
+ const labelControl = getLabelControl(element);
465
+ if (labelControl) {
466
+ return resolveEditableControlElement(labelControl);
467
+ }
468
+ if (isValueBearingElement(element) || element.isContentEditable) {
469
+ return element;
470
+ }
471
+ const role = getRole(element);
472
+ if (EDITABLE_ROLES.has(role)) {
473
+ const nested2 = element.querySelector(EDITABLE_CONTROL_SELECTOR);
474
+ if (nested2 && nested2 !== element) {
475
+ return resolveEditableControlElement(nested2);
476
+ }
477
+ const shadowNested2 = findEditableControlInShadowRoot(element);
478
+ return shadowNested2 ?? element;
479
+ }
480
+ const nested = element.querySelector(EDITABLE_CONTROL_SELECTOR);
481
+ if (nested) {
482
+ return resolveEditableControlElement(nested);
483
+ }
484
+ const shadowNested = findEditableControlInShadowRoot(element);
485
+ return shadowNested ?? element;
486
+ }
487
+ function readEditableControlValue(element, options = {}) {
488
+ const target = resolveEditableControlElement(element);
489
+ const maskPasswords = options.maskPasswords !== false;
490
+ if (isValueBearingElement(target)) {
491
+ const type = String(target.type || "").toLowerCase();
492
+ if (maskPasswords && type === "password") {
493
+ return "***";
494
+ }
495
+ return String(target.value || "").trim().slice(0, 100);
496
+ }
497
+ const genericValue = target.value;
498
+ if (typeof genericValue === "string") {
499
+ return genericValue.trim().slice(0, 100);
500
+ }
501
+ if (typeof genericValue === "number" && Number.isFinite(genericValue)) {
502
+ return String(genericValue).trim().slice(0, 100);
503
+ }
504
+ if (target.isContentEditable) {
505
+ return String(target.textContent || "").trim().slice(0, 100);
506
+ }
507
+ const ariaValueText = target.getAttribute("aria-valuetext");
508
+ if (ariaValueText?.trim()) {
509
+ return ariaValueText.trim().slice(0, 100);
510
+ }
511
+ const ariaValueNow = target.getAttribute("aria-valuenow");
512
+ if (ariaValueNow?.trim()) {
513
+ return ariaValueNow.trim().slice(0, 100);
514
+ }
515
+ return String(target.textContent || "").trim().slice(0, 100);
516
+ }
517
+ function readEditableControlPlaceholder(element) {
518
+ const target = resolveEditableControlElement(element);
519
+ if (typeof target.placeholder === "string") {
520
+ return String(target.placeholder || "").trim().slice(0, 100);
521
+ }
522
+ return String(target.getAttribute("placeholder") || "").trim().slice(0, 100);
523
+ }
524
+ function readEditableControlName(element) {
525
+ const target = resolveEditableControlElement(element);
526
+ const explicitName = target.name;
527
+ if (typeof explicitName === "string" && explicitName.trim()) {
528
+ return explicitName.trim().slice(0, 100);
529
+ }
530
+ return String(
531
+ target.getAttribute("name") || target.getAttribute("id") || target.getAttribute("aria-label") || ""
532
+ ).trim().slice(0, 100);
533
+ }
534
+ function readEditableControlType(element) {
535
+ const target = resolveEditableControlElement(element);
536
+ const role = getRole(target);
537
+ if (role) return role;
538
+ const explicitType = target.type;
539
+ if (typeof explicitType === "string" && explicitType.trim()) {
540
+ return explicitType.trim().toLowerCase().slice(0, 50);
541
+ }
542
+ const attributeType = target.getAttribute("type");
543
+ if (attributeType?.trim()) {
544
+ return attributeType.trim().toLowerCase().slice(0, 50);
545
+ }
546
+ return getTagName(target);
547
+ }
548
+ function isEditableControlDisabled(element) {
549
+ const target = resolveEditableControlElement(element);
550
+ const ariaDisabled = String(target.getAttribute("aria-disabled") || "").toLowerCase();
551
+ if (ariaDisabled === "true") return true;
552
+ return Boolean(target.disabled);
553
+ }
554
+ function collectEditableControls(scope) {
555
+ const seen = /* @__PURE__ */ new Set();
556
+ const controls = [];
557
+ for (const rawElement of Array.from(scope.querySelectorAll(EDITABLE_CONTROL_SELECTOR))) {
558
+ const resolved = resolveEditableControlElement(rawElement);
559
+ if (seen.has(resolved)) continue;
560
+ if (isValueBearingElement(resolved) && String(resolved.type || "").toLowerCase() === "hidden") {
561
+ continue;
562
+ }
563
+ seen.add(resolved);
564
+ controls.push(resolved);
565
+ }
566
+ return controls;
567
+ }
568
+ var EDITABLE_ROLES, EDITABLE_CONTROL_SELECTOR;
569
+ var init_editable_controls = __esm({
570
+ "src/utils/editable-controls.ts"() {
571
+ "use strict";
572
+ EDITABLE_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox"]);
573
+ EDITABLE_CONTROL_SELECTOR = [
574
+ 'input:not([type="hidden"])',
575
+ "textarea",
576
+ "select",
577
+ '[contenteditable="true"]',
578
+ '[role="textbox"]',
579
+ '[role="combobox"]'
580
+ ].join(", ");
581
+ }
582
+ });
583
+
584
+ // src/utils/aom.ts
585
+ var aom_exports = {};
586
+ __export(aom_exports, {
587
+ clearAOMMap: () => clearAOMMap,
588
+ generateMinifiedAOM: () => generateMinifiedAOM,
589
+ getElementByUid: () => getElementByUid
590
+ });
591
+ function normalizeUidText(value) {
592
+ return String(value || "").replace(/\s+/g, " ").trim().slice(0, 120);
593
+ }
594
+ function buildStableUidLocator(el) {
595
+ const tag = el.tagName.toLowerCase();
596
+ const role = normalizeUidText(el.getAttribute("role"));
597
+ const testId = normalizeUidText(el.getAttribute("data-testid"));
598
+ const fingerprint = normalizeUidText(generateFingerprint(el));
599
+ const controlName = normalizeUidText(readEditableControlName(el));
600
+ const ariaLabel = normalizeUidText(el.getAttribute("aria-label"));
601
+ const placeholder = normalizeUidText(readEditableControlPlaceholder(el));
602
+ const inputType = normalizeUidText(el.type || el.getAttribute("type"));
603
+ const href = el instanceof HTMLAnchorElement ? normalizeUidText(el.getAttribute("href")) : "";
604
+ return [
605
+ tag,
606
+ role,
607
+ testId,
608
+ fingerprint,
609
+ controlName,
610
+ ariaLabel,
611
+ placeholder,
612
+ inputType,
613
+ href
614
+ ].join("::");
615
+ }
616
+ function allocateUid() {
617
+ return `node:${nextUid++}`;
618
+ }
619
+ function resolveStableUid(el, activeUids) {
620
+ const existingElementUid = elementUidMap.get(el);
621
+ if (existingElementUid && !activeUids.has(existingElementUid)) {
622
+ return existingElementUid;
623
+ }
624
+ const locatorKey = buildStableUidLocator(el);
625
+ const existingLocatorUid = locatorUidMap.get(locatorKey);
626
+ if (existingLocatorUid && !activeUids.has(existingLocatorUid)) {
627
+ elementUidMap.set(el, existingLocatorUid);
628
+ return existingLocatorUid;
629
+ }
630
+ const uid = allocateUid();
631
+ elementUidMap.set(el, uid);
632
+ if (locatorKey) {
633
+ locatorUidMap.set(locatorKey, uid);
634
+ }
635
+ return uid;
636
+ }
637
+ function generateMinifiedAOM() {
638
+ const interactiveSet = /* @__PURE__ */ new Set();
639
+ const activeUids = /* @__PURE__ */ new Set();
640
+ const nextUidMap = /* @__PURE__ */ new Map();
641
+ const interactiveCandidates = [
642
+ ...Array.from(document.querySelectorAll(
643
+ 'button, a, input, select, textarea, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"], [role="textbox"], [role="combobox"], [contenteditable="true"]'
644
+ )),
645
+ ...collectEditableControls(document)
646
+ ];
647
+ const nodes = [];
648
+ interactiveCandidates.forEach((candidate) => {
649
+ const el = resolveEditableControlElement(candidate);
650
+ if (interactiveSet.has(el)) {
223
651
  return;
224
652
  }
225
- const uid = `node:${nextUid++}`;
226
- uidMap.set(uid, el);
653
+ interactiveSet.add(el);
654
+ if (!el.offsetParent && (el.offsetWidth === 0 || el.offsetHeight === 0)) {
655
+ return;
656
+ }
657
+ if (el.closest("#modelnex-studio-root") || el.closest("#modelnex-active-agent-root")) {
658
+ return;
659
+ }
660
+ const uid = resolveStableUid(el, activeUids);
661
+ activeUids.add(uid);
662
+ nextUidMap.set(uid, el);
227
663
  let text = (el.textContent || "").replace(/\s+/g, " ").trim();
228
664
  const ariaLabel = el.getAttribute("aria-label");
229
665
  const placeholder = el.getAttribute("placeholder");
@@ -254,539 +690,169 @@ function generateMinifiedAOM() {
254
690
  const url = new URL(el.href);
255
691
  node.href = url.pathname + url.search + url.hash;
256
692
  } catch {
257
- node.href = el.getAttribute("href");
258
- }
259
- }
260
- nodes.push(node);
261
- });
262
- return { nodes };
263
- }
264
- function getElementByUid(uid) {
265
- return uidMap.get(uid) || null;
266
- }
267
- function clearAOMMap() {
268
- uidMap.clear();
269
- nextUid = 1;
270
- }
271
- var uidMap, nextUid;
272
- var init_aom = __esm({
273
- "src/utils/aom.ts"() {
274
- "use strict";
275
- init_editable_controls();
276
- uidMap = /* @__PURE__ */ new Map();
277
- nextUid = 1;
278
- }
279
- });
280
-
281
- // src/utils/dom-sync.ts
282
- var dom_sync_exports = {};
283
- __export(dom_sync_exports, {
284
- waitForDomSettle: () => waitForDomSettle
285
- });
286
- function waitForDomSettle(options = {}) {
287
- const { timeoutMs = 5e3, debounceMs = 400, minWaitMs = 100 } = options;
288
- return new Promise((resolve) => {
289
- let debounceTimer = null;
290
- let resolved = false;
291
- const maxTimer = setTimeout(() => {
292
- if (!resolved) {
293
- resolved = true;
294
- cleanup();
295
- resolve();
296
- }
297
- }, Math.max(timeoutMs, minWaitMs));
298
- const finish = () => {
299
- if (!resolved) {
300
- resolved = true;
301
- cleanup();
302
- resolve();
303
- }
304
- };
305
- const observer = new MutationObserver((mutations) => {
306
- const hasSignificantMutations = mutations.some((m) => {
307
- if (m.target instanceof HTMLElement) {
308
- if (m.target.hasAttribute("data-modelnex-tour-highlight")) return false;
309
- if (m.target.hasAttribute("data-modelnex-caption")) return false;
310
- if (m.target.closest("#modelnex-studio-root")) return false;
311
- if (m.target.closest("#modelnex-active-agent-root")) return false;
312
- }
313
- return true;
314
- });
315
- if (!hasSignificantMutations) return;
316
- if (debounceTimer) clearTimeout(debounceTimer);
317
- debounceTimer = setTimeout(finish, debounceMs);
318
- });
319
- const cleanup = () => {
320
- observer.disconnect();
321
- if (debounceTimer) clearTimeout(debounceTimer);
322
- clearTimeout(maxTimer);
323
- };
324
- setTimeout(() => {
325
- if (resolved) return;
326
- observer.observe(document.body, {
327
- childList: true,
328
- subtree: true,
329
- attributes: true,
330
- characterData: true
331
- });
332
- debounceTimer = setTimeout(finish, debounceMs);
333
- }, minWaitMs);
334
- });
335
- }
336
- var init_dom_sync = __esm({
337
- "src/utils/dom-sync.ts"() {
338
- "use strict";
339
- }
340
- });
341
-
342
- // src/index.ts
343
- var index_exports = {};
344
- __export(index_exports, {
345
- DEFAULT_MODELNEX_SERVER_URL: () => DEFAULT_MODELNEX_SERVER_URL,
346
- ModelNexChatBubble: () => ModelNexChatBubble,
347
- ModelNexOnboardingPanel: () => ModelNexOnboardingPanel,
348
- ModelNexProvider: () => ModelNexProvider,
349
- RecordingOverlay: () => RecordingOverlay,
350
- StudioOverlay: () => StudioOverlay,
351
- TourProgressPanel: () => TourProgressPanel,
352
- UIStateProvider: () => UIStateProvider,
353
- buildDraftPreviewUrl: () => buildDraftPreviewUrl,
354
- buildRecordingCapturePayload: () => buildRecordingCapturePayload,
355
- buildRecordingStepGoal: () => buildRecordingStepGoal,
356
- clearActiveDraftPreview: () => clearActiveDraftPreview,
357
- extractInteractiveElements: () => extractInteractiveElements,
358
- generateFingerprint: () => generateFingerprint,
359
- getPreviewQueryParamName: () => getPreviewQueryParamName,
360
- getRecordingDraftActionLabel: () => getRecordingDraftActionLabel,
361
- getRecordingDraftStatusMessage: () => getRecordingDraftStatusMessage,
362
- hasDraftPreviewModeSignal: () => hasDraftPreviewModeSignal,
363
- inferOnboardingMetadataForStep: () => inferOnboardingMetadataForStep,
364
- isAskDrivenInputStepType: () => isAskDrivenInputStepType,
365
- isInteractiveInputStepType: () => isInteractiveInputStepType,
366
- isManualOnboardingStep: () => isManualOnboardingStep,
367
- isRecordingDraftGenerating: () => isRecordingDraftGenerating,
368
- observeDraftPreviewModeSignal: () => observeDraftPreviewModeSignal,
369
- persistActiveDraftPreview: () => persistActiveDraftPreview,
370
- readActiveDraftPreview: () => readActiveDraftPreview,
371
- shouldPromptForPreviewStart: () => shouldPromptForPreviewStart,
372
- shouldShowRecordingOverlay: () => shouldShowRecordingOverlay,
373
- useActionHighlight: () => useActionHighlight,
374
- useAgentViewport: () => useAgentViewport,
375
- useAutoExtract: () => useAutoExtract,
376
- useExperiencePlayback: () => useExperiencePlayback,
377
- useOnboardingPlayback: () => useOnboardingPlayback,
378
- useRecordingMode: () => useRecordingMode,
379
- useRunCommand: () => useRunCommand,
380
- useTagStore: () => useTagStore,
381
- useTourPlayback: () => useTourPlayback,
382
- useUIState: () => useUIState,
383
- useViewportTrack: () => useViewportTrack,
384
- useVisibleIds: () => useVisibleIds,
385
- useVoice: () => useVoice
386
- });
387
- module.exports = __toCommonJS(index_exports);
388
- var import_react21 = __toESM(require("react"));
389
-
390
- // src/context.ts
391
- var import_react = require("react");
392
- var ModelNexContext = (0, import_react.createContext)(null);
393
-
394
- // src/hooks/useModelNexSocket.ts
395
- var import_react3 = require("react");
396
- var import_socket = require("socket.io-client");
397
-
398
- // src/utils/socket-io-transports.ts
399
- function resolveSocketIoTransports(serverUrl, order) {
400
- try {
401
- const host = new URL(serverUrl).hostname.toLowerCase();
402
- if (host.endsWith("awsapprunner.com")) {
403
- return ["websocket"];
404
- }
405
- } catch {
406
- }
407
- return ["websocket", "polling"];
408
- }
409
-
410
- // src/auto-extract.ts
411
- var import_react2 = require("react");
412
-
413
- // src/utils/dev-logging.ts
414
- function isSdkDebugEnabled(devMode) {
415
- if (devMode) return true;
416
- if (typeof window !== "undefined" && Boolean(window.MODELNEX_DEBUG)) {
417
- return true;
418
- }
419
- return process.env.NODE_ENV === "development";
420
- }
421
- function emitSdkDebugLog(message, payload, options) {
422
- if (!isSdkDebugEnabled(options?.devMode)) return;
423
- if (payload && Object.keys(payload).length > 0) {
424
- console.log(message, payload);
425
- } else {
426
- console.log(message);
427
- }
428
- if (options?.dispatchEvent && typeof window !== "undefined") {
429
- window.dispatchEvent(new CustomEvent("modelnex-debug", { detail: { msg: message, data: payload } }));
430
- }
431
- }
432
- function sanitizeActionList(actions) {
433
- if (!Array.isArray(actions) || actions.length === 0) return void 0;
434
- return actions.map(({ actionId }) => ({ actionId }));
435
- }
436
- function sanitizeAgentDebug(debug) {
437
- if (!debug) return void 0;
438
- const actions = sanitizeActionList(debug.actions);
439
- const traces = Array.isArray(debug.traces) && debug.traces.length > 0 ? debug.traces.map((trace) => ({
440
- step: trace.step,
441
- actions: sanitizeActionList(trace.actions) ?? [],
442
- results: Array.isArray(trace.results) && trace.results.length > 0 ? trace.results.map(({ actionId, success }) => ({ actionId, success })) : void 0
443
- })) : void 0;
444
- if ((!actions || actions.length === 0) && (!traces || traces.length === 0)) {
445
- return void 0;
446
- }
447
- return {
448
- ...actions ? { actions } : {},
449
- ...traces ? { traces } : {}
450
- };
451
- }
452
- function sanitizeChatMessages(messages, devMode) {
453
- const includeDebug = isSdkDebugEnabled(devMode);
454
- return messages.map((message) => {
455
- if (message.role !== "assistant") {
456
- return message;
457
- }
458
- return {
459
- ...message,
460
- debug: includeDebug ? sanitizeAgentDebug(message.debug) : void 0
461
- };
462
- });
463
- }
464
-
465
- // src/auto-extract.ts
466
- function simpleHash(str) {
467
- let hash = 0;
468
- for (let i = 0; i < str.length; i++) {
469
- const char = str.charCodeAt(i);
470
- hash = (hash << 5) - hash + char;
471
- hash |= 0;
472
- }
473
- return (hash >>> 0).toString(16).padStart(8, "0");
474
- }
475
- function isStableDomId(id) {
476
- if (!id) return false;
477
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
478
- const REACT_USE_ID_RE = /^(?:radix-)?:[rR][^\s]*$/;
479
- if (REACT_USE_ID_RE.test(id)) return false;
480
- if (/^\d+$/.test(id)) return false;
481
- if (UUID_RE.test(id)) return false;
482
- return true;
483
- }
484
- function getNearestHeading(el) {
485
- const shellContainer = el.closest('nav, aside, header, [role="navigation"], [role="complementary"], [role="banner"]');
486
- const boundary = shellContainer || document.body;
487
- let current = el;
488
- while (current && current !== boundary) {
489
- let sibling = current.previousElementSibling;
490
- while (sibling) {
491
- if (/^H[1-6]$/.test(sibling.tagName)) {
492
- return sibling.textContent?.trim().slice(0, 100) || null;
493
- }
494
- const innerHeading = sibling.querySelector("h1, h2, h3, h4, h5, h6");
495
- if (innerHeading) {
496
- return innerHeading.textContent?.trim().slice(0, 100) || null;
497
- }
498
- sibling = sibling.previousElementSibling;
499
- }
500
- const parentHeading = current.parentElement?.querySelector("h1, h2, h3, h4, h5, h6");
501
- if (parentHeading && !current.contains(parentHeading)) {
502
- return parentHeading.textContent?.trim().slice(0, 100) || null;
503
- }
504
- current = current.parentElement;
505
- }
506
- return null;
507
- }
508
- function getParentContainer(el) {
509
- let current = el.parentElement;
510
- while (current && current !== document.body) {
511
- const role = current.getAttribute("role");
512
- const tag = current.tagName.toLowerCase();
513
- const state = current.getAttribute("data-state");
514
- if (role === "dialog" || role === "alertdialog" || tag === "dialog") {
515
- const title = current.querySelector('h2, h3, [class*="title"]');
516
- const titleText = title?.textContent?.trim().slice(0, 60) || "";
517
- return titleText ? `dialog:${titleText}` : "dialog";
518
- }
519
- if (role === "menu" || role === "listbox") {
520
- return role;
521
- }
522
- if (current.hasAttribute("popover") || state === "open" && current.getAttribute("data-radix-popper-content-wrapper") !== null) {
523
- return "popover";
524
- }
525
- if (current.getAttribute("data-radix-menu-content") !== null || role === "menubar") {
526
- return "dropdown-menu";
527
- }
528
- if (role === "tabpanel") {
529
- const label = current.getAttribute("aria-label") || current.getAttribute("aria-labelledby");
530
- return label ? `tabpanel:${label.slice(0, 40)}` : "tabpanel";
531
- }
532
- if (tag === "nav" || role === "navigation") {
533
- const label = current.getAttribute("aria-label");
534
- return label ? `navigation:${label.slice(0, 40)}` : "navigation";
535
- }
536
- if (tag === "aside") {
537
- return "sidebar";
538
- }
539
- if (tag === "header") {
540
- return "header";
541
- }
542
- current = current.parentElement;
543
- }
544
- return null;
545
- }
546
- function getRowContext(el) {
547
- let current = el.parentElement;
548
- while (current && current !== document.body) {
549
- const tag = current.tagName.toLowerCase();
550
- if (tag === "tr" || tag === "li" || current.getAttribute("role") === "row" || current.getAttribute("role") === "listitem") {
551
- const rowText = current.textContent?.trim().replace(/\s+/g, " ").slice(0, 120) || "";
552
- return rowText;
553
- }
554
- current = current.parentElement;
555
- }
556
- return "";
557
- }
558
- function getNearestAncestorId(el) {
559
- let current = el.parentElement;
560
- while (current && current !== document.body) {
561
- const aid = current.id;
562
- if (isStableDomId(aid)) {
563
- return aid;
564
- }
565
- const ancestorTestId = current.getAttribute("data-testid");
566
- if (ancestorTestId) return `[data-testid="${ancestorTestId}"]`;
567
- current = current.parentElement;
568
- }
569
- return null;
570
- }
571
- function generateFingerprint(el) {
572
- const tag = el.tagName.toLowerCase();
573
- const testId = el.getAttribute("data-testid");
574
- const id = el.id;
575
- const name = el.getAttribute("name");
576
- const ariaLabel = el.getAttribute("aria-label");
577
- const type = el.type || "";
578
- const text = el.textContent?.trim().slice(0, 80) || "";
579
- if (testId) return `tid:${testId}`;
580
- if (isStableDomId(id)) return `id:${id}`;
581
- if (name) return `name:${tag}:${name}`;
582
- if (ariaLabel) {
583
- const rowCtx2 = getRowContext(el);
584
- if (rowCtx2) {
585
- return `aria:${simpleHash(ariaLabel + ":" + rowCtx2)}:${ariaLabel.slice(0, 40)}`;
586
- }
587
- return `aria:${simpleHash(ariaLabel)}:${ariaLabel.slice(0, 40)}`;
588
- }
589
- if (tag === "a") {
590
- const href = el.getAttribute("href");
591
- if (href && href !== "#" && !href.startsWith("javascript:")) {
592
- return `href:${simpleHash(href + ":" + text)}:${text.slice(0, 30) || href.slice(0, 30)}`;
593
- }
594
- }
595
- const ancestorId = getNearestAncestorId(el);
596
- if (ancestorId) {
597
- return `anc:${simpleHash(ancestorId + ":" + tag + ":" + text)}:${text.slice(0, 30) || tag}`;
598
- }
599
- const heading = getNearestHeading(el) || "";
600
- const rowCtx = getRowContext(el);
601
- const raw = `${tag}:${type}:${text}:${heading}:${rowCtx}`;
602
- return `hash:${simpleHash(raw)}:${text.slice(0, 30) || tag}`;
603
- }
604
- function classifyRole(el) {
605
- const tag = el.tagName.toLowerCase();
606
- const role = el.getAttribute("role");
607
- if (tag === "button" || role === "button") return "button";
608
- if (tag === "a") return "link";
609
- if (tag === "input") return "input";
610
- if (tag === "textarea") return "textarea";
611
- if (tag === "select") return "select";
612
- if (tag === "form") return "form";
613
- if (tag === "label") return "label";
614
- if (role === "link") return "link";
615
- if (role === "menuitem") return "menuitem";
616
- if (role === "menuitemcheckbox") return "menuitem";
617
- if (role === "menuitemradio") return "menuitem";
618
- if (role === "tab") return "tab";
619
- if (role === "checkbox") return "checkbox";
620
- if (role === "radio") return "radio";
621
- if (role === "switch") return "switch";
622
- if (role === "slider") return "slider";
623
- if (role === "combobox") return "combobox";
624
- if (role === "option") return "option";
625
- if (role === "treeitem") return "treeitem";
626
- if (role === "gridcell") return "gridcell";
627
- if (el.hasAttribute("tabindex") && el.getAttribute("tabindex") !== "-1") return "interactive";
628
- if (el.hasAttribute("cmdk-item")) return "menuitem";
629
- return null;
630
- }
631
- var INTERACTIVE_SELECTOR = [
632
- "button",
633
- "a",
634
- // All anchor tags (SPA links may not have href)
635
- 'input:not([type="hidden"])',
636
- "textarea",
637
- "select",
638
- "label[for]",
639
- '[role="button"]',
640
- '[role="link"]',
641
- '[role="menuitem"]',
642
- '[role="menuitemcheckbox"]',
643
- '[role="menuitemradio"]',
644
- '[role="tab"]',
645
- '[role="checkbox"]',
646
- '[role="radio"]',
647
- '[role="switch"]',
648
- '[role="slider"]',
649
- '[role="combobox"]',
650
- '[role="option"]',
651
- '[role="treeitem"]',
652
- '[role="gridcell"]',
653
- '[tabindex]:not([tabindex="-1"])',
654
- "[data-discover]",
655
- // React Router links
656
- "[cmdk-item]",
657
- // cmdk menu items
658
- "form"
659
- ].join(", ");
660
- function isVisible(el) {
661
- if (el.offsetParent === null) {
662
- const computed = window.getComputedStyle(el);
663
- if (computed.position !== "fixed" && computed.position !== "sticky") {
664
- if (computed.display === "none" || computed.visibility === "hidden") {
665
- return false;
693
+ node.href = el.getAttribute("href");
666
694
  }
667
- const rect2 = el.getBoundingClientRect();
668
- if (rect2.width === 0 && rect2.height === 0) return false;
669
695
  }
696
+ nodes.push(node);
697
+ });
698
+ uidMap.clear();
699
+ for (const [uid, el] of nextUidMap.entries()) {
700
+ uidMap.set(uid, el);
670
701
  }
671
- const style = window.getComputedStyle(el);
672
- if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
673
- return false;
674
- }
675
- const rect = el.getBoundingClientRect();
676
- return rect.width > 0 && rect.height > 0;
702
+ return { nodes };
677
703
  }
678
- function extractInteractiveElements() {
679
- const elements = [];
680
- const seen = /* @__PURE__ */ new Set();
681
- try {
682
- const nodes = document.querySelectorAll(INTERACTIVE_SELECTOR);
683
- nodes.forEach((node) => {
684
- const el = node;
685
- if (!isVisible(el)) return;
686
- if (el.closest(".modelnex-chat-panel, .modelnex-studio-overlay, [data-modelnex-internal]")) return;
687
- const role = classifyRole(el);
688
- if (!role) return;
689
- let fingerprint = generateFingerprint(el);
690
- if (seen.has(fingerprint)) {
691
- let idx = 2;
692
- while (seen.has(`${fingerprint}#${idx}`)) idx++;
693
- fingerprint = `${fingerprint}#${idx}`;
694
- }
695
- seen.add(fingerprint);
696
- const rect = el.getBoundingClientRect();
697
- const text = el.textContent?.trim().slice(0, 200) || "";
698
- elements.push({
699
- fingerprint,
700
- tagName: el.tagName.toLowerCase(),
701
- text,
702
- role,
703
- rect: {
704
- top: rect.top,
705
- left: rect.left,
706
- width: rect.width,
707
- height: rect.height
708
- },
709
- attributes: {
710
- id: el.id || void 0,
711
- name: el.getAttribute("name") || void 0,
712
- type: el.type || void 0,
713
- ariaLabel: el.getAttribute("aria-label") || void 0,
714
- dataTestId: el.getAttribute("data-testid") || void 0,
715
- href: el.href || void 0,
716
- placeholder: el.placeholder || void 0
717
- },
718
- nearestHeading: getNearestHeading(el),
719
- parentContainer: getParentContainer(el),
720
- disabled: el.disabled || el.getAttribute("aria-disabled") === "true",
721
- element: el
722
- });
723
- });
724
- } catch (err) {
725
- console.warn("[ModelNex] Auto-extraction error:", err);
726
- }
727
- return elements;
704
+ function getElementByUid(uid) {
705
+ return uidMap.get(uid) || null;
728
706
  }
729
- function useAutoExtract(devMode) {
730
- const [elements, setElements] = (0, import_react2.useState)([]);
731
- const timerRef = (0, import_react2.useRef)(null);
732
- const lastSnapshotRef = (0, import_react2.useRef)("");
733
- const scan = (0, import_react2.useCallback)(() => {
734
- if (typeof document === "undefined") return;
735
- const extracted = extractInteractiveElements();
736
- const snapshot = JSON.stringify(extracted.map((e) => ({
737
- fingerprint: e.fingerprint,
738
- role: e.role,
739
- text: e.text,
740
- rect: e.rect,
741
- disabled: e.disabled,
742
- nearestHeading: e.nearestHeading,
743
- parentContainer: e.parentContainer
744
- })));
745
- if (snapshot === lastSnapshotRef.current) return;
746
- lastSnapshotRef.current = snapshot;
747
- emitSdkDebugLog("[ModelNex AutoExtract] Scan complete", {
748
- elementCount: extracted.length
749
- }, { devMode });
750
- setElements(extracted);
751
- }, [devMode]);
752
- (0, import_react2.useEffect)(() => {
753
- const initialTimer = setTimeout(scan, 300);
707
+ function clearAOMMap() {
708
+ uidMap.clear();
709
+ locatorUidMap.clear();
710
+ nextUid = 1;
711
+ }
712
+ var uidMap, elementUidMap, locatorUidMap, nextUid;
713
+ var init_aom = __esm({
714
+ "src/utils/aom.ts"() {
715
+ "use strict";
716
+ init_editable_controls();
717
+ init_auto_extract();
718
+ uidMap = /* @__PURE__ */ new Map();
719
+ elementUidMap = /* @__PURE__ */ new WeakMap();
720
+ locatorUidMap = /* @__PURE__ */ new Map();
721
+ nextUid = 1;
722
+ }
723
+ });
724
+
725
+ // src/utils/dom-sync.ts
726
+ var dom_sync_exports = {};
727
+ __export(dom_sync_exports, {
728
+ waitForDomSettle: () => waitForDomSettle
729
+ });
730
+ function waitForDomSettle(options = {}) {
731
+ const { timeoutMs = 5e3, debounceMs = 400, minWaitMs = 100 } = options;
732
+ return new Promise((resolve) => {
733
+ let debounceTimer = null;
734
+ let resolved = false;
735
+ const maxTimer = setTimeout(() => {
736
+ if (!resolved) {
737
+ resolved = true;
738
+ cleanup();
739
+ resolve();
740
+ }
741
+ }, Math.max(timeoutMs, minWaitMs));
742
+ const finish = () => {
743
+ if (!resolved) {
744
+ resolved = true;
745
+ cleanup();
746
+ resolve();
747
+ }
748
+ };
754
749
  const observer = new MutationObserver((mutations) => {
755
- const hasRelevantMutation = mutations.some((mutation) => {
756
- const target = mutation.target;
757
- if (target?.closest?.(".modelnex-chat-panel, .modelnex-studio-overlay, [data-modelnex-internal]")) {
758
- return false;
750
+ const hasSignificantMutations = mutations.some((m) => {
751
+ if (m.target instanceof HTMLElement) {
752
+ if (m.target.hasAttribute("data-modelnex-tour-highlight")) return false;
753
+ if (m.target.hasAttribute("data-modelnex-caption")) return false;
754
+ if (m.target.closest("#modelnex-studio-root")) return false;
755
+ if (m.target.closest("#modelnex-active-agent-root")) return false;
759
756
  }
760
757
  return true;
761
758
  });
762
- if (!hasRelevantMutation) return;
763
- if (timerRef.current) clearTimeout(timerRef.current);
764
- timerRef.current = setTimeout(scan, 500);
765
- });
766
- observer.observe(document.body, {
767
- childList: true,
768
- subtree: true,
769
- attributes: true,
770
- attributeFilter: ["disabled", "aria-disabled", "hidden", "style", "class"]
759
+ if (!hasSignificantMutations) return;
760
+ if (debounceTimer) clearTimeout(debounceTimer);
761
+ debounceTimer = setTimeout(finish, debounceMs);
771
762
  });
772
- const handlePositionChange = () => {
773
- if (timerRef.current) clearTimeout(timerRef.current);
774
- timerRef.current = setTimeout(scan, 200);
775
- };
776
- window.addEventListener("scroll", handlePositionChange, { passive: true });
777
- window.addEventListener("resize", handlePositionChange, { passive: true });
778
- return () => {
779
- clearTimeout(initialTimer);
780
- if (timerRef.current) clearTimeout(timerRef.current);
763
+ const cleanup = () => {
781
764
  observer.disconnect();
782
- window.removeEventListener("scroll", handlePositionChange);
783
- window.removeEventListener("resize", handlePositionChange);
765
+ if (debounceTimer) clearTimeout(debounceTimer);
766
+ clearTimeout(maxTimer);
784
767
  };
785
- }, [scan]);
786
- return elements;
768
+ setTimeout(() => {
769
+ if (resolved) return;
770
+ observer.observe(document.body, {
771
+ childList: true,
772
+ subtree: true,
773
+ attributes: true,
774
+ characterData: true
775
+ });
776
+ debounceTimer = setTimeout(finish, debounceMs);
777
+ }, minWaitMs);
778
+ });
779
+ }
780
+ var init_dom_sync = __esm({
781
+ "src/utils/dom-sync.ts"() {
782
+ "use strict";
783
+ }
784
+ });
785
+
786
+ // src/index.ts
787
+ var index_exports = {};
788
+ __export(index_exports, {
789
+ DEFAULT_MODELNEX_SERVER_URL: () => DEFAULT_MODELNEX_SERVER_URL,
790
+ ModelNexChatBubble: () => ModelNexChatBubble,
791
+ ModelNexOnboardingPanel: () => ModelNexOnboardingPanel,
792
+ ModelNexProvider: () => ModelNexProvider,
793
+ RecordingOverlay: () => RecordingOverlay,
794
+ StudioOverlay: () => StudioOverlay,
795
+ TourProgressPanel: () => TourProgressPanel,
796
+ UIStateProvider: () => UIStateProvider,
797
+ buildDraftPreviewUrl: () => buildDraftPreviewUrl,
798
+ buildRecordingCapturePayload: () => buildRecordingCapturePayload,
799
+ buildRecordingStepGoal: () => buildRecordingStepGoal,
800
+ clearActiveDraftPreview: () => clearActiveDraftPreview,
801
+ extractInteractiveElements: () => extractInteractiveElements,
802
+ generateFingerprint: () => generateFingerprint,
803
+ getPreviewQueryParamName: () => getPreviewQueryParamName,
804
+ getRecordingDraftActionLabel: () => getRecordingDraftActionLabel,
805
+ getRecordingDraftStatusMessage: () => getRecordingDraftStatusMessage,
806
+ hasDraftPreviewModeSignal: () => hasDraftPreviewModeSignal,
807
+ inferOnboardingMetadataForStep: () => inferOnboardingMetadataForStep,
808
+ isAskDrivenInputStepType: () => isAskDrivenInputStepType,
809
+ isInteractiveInputStepType: () => isInteractiveInputStepType,
810
+ isManualOnboardingStep: () => isManualOnboardingStep,
811
+ isRecordingDraftGenerating: () => isRecordingDraftGenerating,
812
+ observeDraftPreviewModeSignal: () => observeDraftPreviewModeSignal,
813
+ persistActiveDraftPreview: () => persistActiveDraftPreview,
814
+ readActiveDraftPreview: () => readActiveDraftPreview,
815
+ shouldPromptForPreviewStart: () => shouldPromptForPreviewStart,
816
+ shouldShowRecordingOverlay: () => shouldShowRecordingOverlay,
817
+ useActionHighlight: () => useActionHighlight,
818
+ useAgentViewport: () => useAgentViewport,
819
+ useAutoExtract: () => useAutoExtract,
820
+ useExperiencePlayback: () => useExperiencePlayback,
821
+ useOnboardingPlayback: () => useOnboardingPlayback,
822
+ useRecordingMode: () => useRecordingMode,
823
+ useRunCommand: () => useRunCommand,
824
+ useTagStore: () => useTagStore,
825
+ useTourPlayback: () => useTourPlayback,
826
+ useUIState: () => useUIState,
827
+ useViewportTrack: () => useViewportTrack,
828
+ useVisibleIds: () => useVisibleIds,
829
+ useVoice: () => useVoice
830
+ });
831
+ module.exports = __toCommonJS(index_exports);
832
+ var import_react21 = __toESM(require("react"));
833
+
834
+ // src/context.ts
835
+ var import_react = require("react");
836
+ var ModelNexContext = (0, import_react.createContext)(null);
837
+
838
+ // src/hooks/useModelNexSocket.ts
839
+ var import_react3 = require("react");
840
+ var import_socket = require("socket.io-client");
841
+
842
+ // src/utils/socket-io-transports.ts
843
+ function resolveSocketIoTransports(serverUrl, order) {
844
+ try {
845
+ const host = new URL(serverUrl).hostname.toLowerCase();
846
+ if (host.endsWith("awsapprunner.com")) {
847
+ return ["websocket"];
848
+ }
849
+ } catch {
850
+ }
851
+ return ["websocket", "polling"];
787
852
  }
788
853
 
789
854
  // src/utils/dom-summary.ts
855
+ init_auto_extract();
790
856
  init_editable_controls();
791
857
  function textOf(el) {
792
858
  return el?.textContent?.trim().slice(0, 200) ?? "";
@@ -1052,6 +1118,7 @@ function serializeContexts(contexts) {
1052
1118
  }
1053
1119
 
1054
1120
  // src/hooks/useModelNexSocket.ts
1121
+ init_dev_logging();
1055
1122
  function useModelNexSocket({
1056
1123
  serverUrl,
1057
1124
  actions,
@@ -1270,6 +1337,7 @@ function useFieldHighlight(stagingFields, executedFields, setExecutedFields) {
1270
1337
 
1271
1338
  // src/overlay.tsx
1272
1339
  var import_react5 = __toESM(require("react"));
1340
+ init_auto_extract();
1273
1341
  var AIActivityOverlay = ({ activeActions }) => {
1274
1342
  if (activeActions.size === 0) return null;
1275
1343
  return import_react5.default.createElement(
@@ -1465,6 +1533,9 @@ body.modelnex-highlight-actions [data-modelnex-action-id]::before {
1465
1533
  }
1466
1534
  `;
1467
1535
 
1536
+ // src/index.ts
1537
+ init_auto_extract();
1538
+
1468
1539
  // src/tag-store.ts
1469
1540
  var import_react6 = require("react");
1470
1541
  var STORAGE_KEY = "modelnex-tags";
@@ -1614,6 +1685,7 @@ function useTagStore(options) {
1614
1685
 
1615
1686
  // src/studio-mode.tsx
1616
1687
  var import_react7 = require("react");
1688
+ init_auto_extract();
1617
1689
  var import_jsx_runtime = require("react/jsx-runtime");
1618
1690
  var CATEGORIES = [
1619
1691
  { value: "button", label: "Button" },
@@ -2038,6 +2110,7 @@ function StudioOverlay({ elements, tagStore }) {
2038
2110
  // src/hooks/useBuiltinActions.ts
2039
2111
  var import_react8 = require("react");
2040
2112
  var import_zod2 = require("zod");
2113
+ init_auto_extract();
2041
2114
 
2042
2115
  // src/utils/screenshot.ts
2043
2116
  var import_html2canvas = __toESM(require("html2canvas"));
@@ -2940,6 +3013,9 @@ function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl
2940
3013
  // src/constants.ts
2941
3014
  var DEFAULT_MODELNEX_SERVER_URL = "https://api.modelnex.com";
2942
3015
 
3016
+ // src/index.ts
3017
+ init_dev_logging();
3018
+
2943
3019
  // src/utils/dev-mode.ts
2944
3020
  var DEV_MODE_KEY_GLOBAL_NAMES = ["__MODELNEX_DEV_MODE_KEY__", "MODELNEX_DEV_MODE_KEY"];
2945
3021
  var devModeValidationCache = /* @__PURE__ */ new Map();
@@ -3255,6 +3331,7 @@ function readPreviewSessionSuppression() {
3255
3331
 
3256
3332
  // src/hooks/useRunCommand.ts
3257
3333
  var import_react9 = require("react");
3334
+ init_dev_logging();
3258
3335
  function searchTaggedElementsForQuery(store, query, limit = 8) {
3259
3336
  const allTags = store.getAllTags();
3260
3337
  if (allTags.length === 0) return [];
@@ -3638,6 +3715,7 @@ function releaseTourPlaybackOwnership(key, ownerId) {
3638
3715
  }
3639
3716
 
3640
3717
  // src/hooks/useTourPlayback.ts
3718
+ init_dev_logging();
3641
3719
  function resolveElement(step) {
3642
3720
  const el = step.element;
3643
3721
  if (!el) return null;
@@ -6999,6 +7077,7 @@ function useAudioLevel(active) {
6999
7077
 
7000
7078
  // src/hooks/useRecordingMode.ts
7001
7079
  var import_react16 = require("react");
7080
+ init_auto_extract();
7002
7081
 
7003
7082
  // src/utils/tourStepTypes.ts
7004
7083
  function isAskDrivenInputStepType(stepType) {
@@ -12298,6 +12377,9 @@ function useActionHighlight() {
12298
12377
  };
12299
12378
  }
12300
12379
 
12380
+ // src/index.ts
12381
+ init_auto_extract();
12382
+
12301
12383
  // src/hooks/useExperiencePlayback.ts
12302
12384
  function useExperiencePlayback(...args) {
12303
12385
  return useTourPlayback(...args);