@modelnex/sdk 0.5.43 → 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,763 +30,829 @@ 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")) {
223
- return;
224
- }
225
- const uid = `node:${nextUid++}`;
226
- uidMap.set(uid, el);
227
- let text = (el.textContent || "").replace(/\s+/g, " ").trim();
228
- const ariaLabel = el.getAttribute("aria-label");
229
- const placeholder = el.getAttribute("placeholder");
230
- const value = readEditableControlValue(el, { maskPasswords: false }) || void 0;
231
- let role = el.tagName.toLowerCase();
232
- if (el.hasAttribute("role")) {
233
- role = el.getAttribute("role");
234
- } else if (role === "a") {
235
- role = "link";
236
- } else if (el.tagName.toLowerCase() === "input") {
237
- const inputType = el.type;
238
- role = inputType ? `input[${inputType}]` : "input";
239
- }
240
- const node = { uid, role };
241
- const displayLabel = ariaLabel || text || placeholder;
242
- if (displayLabel) {
243
- node.text = displayLabel.substring(0, 100);
244
- }
245
- const controlName = readEditableControlName(el);
246
- if (controlName) {
247
- node.name = controlName;
248
- }
249
- if (value) {
250
- node.value = value.substring(0, 100);
251
- }
252
- if (el instanceof HTMLAnchorElement && el.href) {
253
- try {
254
- const url = new URL(el.href);
255
- node.href = url.pathname + url.search + url.hash;
256
- } 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
460
  }
407
- return ["websocket", "polling"];
461
+ return null;
408
462
  }
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;
463
+ function resolveEditableControlElement(element) {
464
+ const labelControl = getLabelControl(element);
465
+ if (labelControl) {
466
+ return resolveEditableControlElement(labelControl);
418
467
  }
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);
468
+ if (isValueBearingElement(element) || element.isContentEditable) {
469
+ return element;
427
470
  }
428
- if (options?.dispatchEvent && typeof window !== "undefined") {
429
- window.dispatchEvent(new CustomEvent("modelnex-debug", { detail: { msg: message, data: payload } }));
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;
430
479
  }
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;
480
+ const nested = element.querySelector(EDITABLE_CONTROL_SELECTOR);
481
+ if (nested) {
482
+ return resolveEditableControlElement(nested);
446
483
  }
447
- return {
448
- ...actions ? { actions } : {},
449
- ...traces ? { traces } : {}
450
- };
484
+ const shadowNested = findEditableControlInShadowRoot(element);
485
+ return shadowNested ?? element;
451
486
  }
452
- function sanitizeChatMessages(messages, devMode) {
453
- const includeDebug = isSdkDebugEnabled(devMode);
454
- return messages.map((message) => {
455
- if (message.role !== "assistant") {
456
- return message;
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 "***";
457
494
  }
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;
495
+ return String(target.value || "").trim().slice(0, 100);
472
496
  }
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;
497
+ const genericValue = target.value;
498
+ if (typeof genericValue === "string") {
499
+ return genericValue.trim().slice(0, 100);
505
500
  }
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;
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);
543
506
  }
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;
507
+ const ariaValueText = target.getAttribute("aria-valuetext");
508
+ if (ariaValueText?.trim()) {
509
+ return ariaValueText.trim().slice(0, 100);
555
510
  }
556
- return "";
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);
557
516
  }
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;
517
+ function readEditableControlPlaceholder(element) {
518
+ const target = resolveEditableControlElement(element);
519
+ if (typeof target.placeholder === "string") {
520
+ return String(target.placeholder || "").trim().slice(0, 100);
568
521
  }
569
- return null;
522
+ return String(target.getAttribute("placeholder") || "").trim().slice(0, 100);
570
523
  }
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)}`;
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);
588
529
  }
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
- }
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);
594
541
  }
595
- const ancestorId = getNearestAncestorId(el);
596
- if (ancestorId) {
597
- return `anc:${simpleHash(ancestorId + ":" + tag + ":" + text)}:${text.slice(0, 30) || tag}`;
542
+ const attributeType = target.getAttribute("type");
543
+ if (attributeType?.trim()) {
544
+ return attributeType.trim().toLowerCase().slice(0, 50);
598
545
  }
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}`;
546
+ return getTagName(target);
603
547
  }
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;
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);
630
553
  }
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;
666
- }
667
- const rect2 = el.getBoundingClientRect();
668
- if (rect2.width === 0 && rect2.height === 0) return false;
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;
669
562
  }
563
+ seen.add(resolved);
564
+ controls.push(resolved);
670
565
  }
671
- const style = window.getComputedStyle(el);
672
- if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
673
- return false;
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(", ");
674
581
  }
675
- const rect = el.getBoundingClientRect();
676
- return rect.width > 0 && rect.height > 0;
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);
677
593
  }
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);
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)) {
651
+ return;
652
+ }
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);
663
+ let text = (el.textContent || "").replace(/\s+/g, " ").trim();
664
+ const ariaLabel = el.getAttribute("aria-label");
665
+ const placeholder = el.getAttribute("placeholder");
666
+ const value = readEditableControlValue(el, { maskPasswords: false }) || void 0;
667
+ let role = el.tagName.toLowerCase();
668
+ if (el.hasAttribute("role")) {
669
+ role = el.getAttribute("role");
670
+ } else if (role === "a") {
671
+ role = "link";
672
+ } else if (el.tagName.toLowerCase() === "input") {
673
+ const inputType = el.type;
674
+ role = inputType ? `input[${inputType}]` : "input";
675
+ }
676
+ const node = { uid, role };
677
+ const displayLabel = ariaLabel || text || placeholder;
678
+ if (displayLabel) {
679
+ node.text = displayLabel.substring(0, 100);
680
+ }
681
+ const controlName = readEditableControlName(el);
682
+ if (controlName) {
683
+ node.name = controlName;
684
+ }
685
+ if (value) {
686
+ node.value = value.substring(0, 100);
687
+ }
688
+ if (el instanceof HTMLAnchorElement && el.href) {
689
+ try {
690
+ const url = new URL(el.href);
691
+ node.href = url.pathname + url.search + url.hash;
692
+ } catch {
693
+ node.href = el.getAttribute("href");
694
+ }
695
+ }
696
+ nodes.push(node);
697
+ });
698
+ uidMap.clear();
699
+ for (const [uid, el] of nextUidMap.entries()) {
700
+ uidMap.set(uid, el);
726
701
  }
727
- return elements;
702
+ return { nodes };
728
703
  }
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);
704
+ function getElementByUid(uid) {
705
+ return uidMap.get(uid) || null;
706
+ }
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,10 +2110,22 @@ 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"));
2044
- async function captureScreenshot(selector) {
2117
+ var SCREENSHOT_SCALE_BY_RESOLUTION = {
2118
+ low: 0.75,
2119
+ medium: 1.25,
2120
+ high: 2
2121
+ };
2122
+ function normalizeScreenshotResolution(resolution) {
2123
+ return resolution === "low" || resolution === "high" || resolution === "medium" ? resolution : "medium";
2124
+ }
2125
+ function resolveScreenshotScale(resolution) {
2126
+ return SCREENSHOT_SCALE_BY_RESOLUTION[normalizeScreenshotResolution(resolution)];
2127
+ }
2128
+ async function captureScreenshot(selector, resolution) {
2045
2129
  const target = selector ? document.querySelector(selector) : document.documentElement;
2046
2130
  if (!target || !(target instanceof HTMLElement)) {
2047
2131
  throw new Error(`Screenshot target not found: ${selector ?? "document"}`);
@@ -2049,7 +2133,7 @@ async function captureScreenshot(selector) {
2049
2133
  const canvas = await (0, import_html2canvas.default)(target, {
2050
2134
  useCORS: true,
2051
2135
  allowTaint: true,
2052
- scale: Math.min(window.devicePixelRatio, 2),
2136
+ scale: resolveScreenshotScale(resolution),
2053
2137
  width: window.innerWidth,
2054
2138
  height: window.innerHeight,
2055
2139
  x: window.scrollX,
@@ -2512,14 +2596,15 @@ async function resolveWorkflowFromInput(getters, params) {
2512
2596
  return { workflow: ranked[0].workflow };
2513
2597
  }
2514
2598
  var screenshotSchema = import_zod2.z.object({
2515
- selector: import_zod2.z.string().optional().describe("Optional CSS selector to capture a specific element. Omit to capture the full viewport.")
2599
+ selector: import_zod2.z.string().optional().describe("Optional CSS selector to capture a specific element. Omit to capture the full viewport."),
2600
+ resolution: import_zod2.z.enum(["low", "medium", "high"]).optional().describe("Optional screenshot resolution. Use low for smaller/faster captures, medium for the default balance, or high for extra detail.")
2516
2601
  });
2517
2602
  var BUILTIN_SCREENSHOT_ACTION = {
2518
2603
  id: "take_screenshot",
2519
2604
  description: "Take a screenshot of the current page (or a specific element via CSS selector). Returns a base64 PNG image. Use when you need to see the current UI state, verify a visual change, or when the user asks you to look at the screen.",
2520
2605
  schema: screenshotSchema,
2521
2606
  execute: async (params) => {
2522
- return await captureScreenshot(params.selector);
2607
+ return await captureScreenshot(params.selector, params.resolution);
2523
2608
  }
2524
2609
  };
2525
2610
  var clickElementSchema = import_zod2.z.object({
@@ -2928,6 +3013,9 @@ function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl
2928
3013
  // src/constants.ts
2929
3014
  var DEFAULT_MODELNEX_SERVER_URL = "https://api.modelnex.com";
2930
3015
 
3016
+ // src/index.ts
3017
+ init_dev_logging();
3018
+
2931
3019
  // src/utils/dev-mode.ts
2932
3020
  var DEV_MODE_KEY_GLOBAL_NAMES = ["__MODELNEX_DEV_MODE_KEY__", "MODELNEX_DEV_MODE_KEY"];
2933
3021
  var devModeValidationCache = /* @__PURE__ */ new Map();
@@ -3243,6 +3331,7 @@ function readPreviewSessionSuppression() {
3243
3331
 
3244
3332
  // src/hooks/useRunCommand.ts
3245
3333
  var import_react9 = require("react");
3334
+ init_dev_logging();
3246
3335
  function searchTaggedElementsForQuery(store, query, limit = 8) {
3247
3336
  const allTags = store.getAllTags();
3248
3337
  if (allTags.length === 0) return [];
@@ -3529,6 +3618,54 @@ function compactTourForTransport(tour) {
3529
3618
  };
3530
3619
  }
3531
3620
 
3621
+ // src/utils/tourSessionPersistence.ts
3622
+ var ACTIVE_TOUR_SESSION_STORAGE_KEY = "modelnex-active-tour-session";
3623
+ var ACTIVE_TOUR_SESSION_VERSION = 1;
3624
+ var ACTIVE_TOUR_SESSION_MAX_AGE_MS = 10 * 60 * 1e3;
3625
+ function canUseSessionStorage2() {
3626
+ return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
3627
+ }
3628
+ function persistActiveTourSession(runId) {
3629
+ if (!canUseSessionStorage2() || !Number.isFinite(runId)) return;
3630
+ const session = {
3631
+ version: ACTIVE_TOUR_SESSION_VERSION,
3632
+ runId,
3633
+ savedAt: Date.now()
3634
+ };
3635
+ try {
3636
+ window.sessionStorage.setItem(ACTIVE_TOUR_SESSION_STORAGE_KEY, JSON.stringify(session));
3637
+ } catch {
3638
+ }
3639
+ }
3640
+ function clearPersistedActiveTourSession() {
3641
+ if (!canUseSessionStorage2()) return;
3642
+ try {
3643
+ window.sessionStorage.removeItem(ACTIVE_TOUR_SESSION_STORAGE_KEY);
3644
+ } catch {
3645
+ }
3646
+ }
3647
+ function readPersistedActiveTourSession() {
3648
+ if (!canUseSessionStorage2()) return null;
3649
+ try {
3650
+ const raw = window.sessionStorage.getItem(ACTIVE_TOUR_SESSION_STORAGE_KEY);
3651
+ if (!raw) return null;
3652
+ const parsed = JSON.parse(raw);
3653
+ const isValid = parsed && parsed.version === ACTIVE_TOUR_SESSION_VERSION && typeof parsed.runId === "number" && Number.isFinite(parsed.runId) && typeof parsed.savedAt === "number" && Number.isFinite(parsed.savedAt);
3654
+ if (!isValid) {
3655
+ clearPersistedActiveTourSession();
3656
+ return null;
3657
+ }
3658
+ if (Date.now() - parsed.savedAt > ACTIVE_TOUR_SESSION_MAX_AGE_MS) {
3659
+ clearPersistedActiveTourSession();
3660
+ return null;
3661
+ }
3662
+ return parsed;
3663
+ } catch {
3664
+ clearPersistedActiveTourSession();
3665
+ return null;
3666
+ }
3667
+ }
3668
+
3532
3669
  // src/hooks/useTourPlayback.ts
3533
3670
  init_editable_controls();
3534
3671
 
@@ -3578,6 +3715,7 @@ function releaseTourPlaybackOwnership(key, ownerId) {
3578
3715
  }
3579
3716
 
3580
3717
  // src/hooks/useTourPlayback.ts
3718
+ init_dev_logging();
3581
3719
  function resolveElement(step) {
3582
3720
  const el = step.element;
3583
3721
  if (!el) return null;
@@ -4143,7 +4281,8 @@ function useTourPlayback({
4143
4281
  const pendingTourRef = (0, import_react12.useRef)(null);
4144
4282
  const pendingTourOptionsRef = (0, import_react12.useRef)(null);
4145
4283
  const showCaptionsRef = (0, import_react12.useRef)(showCaptions);
4146
- const runIdRef = (0, import_react12.useRef)(null);
4284
+ const initialPersistedTourSessionRef = (0, import_react12.useRef)(readPersistedActiveTourSession());
4285
+ const runIdRef = (0, import_react12.useRef)(initialPersistedTourSessionRef.current?.runId ?? null);
4147
4286
  const turnIdRef = (0, import_react12.useRef)(null);
4148
4287
  const startRequestedRef = (0, import_react12.useRef)(false);
4149
4288
  const playbackOwnerIdRef = (0, import_react12.useRef)(`playback-owner-${Math.random().toString(36).slice(2, 10)}`);
@@ -4171,6 +4310,19 @@ function useTourPlayback({
4171
4310
  releaseTourPlaybackOwnership(claimedKey, playbackOwnerIdRef.current);
4172
4311
  claimedPlaybackOwnerKeyRef.current = null;
4173
4312
  }, []);
4313
+ const emitTourInit = (0, import_react12.useCallback)((socket, currentWebsiteId, profile) => {
4314
+ if (!currentWebsiteId || !profile?.type) return;
4315
+ const persistedRunId = runIdRef.current ?? readPersistedActiveTourSession()?.runId ?? null;
4316
+ const payload = {
4317
+ websiteId: currentWebsiteId,
4318
+ userId: profile.userId,
4319
+ userType: profile.type
4320
+ };
4321
+ if (typeof persistedRunId === "number") {
4322
+ payload.resumeRunId = persistedRunId;
4323
+ }
4324
+ emitSocketEvent(socket, "tour:init", payload);
4325
+ }, []);
4174
4326
  (0, import_react12.useEffect)(() => {
4175
4327
  if (disabled) return;
4176
4328
  if (typeof window === "undefined") return;
@@ -4182,9 +4334,7 @@ function useTourPlayback({
4182
4334
  }, { devMode: devModeRef.current });
4183
4335
  const profile = userProfileRef.current;
4184
4336
  const currentWebsiteId = websiteIdRef.current;
4185
- if (currentWebsiteId && profile?.type) {
4186
- emitSocketEvent(socket, "tour:init", { websiteId: currentWebsiteId, userId: profile.userId, userType: profile.type });
4187
- }
4337
+ emitTourInit(socket, currentWebsiteId, profile);
4188
4338
  };
4189
4339
  const handleServerState = (payload) => {
4190
4340
  if (typeof payload?.runId === "number") {
@@ -4193,6 +4343,11 @@ function useTourPlayback({
4193
4343
  if (typeof payload?.turnId === "string" || payload?.turnId === null) {
4194
4344
  turnIdRef.current = payload.turnId ?? null;
4195
4345
  }
4346
+ if (payload?.isActive && typeof payload?.runId === "number") {
4347
+ persistActiveTourSession(payload.runId);
4348
+ } else if (payload?.isActive === false) {
4349
+ clearPersistedActiveTourSession();
4350
+ }
4196
4351
  setServerState(payload);
4197
4352
  };
4198
4353
  const handleCommandCancel = (payload) => {
@@ -4456,19 +4611,9 @@ function useTourPlayback({
4456
4611
  return { result: value };
4457
4612
  }
4458
4613
  if (action.type === "take_screenshot") {
4459
- const html2canvasModule = await import("html2canvas");
4460
- const html2canvas2 = html2canvasModule.default;
4461
- const canvas = await html2canvas2(document.body, {
4462
- useCORS: true,
4463
- allowTaint: true,
4464
- scale: Math.min(window.devicePixelRatio, 2),
4465
- width: window.innerWidth,
4466
- height: window.innerHeight,
4467
- x: window.scrollX,
4468
- y: window.scrollY,
4469
- logging: false
4470
- });
4471
- return { result: canvas.toDataURL("image/png") };
4614
+ const selector = typeof action.params?.selector === "string" ? action.params.selector : void 0;
4615
+ const resolution = typeof action.params?.resolution === "string" ? action.params.resolution : void 0;
4616
+ return { result: await captureScreenshot(selector, resolution) };
4472
4617
  }
4473
4618
  if (action.type === "navigate_to_url") {
4474
4619
  const nextUrl = typeof action.params?.url === "string" ? action.params.url : "";
@@ -4760,6 +4905,9 @@ function useTourPlayback({
4760
4905
  const handleTourStart = async (tourData) => {
4761
4906
  if (isActiveRef.current) return;
4762
4907
  runIdRef.current = typeof tourData.runId === "number" ? tourData.runId : runIdRef.current;
4908
+ if (typeof runIdRef.current === "number") {
4909
+ persistActiveTourSession(runIdRef.current);
4910
+ }
4763
4911
  const tour = tourData.tourContext ?? tourRef.current;
4764
4912
  const expType = experienceTypeRef.current;
4765
4913
  if (tour?.type && tour.type !== expType) {
@@ -4835,6 +4983,7 @@ function useTourPlayback({
4835
4983
  }
4836
4984
  };
4837
4985
  const handleTourEndEvent = () => {
4986
+ clearPersistedActiveTourSession();
4838
4987
  setServerState((prev) => prev ? { ...prev, isActive: false, phase: "completed" } : prev);
4839
4988
  handleTourEnd();
4840
4989
  };
@@ -4884,17 +5033,17 @@ function useTourPlayback({
4884
5033
  releasePlaybackOwnership();
4885
5034
  tourSocketPool.release(serverUrl, toClose);
4886
5035
  };
4887
- }, [serverUrl, disabled, releasePlaybackOwnership]);
5036
+ }, [serverUrl, disabled, emitTourInit, releasePlaybackOwnership]);
4888
5037
  (0, import_react12.useEffect)(() => {
4889
5038
  if (disabled) return;
4890
5039
  const s = socketRef.current;
4891
5040
  const profile = userProfile;
4892
5041
  if (!websiteId || !profile?.type) return;
4893
5042
  const timer = setTimeout(() => {
4894
- emitSocketEvent(s, "tour:init", { websiteId, userId: profile.userId, userType: profile.type });
5043
+ emitTourInit(s, websiteId, profile);
4895
5044
  }, 150);
4896
5045
  return () => clearTimeout(timer);
4897
- }, [disabled, websiteId, userProfile?.userId, userProfile?.type]);
5046
+ }, [disabled, emitTourInit, websiteId, userProfile?.userId, userProfile?.type]);
4898
5047
  (0, import_react12.useEffect)(() => {
4899
5048
  if (!showCaptions || !isReviewMode) {
4900
5049
  removeCaption();
@@ -5013,6 +5162,7 @@ function useTourPlayback({
5013
5162
  tourRef.current = null;
5014
5163
  setPlaybackState("idle");
5015
5164
  setServerState(null);
5165
+ clearPersistedActiveTourSession();
5016
5166
  runIdRef.current = null;
5017
5167
  turnIdRef.current = null;
5018
5168
  setPreviewRunId(null);
@@ -5042,6 +5192,7 @@ function useTourPlayback({
5042
5192
  tourRef.current = null;
5043
5193
  setPlaybackState("idle");
5044
5194
  setServerState(null);
5195
+ clearPersistedActiveTourSession();
5045
5196
  runIdRef.current = null;
5046
5197
  turnIdRef.current = null;
5047
5198
  setPreviewRunId(null);
@@ -6926,6 +7077,7 @@ function useAudioLevel(active) {
6926
7077
 
6927
7078
  // src/hooks/useRecordingMode.ts
6928
7079
  var import_react16 = require("react");
7080
+ init_auto_extract();
6929
7081
 
6930
7082
  // src/utils/tourStepTypes.ts
6931
7083
  function isAskDrivenInputStepType(stepType) {
@@ -12225,6 +12377,9 @@ function useActionHighlight() {
12225
12377
  };
12226
12378
  }
12227
12379
 
12380
+ // src/index.ts
12381
+ init_auto_extract();
12382
+
12228
12383
  // src/hooks/useExperiencePlayback.ts
12229
12384
  function useExperiencePlayback(...args) {
12230
12385
  return useTourPlayback(...args);