@modelnex/sdk 0.5.27 → 0.5.29

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.mjs CHANGED
@@ -27,6 +27,60 @@ function resolveSocketIoTransports(serverUrl, order) {
27
27
 
28
28
  // src/auto-extract.ts
29
29
  import { useState, useEffect as useEffect2, useRef as useRef2, useCallback as useCallback2 } from "react";
30
+
31
+ // src/utils/dev-logging.ts
32
+ function isSdkDebugEnabled(devMode) {
33
+ if (devMode) return true;
34
+ if (typeof window !== "undefined" && Boolean(window.MODELNEX_DEBUG)) {
35
+ return true;
36
+ }
37
+ return process.env.NODE_ENV === "development";
38
+ }
39
+ function emitSdkDebugLog(message, payload, options) {
40
+ if (!isSdkDebugEnabled(options?.devMode)) return;
41
+ if (payload && Object.keys(payload).length > 0) {
42
+ console.log(message, payload);
43
+ } else {
44
+ console.log(message);
45
+ }
46
+ if (options?.dispatchEvent && typeof window !== "undefined") {
47
+ window.dispatchEvent(new CustomEvent("modelnex-debug", { detail: { msg: message, data: payload } }));
48
+ }
49
+ }
50
+ function sanitizeActionList(actions) {
51
+ if (!Array.isArray(actions) || actions.length === 0) return void 0;
52
+ return actions.map(({ actionId }) => ({ actionId }));
53
+ }
54
+ function sanitizeAgentDebug(debug) {
55
+ if (!debug) return void 0;
56
+ const actions = sanitizeActionList(debug.actions);
57
+ const traces = Array.isArray(debug.traces) && debug.traces.length > 0 ? debug.traces.map((trace) => ({
58
+ step: trace.step,
59
+ actions: sanitizeActionList(trace.actions) ?? [],
60
+ results: Array.isArray(trace.results) && trace.results.length > 0 ? trace.results.map(({ actionId, success }) => ({ actionId, success })) : void 0
61
+ })) : void 0;
62
+ if ((!actions || actions.length === 0) && (!traces || traces.length === 0)) {
63
+ return void 0;
64
+ }
65
+ return {
66
+ ...actions ? { actions } : {},
67
+ ...traces ? { traces } : {}
68
+ };
69
+ }
70
+ function sanitizeChatMessages(messages, devMode) {
71
+ const includeDebug = isSdkDebugEnabled(devMode);
72
+ return messages.map((message) => {
73
+ if (message.role !== "assistant") {
74
+ return message;
75
+ }
76
+ return {
77
+ ...message,
78
+ debug: includeDebug ? sanitizeAgentDebug(message.debug) : void 0
79
+ };
80
+ });
81
+ }
82
+
83
+ // src/auto-extract.ts
30
84
  function simpleHash(str) {
31
85
  let hash = 0;
32
86
  for (let i = 0; i < str.length; i++) {
@@ -290,7 +344,7 @@ function extractInteractiveElements() {
290
344
  }
291
345
  return elements;
292
346
  }
293
- function useAutoExtract() {
347
+ function useAutoExtract(devMode) {
294
348
  const [elements, setElements] = useState([]);
295
349
  const timerRef = useRef2(null);
296
350
  const lastSnapshotRef = useRef2("");
@@ -308,9 +362,11 @@ function useAutoExtract() {
308
362
  })));
309
363
  if (snapshot === lastSnapshotRef.current) return;
310
364
  lastSnapshotRef.current = snapshot;
311
- console.log(`[ModelNex AutoExtract] Found ${extracted.length} interactive elements`, extracted.map((e) => ({ fp: e.fingerprint, role: e.role, text: e.text.slice(0, 30) })));
365
+ emitSdkDebugLog("[ModelNex AutoExtract] Scan complete", {
366
+ elementCount: extracted.length
367
+ }, { devMode });
312
368
  setElements(extracted);
313
- }, []);
369
+ }, [devMode]);
314
370
  useEffect2(() => {
315
371
  const initialTimer = setTimeout(scan, 300);
316
372
  const observer = new MutationObserver((mutations) => {
@@ -625,7 +681,8 @@ function useModelNexSocket({
625
681
  setStagingFields,
626
682
  setExecutedFields,
627
683
  onSocketId,
628
- websiteId
684
+ websiteId,
685
+ devMode
629
686
  }) {
630
687
  const socketRef = useRef3(null);
631
688
  const actionsRef = useRef3(actions);
@@ -658,12 +715,14 @@ function useModelNexSocket({
658
715
  allTaggedElements: tagsRef.current ? Array.from(tagsRef.current.values()) : void 0
659
716
  });
660
717
  socket.on("connect", () => {
661
- console.log("ModelNex SDK: Connected to agent server", socket.id);
718
+ emitSdkDebugLog("[ModelNex SDK] Connected to agent server", {
719
+ socketId: socket.id ?? null
720
+ }, { devMode });
662
721
  onSocketId?.(socket.id || null);
663
722
  socket.emit("client:sync", buildSyncPayload());
664
723
  });
665
724
  socket.on("disconnect", () => {
666
- console.log("ModelNex SDK: Disconnected from agent server");
725
+ emitSdkDebugLog("[ModelNex SDK] Disconnected from agent server", void 0, { devMode });
667
726
  onSocketId?.(null);
668
727
  });
669
728
  socket.on("agent:request_context", () => {
@@ -689,12 +748,11 @@ function useModelNexSocket({
689
748
  reasoning
690
749
  }) => {
691
750
  const log = (msg, data) => {
692
- console.log(msg, data ?? "");
693
- window.dispatchEvent(new CustomEvent("modelnex-debug", { detail: { msg, data } }));
751
+ emitSdkDebugLog(msg, data, { devMode, dispatchEvent: true });
694
752
  };
695
753
  if (reasoning || params?.fingerprint) {
696
754
  window.dispatchEvent(new CustomEvent("modelnex-action-start", {
697
- detail: { actionId, fingerprint: params?.fingerprint ?? null, reasoning: reasoning ?? "" }
755
+ detail: { actionId, fingerprint: params?.fingerprint ?? null }
698
756
  }));
699
757
  }
700
758
  const sendResult = (success, result, error) => {
@@ -705,17 +763,18 @@ function useModelNexSocket({
705
763
  const currentActions = actionsRef.current;
706
764
  log("[SDK] agent:execute received", {
707
765
  actionId,
708
- params,
709
- registeredActions: Array.from(currentActions.keys())
766
+ executionId: executionId ?? null,
767
+ fieldId: fieldId ?? null,
768
+ hasParams: params != null
710
769
  });
711
770
  const NAV_ACTION_IDS = ["navigate_to_documents", "navigate_to_templates", "navigate_to_inbox", "navigate_to_settings", "navigate_to_template", "navigate_to_document", "navigate_to_folder", "navigate_editor_step"];
712
771
  const action = currentActions.get(actionId);
713
772
  if (action) {
714
773
  try {
715
774
  const validatedParams = action.schema.parse(params);
716
- log("[SDK] Executing", { actionId, validatedParams });
775
+ log("[SDK] Executing action", { actionId });
717
776
  const execResult = await action.execute(validatedParams);
718
- log("[SDK] Execute completed", { actionId });
777
+ log("[SDK] Action completed", { actionId });
719
778
  sendResult(true, execResult);
720
779
  if (NAV_ACTION_IDS.includes(actionId)) {
721
780
  requestAnimationFrame(() => {
@@ -739,25 +798,29 @@ function useModelNexSocket({
739
798
  }
740
799
  } catch (err) {
741
800
  const errMsg = err instanceof Error ? err.message : String(err);
742
- console.error(`[ModelNex SDK] Execution failed for ${actionId}`, err);
801
+ console.error(`[ModelNex SDK] Execution failed for ${actionId}: ${errMsg}`);
802
+ if (isSdkDebugEnabled(devMode)) {
803
+ window.dispatchEvent(
804
+ new CustomEvent("modelnex-debug", {
805
+ detail: { msg: "[SDK] Execution failed", data: { actionId, error: errMsg } }
806
+ })
807
+ );
808
+ }
809
+ sendResult(false, void 0, errMsg);
810
+ }
811
+ } else {
812
+ const errMsg = `Action not found: ${actionId}`;
813
+ console.warn(`[ModelNex SDK] ${errMsg}`);
814
+ if (isSdkDebugEnabled(devMode)) {
743
815
  window.dispatchEvent(
744
816
  new CustomEvent("modelnex-debug", {
745
- detail: { msg: "[SDK] Execution failed", data: { actionId, error: errMsg } }
817
+ detail: {
818
+ msg: "[SDK] Action not found",
819
+ data: { actionId }
820
+ }
746
821
  })
747
822
  );
748
- sendResult(false, void 0, errMsg);
749
823
  }
750
- } else {
751
- const errMsg = `Action not found: ${actionId}`;
752
- console.warn("[ModelNex SDK]", errMsg, "Available:", Array.from(currentActions.keys()));
753
- window.dispatchEvent(
754
- new CustomEvent("modelnex-debug", {
755
- detail: {
756
- msg: "[SDK] Action not found",
757
- data: { actionId, available: Array.from(actions.keys()) }
758
- }
759
- })
760
- );
761
824
  sendResult(false, void 0, errMsg);
762
825
  }
763
826
  }
@@ -1617,8 +1680,153 @@ async function captureScreenshot(selector) {
1617
1680
  return canvas.toDataURL("image/png");
1618
1681
  }
1619
1682
 
1683
+ // src/utils/experience-tool-bridge.ts
1684
+ var activeBridge = null;
1685
+ function registerExperienceToolBridge(bridge) {
1686
+ activeBridge = bridge;
1687
+ return () => {
1688
+ if (activeBridge === bridge) {
1689
+ activeBridge = null;
1690
+ }
1691
+ };
1692
+ }
1693
+ function getExperienceToolBridge() {
1694
+ return activeBridge;
1695
+ }
1696
+
1697
+ // src/utils/tourDiscovery.ts
1698
+ async function fetchTours(serverUrl, toursApiBase, websiteId, userType, userId, experienceType = "tour") {
1699
+ try {
1700
+ const params = new URLSearchParams({
1701
+ websiteId,
1702
+ userType
1703
+ });
1704
+ if (userId) params.set("userId", userId);
1705
+ if (experienceType !== "tour") params.set("type", experienceType);
1706
+ const res = await fetch(getTourApiUrl(serverUrl, toursApiBase, `/tours?${params.toString()}`));
1707
+ if (!res.ok) return [];
1708
+ const data = await res.json();
1709
+ return data.tours ?? [];
1710
+ } catch {
1711
+ return [];
1712
+ }
1713
+ }
1714
+ async function fetchTourById(serverUrl, toursApiBase, tourId, experienceType = "tour", websiteId) {
1715
+ try {
1716
+ const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}`), websiteId));
1717
+ if (experienceType !== "tour") {
1718
+ url.searchParams.set("type", experienceType);
1719
+ }
1720
+ const res = await fetch(url.toString());
1721
+ if (!res.ok) {
1722
+ return null;
1723
+ }
1724
+ const data = await res.json();
1725
+ return data.tour ?? null;
1726
+ } catch {
1727
+ return null;
1728
+ }
1729
+ }
1730
+ async function markTourComplete(serverUrl, toursApiBase, tourId, userId, experienceType = "tour", websiteId) {
1731
+ try {
1732
+ const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/complete`), websiteId));
1733
+ if (experienceType !== "tour") {
1734
+ url.searchParams.set("type", experienceType);
1735
+ }
1736
+ await fetch(url.toString(), {
1737
+ method: "POST",
1738
+ headers: { "Content-Type": "application/json" },
1739
+ body: JSON.stringify({ userId })
1740
+ });
1741
+ } catch {
1742
+ }
1743
+ }
1744
+ async function markTourDismissed(serverUrl, toursApiBase, tourId, userId, experienceType = "tour", websiteId) {
1745
+ try {
1746
+ const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/dismiss`), websiteId));
1747
+ if (experienceType !== "tour") {
1748
+ url.searchParams.set("type", experienceType);
1749
+ }
1750
+ await fetch(url.toString(), {
1751
+ method: "POST",
1752
+ headers: { "Content-Type": "application/json" },
1753
+ body: JSON.stringify({ userId })
1754
+ });
1755
+ } catch {
1756
+ }
1757
+ }
1758
+ async function recordTourEvent(serverUrl, toursApiBase, tourId, userId, eventType, websiteId) {
1759
+ try {
1760
+ const url = withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/events`), websiteId);
1761
+ await fetch(url, {
1762
+ method: "POST",
1763
+ headers: { "Content-Type": "application/json" },
1764
+ body: JSON.stringify({ userId, eventType })
1765
+ });
1766
+ } catch {
1767
+ }
1768
+ }
1769
+ function getToursBaseUrl(serverUrl, toursApiBase) {
1770
+ if (toursApiBase?.startsWith("/")) {
1771
+ return `${typeof window !== "undefined" ? window.location.origin : ""}${toursApiBase.replace(/\/$/, "")}`;
1772
+ }
1773
+ return serverUrl.replace(/\/$/, "");
1774
+ }
1775
+ function getTourApiUrl(serverUrl, toursApiBase, path) {
1776
+ const base = getToursBaseUrl(serverUrl, toursApiBase);
1777
+ return toursApiBase ? `${base}${path}` : `${base}/api${path}`;
1778
+ }
1779
+ function withWebsiteId(url, websiteId) {
1780
+ if (!websiteId) {
1781
+ return url;
1782
+ }
1783
+ const baseOrigin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
1784
+ const resolved = new URL(url, baseOrigin);
1785
+ resolved.searchParams.set("websiteId", websiteId);
1786
+ return resolved.toString();
1787
+ }
1788
+ function normalizeTrigger(trigger) {
1789
+ if (trigger === "feature_launch" || trigger === "feature_unlocked" || trigger === "feature_unlock" || trigger === "new_feature") {
1790
+ return "feature_launch";
1791
+ }
1792
+ if (trigger === "return_visit") {
1793
+ return "return_visit";
1794
+ }
1795
+ return trigger ?? "first_visit";
1796
+ }
1797
+ function getStartPolicy(tour) {
1798
+ return tour.startPolicy ?? (normalizeTrigger(tour.trigger) === "manual" ? "immediate_start" : "prompt_only");
1799
+ }
1800
+ function getNotificationType(tour) {
1801
+ return tour.notificationType ?? "bubble_card";
1802
+ }
1803
+ function getUserProfileFeatures(userProfile) {
1804
+ const directFeatures = Array.isArray(userProfile.features) ? userProfile.features : [];
1805
+ const nestedFeatures = Array.isArray(userProfile.tourFacts?.features) ? userProfile.tourFacts.features : [];
1806
+ return [...new Set([...directFeatures, ...nestedFeatures].filter((value) => Boolean(value)))];
1807
+ }
1808
+ function isTourEligible(tour, userProfile) {
1809
+ switch (normalizeTrigger(tour.trigger)) {
1810
+ case "first_visit":
1811
+ return !!userProfile.isNewUser;
1812
+ case "return_visit":
1813
+ return userProfile.isNewUser === false;
1814
+ case "feature_launch": {
1815
+ const featureKey = tour.featureKey?.trim();
1816
+ if (!featureKey) return false;
1817
+ return getUserProfileFeatures(userProfile).includes(featureKey);
1818
+ }
1819
+ case "manual":
1820
+ return false;
1821
+ default:
1822
+ return false;
1823
+ }
1824
+ }
1825
+
1620
1826
  // src/hooks/useBuiltinActions.ts
1621
1827
  var resolutionCache = /* @__PURE__ */ new Map();
1828
+ var DEFAULT_WORKFLOW_SEARCH_LIMIT = 5;
1829
+ var WORKFLOW_TYPES = ["onboarding", "tour"];
1622
1830
  function safeQueryAll2(selector) {
1623
1831
  try {
1624
1832
  return Array.from(document.querySelectorAll(selector)).filter(
@@ -1793,6 +2001,137 @@ function lastResortScan(fingerprint, options, elements) {
1793
2001
  function cacheResolution(originalFp, selector, resolvedFp) {
1794
2002
  resolutionCache.set(originalFp, { selector, resolvedFingerprint: resolvedFp });
1795
2003
  }
2004
+ function normalizeWorkflowQuery(value) {
2005
+ return value.trim().toLowerCase();
2006
+ }
2007
+ function getWorkflowSearchText(tour) {
2008
+ const firstSteps = (tour.steps || []).slice(0, 6).flatMap((step) => [step.goal, step.narration, step.ask]).filter(Boolean).join(" ");
2009
+ return [
2010
+ tour.name,
2011
+ tour.type,
2012
+ tour.trigger,
2013
+ tour.startPolicy,
2014
+ tour.featureKey,
2015
+ tour.goal?.primaryAction,
2016
+ tour.goal?.successMetric,
2017
+ ...tour.targetUserTypes,
2018
+ firstSteps
2019
+ ].filter(Boolean).join(" ").toLowerCase();
2020
+ }
2021
+ function scoreWorkflowMatch(tour, query) {
2022
+ const normalizedQuery = normalizeWorkflowQuery(query);
2023
+ if (!normalizedQuery) return 1;
2024
+ const name = tour.name.toLowerCase();
2025
+ const featureKey = (tour.featureKey || "").toLowerCase();
2026
+ const haystack = getWorkflowSearchText(tour);
2027
+ if (name === normalizedQuery) return 140;
2028
+ if (featureKey && featureKey === normalizedQuery) return 125;
2029
+ if (name.includes(normalizedQuery)) return 115;
2030
+ if (featureKey && featureKey.includes(normalizedQuery)) return 100;
2031
+ if (haystack.includes(normalizedQuery)) return 85;
2032
+ const words = normalizedQuery.split(/\s+/).filter((word) => word.length > 1);
2033
+ if (words.length === 0) return 0;
2034
+ const matched = words.filter((word) => haystack.includes(word));
2035
+ if (matched.length === 0) return 0;
2036
+ const ratio = matched.length / words.length;
2037
+ return Math.round(ratio * 70);
2038
+ }
2039
+ function summarizeWorkflowStep(step, index) {
2040
+ const detail = step.goal || step.ask || step.narration || step.rawNarration || "No description";
2041
+ return `${index + 1}. [${step.type}] ${detail}`;
2042
+ }
2043
+ function formatWorkflowSummary(tour, options) {
2044
+ const stepLimit = options?.stepLimit ?? 4;
2045
+ const parts = [
2046
+ `**${tour.name}**`,
2047
+ `ID: ${tour.id}`,
2048
+ `Type: ${tour.type || "tour"}`,
2049
+ `Trigger: ${tour.trigger}`,
2050
+ `Start policy: ${tour.startPolicy || "prompt_only"}`,
2051
+ tour.featureKey ? `Feature key: ${tour.featureKey}` : null,
2052
+ tour.targetUserTypes?.length ? `Target users: ${tour.targetUserTypes.join(", ")}` : null,
2053
+ `Steps: ${tour.steps?.length ?? 0}`,
2054
+ tour.goal?.primaryAction ? `Primary action: ${tour.goal.primaryAction}` : null
2055
+ ].filter(Boolean);
2056
+ if (options?.includeSteps) {
2057
+ const stepPreview = (tour.steps || []).slice(0, stepLimit).map((step, index) => summarizeWorkflowStep(step, index)).join("\n");
2058
+ if (stepPreview) {
2059
+ parts.push(`Steps preview:
2060
+ ${stepPreview}`);
2061
+ }
2062
+ }
2063
+ return parts.join("\n");
2064
+ }
2065
+ function getRequestedWorkflowTypes(experienceType) {
2066
+ if (!experienceType || experienceType === "all") {
2067
+ return WORKFLOW_TYPES;
2068
+ }
2069
+ return [experienceType];
2070
+ }
2071
+ async function fetchAvailableWorkflows(getters, experienceType) {
2072
+ const serverUrl = getters.serverUrl();
2073
+ const websiteId = getters.websiteId();
2074
+ if (!serverUrl) {
2075
+ throw new Error("Server URL is not configured.");
2076
+ }
2077
+ if (!websiteId) {
2078
+ throw new Error("websiteId is required to search workflows.");
2079
+ }
2080
+ const userProfile = getters.userProfile();
2081
+ const userType = userProfile?.type || "";
2082
+ const userId = userProfile?.userId;
2083
+ const toursApiBase = getters.toursApiBase();
2084
+ const requestedTypes = getRequestedWorkflowTypes(experienceType);
2085
+ const lists = await Promise.all(
2086
+ requestedTypes.map(
2087
+ (type) => fetchTours(serverUrl, toursApiBase, websiteId, userType, userId, type)
2088
+ )
2089
+ );
2090
+ return lists.flatMap(
2091
+ (list, index) => list.map((tour) => ({
2092
+ ...tour,
2093
+ type: tour.type || requestedTypes[index]
2094
+ }))
2095
+ );
2096
+ }
2097
+ async function resolveWorkflowFromInput(getters, params) {
2098
+ const serverUrl = getters.serverUrl();
2099
+ const websiteId = getters.websiteId();
2100
+ const toursApiBase = getters.toursApiBase();
2101
+ if (!serverUrl) {
2102
+ throw new Error("Server URL is not configured.");
2103
+ }
2104
+ if (params.workflowId) {
2105
+ const requestedTypes = getRequestedWorkflowTypes(params.experienceType);
2106
+ for (const type of requestedTypes) {
2107
+ const workflow = await fetchTourById(serverUrl, toursApiBase, params.workflowId, type, websiteId);
2108
+ if (workflow) {
2109
+ return { workflow: { ...workflow, type: workflow.type || type } };
2110
+ }
2111
+ }
2112
+ }
2113
+ const query = params.name?.trim();
2114
+ if (!query) {
2115
+ return { workflow: null, reason: "Provide either workflowId or name." };
2116
+ }
2117
+ const workflows = await fetchAvailableWorkflows(getters, params.experienceType);
2118
+ const ranked = workflows.map((workflow) => ({ workflow, score: scoreWorkflowMatch(workflow, query) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score);
2119
+ if (ranked.length === 0) {
2120
+ return {
2121
+ workflow: null,
2122
+ reason: `No workflow matched "${query}". Try search_workflows first for available options.`
2123
+ };
2124
+ }
2125
+ if (ranked.length > 1 && ranked[0].score === ranked[1].score) {
2126
+ return {
2127
+ workflow: null,
2128
+ reason: `Multiple workflows matched "${query}". Try view_workflow with an exact workflowId.
2129
+
2130
+ ` + ranked.slice(0, 3).map(({ workflow }) => formatWorkflowSummary(workflow)).join("\n\n---\n\n")
2131
+ };
2132
+ }
2133
+ return { workflow: ranked[0].workflow };
2134
+ }
1796
2135
  var screenshotSchema = z2.object({
1797
2136
  selector: z2.string().optional().describe("Optional CSS selector to capture a specific element. Omit to capture the full viewport.")
1798
2137
  });
@@ -2073,15 +2412,91 @@ function createSearchTaggedElementsAction(getTagStore) {
2073
2412
  }
2074
2413
  };
2075
2414
  }
2415
+ var workflowExperienceTypeSchema = z2.enum(["onboarding", "tour", "all"]);
2416
+ var searchWorkflowsSchema = z2.object({
2417
+ query: z2.string().optional().describe("Optional workflow name, feature, or task phrase to search for. Omit to list available workflows."),
2418
+ experienceType: workflowExperienceTypeSchema.optional().describe("Which workflow type to search: onboarding, tour, or all. Default: onboarding."),
2419
+ limit: z2.number().optional().describe(`Maximum number of workflows to return (default: ${DEFAULT_WORKFLOW_SEARCH_LIMIT}).`)
2420
+ });
2421
+ function createSearchWorkflowsAction(getters) {
2422
+ return {
2423
+ id: "search_workflows",
2424
+ description: "Search available workflows and tours that can be launched for the current website/user. Use this when the user asks for onboarding, a walkthrough, a tutorial, a guided setup, or a named workflow/tour.",
2425
+ schema: searchWorkflowsSchema,
2426
+ execute: async (params) => {
2427
+ const workflows = await fetchAvailableWorkflows(getters, params.experienceType ?? "onboarding");
2428
+ if (workflows.length === 0) {
2429
+ return "No workflows are currently available for this website/user.";
2430
+ }
2431
+ const query = params.query?.trim();
2432
+ const limit = params.limit ?? DEFAULT_WORKFLOW_SEARCH_LIMIT;
2433
+ const ranked = workflows.map((workflow) => ({
2434
+ workflow,
2435
+ score: query ? scoreWorkflowMatch(workflow, query) : 1
2436
+ })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
2437
+ if (ranked.length === 0) {
2438
+ return `No workflows matched "${query}".`;
2439
+ }
2440
+ return ranked.map(({ workflow }) => formatWorkflowSummary(workflow)).join("\n\n---\n\n");
2441
+ }
2442
+ };
2443
+ }
2444
+ var resolveWorkflowSchema = z2.object({
2445
+ workflowId: z2.string().optional().describe("Exact workflow id when known."),
2446
+ name: z2.string().optional().describe("Workflow name or a close name match when the id is unknown."),
2447
+ experienceType: workflowExperienceTypeSchema.optional().describe("Which workflow type to search: onboarding, tour, or all. Default: onboarding.")
2448
+ });
2449
+ function createViewWorkflowAction(getters) {
2450
+ return {
2451
+ id: "view_workflow",
2452
+ description: "View a specific workflow or tour in more detail, including its id, trigger, start policy, and the first few steps. Use after search_workflows when you need to inspect a candidate before starting it.",
2453
+ schema: resolveWorkflowSchema,
2454
+ execute: async (params) => {
2455
+ const { workflow, reason } = await resolveWorkflowFromInput(getters, params);
2456
+ if (!workflow) {
2457
+ return reason || "Workflow not found.";
2458
+ }
2459
+ return formatWorkflowSummary(workflow, { includeSteps: true, stepLimit: 5 });
2460
+ }
2461
+ };
2462
+ }
2463
+ var startWorkflowSchema = resolveWorkflowSchema.extend({
2464
+ reviewMode: z2.boolean().optional().describe("Optional. Start in review mode instead of normal playback. Default: false.")
2465
+ });
2466
+ function createStartWorkflowAction(getters) {
2467
+ return {
2468
+ id: "start_workflow",
2469
+ description: "Start a specific workflow or tour in the chat experience. Use this after search_workflows or view_workflow when the user wants to begin a named guided workflow.",
2470
+ schema: startWorkflowSchema,
2471
+ execute: async (params) => {
2472
+ const { workflow, reason } = await resolveWorkflowFromInput(getters, params);
2473
+ if (!workflow) {
2474
+ return reason || "Workflow not found.";
2475
+ }
2476
+ const bridge = getExperienceToolBridge();
2477
+ if (!bridge) {
2478
+ return "Workflow launch is unavailable because no chat playback controller is mounted.";
2479
+ }
2480
+ bridge.startExperience(workflow, workflow.type, params.reviewMode ? {
2481
+ reviewMode: true,
2482
+ reviewMetadata: { trigger: "agent_tool" }
2483
+ } : void 0);
2484
+ return `Started workflow "${workflow.name}" (${workflow.id}).`;
2485
+ }
2486
+ };
2487
+ }
2076
2488
  var BUILTIN_ACTION_IDS = {
2077
2489
  screenshot: "take_screenshot",
2078
2490
  click: "click_element",
2079
2491
  fill: "fill_input",
2080
2492
  wait: "request_user_action",
2081
2493
  searchDocs: "search_docs",
2082
- searchTaggedElements: "search_tagged_elements"
2494
+ searchTaggedElements: "search_tagged_elements",
2495
+ searchWorkflows: "search_workflows",
2496
+ viewWorkflow: "view_workflow",
2497
+ startWorkflow: "start_workflow"
2083
2498
  };
2084
- function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId) {
2499
+ function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId, toursApiBase, userProfile) {
2085
2500
  const registeredRef = useRef6(false);
2086
2501
  const tagStoreRef = useRef6(tagStore);
2087
2502
  tagStoreRef.current = tagStore;
@@ -2089,18 +2504,33 @@ function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl
2089
2504
  serverUrlRef.current = serverUrl;
2090
2505
  const websiteIdRef = useRef6(websiteId);
2091
2506
  websiteIdRef.current = websiteId;
2507
+ const toursApiBaseRef = useRef6(toursApiBase);
2508
+ toursApiBaseRef.current = toursApiBase;
2509
+ const userProfileRef = useRef6(userProfile);
2510
+ userProfileRef.current = userProfile;
2092
2511
  useEffect8(() => {
2093
2512
  if (registeredRef.current) return;
2094
2513
  registeredRef.current = true;
2095
2514
  const getTagStore = () => tagStoreRef.current;
2096
2515
  const getServerUrl = () => serverUrlRef.current;
2097
2516
  const getWebsiteId = () => websiteIdRef.current;
2517
+ const getToursApiBase = () => toursApiBaseRef.current;
2518
+ const getUserProfile = () => userProfileRef.current;
2519
+ const workflowToolGetters = {
2520
+ serverUrl: getServerUrl,
2521
+ websiteId: getWebsiteId,
2522
+ toursApiBase: getToursApiBase,
2523
+ userProfile: getUserProfile
2524
+ };
2098
2525
  registerAction(BUILTIN_SCREENSHOT_ACTION);
2099
2526
  registerAction(createClickAction(getTagStore));
2100
2527
  registerAction(createFillAction(getTagStore));
2101
2528
  registerAction(createWaitAction(getTagStore));
2102
2529
  registerAction(createSearchDocsAction(getServerUrl, getWebsiteId));
2103
2530
  registerAction(createSearchTaggedElementsAction(getTagStore));
2531
+ registerAction(createSearchWorkflowsAction(workflowToolGetters));
2532
+ registerAction(createViewWorkflowAction(workflowToolGetters));
2533
+ registerAction(createStartWorkflowAction(workflowToolGetters));
2104
2534
  return () => {
2105
2535
  unregisterAction(BUILTIN_SCREENSHOT_ACTION.id);
2106
2536
  unregisterAction(BUILTIN_ACTION_IDS.click);
@@ -2108,6 +2538,9 @@ function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl
2108
2538
  unregisterAction(BUILTIN_ACTION_IDS.wait);
2109
2539
  unregisterAction(BUILTIN_ACTION_IDS.searchDocs);
2110
2540
  unregisterAction(BUILTIN_ACTION_IDS.searchTaggedElements);
2541
+ unregisterAction(BUILTIN_ACTION_IDS.searchWorkflows);
2542
+ unregisterAction(BUILTIN_ACTION_IDS.viewWorkflow);
2543
+ unregisterAction(BUILTIN_ACTION_IDS.startWorkflow);
2111
2544
  registeredRef.current = false;
2112
2545
  };
2113
2546
  }, [registerAction, unregisterAction]);
@@ -2116,6 +2549,56 @@ function useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl
2116
2549
  // src/constants.ts
2117
2550
  var DEFAULT_MODELNEX_SERVER_URL = "https://api.modelnex.com";
2118
2551
 
2552
+ // src/utils/dev-mode.ts
2553
+ var DEV_MODE_KEY_GLOBAL_NAMES = ["__MODELNEX_DEV_MODE_KEY__", "MODELNEX_DEV_MODE_KEY"];
2554
+ var devModeValidationCache = /* @__PURE__ */ new Map();
2555
+ function normalizeDevModeKey(value) {
2556
+ if (typeof value !== "string") return void 0;
2557
+ const normalized = value.trim();
2558
+ return normalized || void 0;
2559
+ }
2560
+ function resolveInjectedDevModeKey(explicitDevModeKey) {
2561
+ const normalizedExplicitKey = normalizeDevModeKey(explicitDevModeKey);
2562
+ if (normalizedExplicitKey) return normalizedExplicitKey;
2563
+ if (typeof window === "undefined") return void 0;
2564
+ const browserWindow = window;
2565
+ for (const globalName of DEV_MODE_KEY_GLOBAL_NAMES) {
2566
+ const normalizedGlobalKey = normalizeDevModeKey(browserWindow[globalName]);
2567
+ if (normalizedGlobalKey) return normalizedGlobalKey;
2568
+ }
2569
+ const scriptInjectedKey = typeof document !== "undefined" ? normalizeDevModeKey(
2570
+ document.querySelector("script[data-modelnex-dev-mode-key]")?.dataset.modelnexDevModeKey
2571
+ ) : void 0;
2572
+ if (scriptInjectedKey) return scriptInjectedKey;
2573
+ return void 0;
2574
+ }
2575
+ async function validateInjectedDevModeKey(serverUrl, websiteId, devModeKey) {
2576
+ const normalizedWebsiteId = websiteId.trim();
2577
+ const normalizedDevModeKey = devModeKey.trim();
2578
+ if (!normalizedWebsiteId || !normalizedDevModeKey) return false;
2579
+ const cacheKey = `${serverUrl}::${normalizedWebsiteId}::${normalizedDevModeKey}`;
2580
+ const cached = devModeValidationCache.get(cacheKey);
2581
+ if (cached) {
2582
+ return cached;
2583
+ }
2584
+ const validationPromise = fetch(
2585
+ `${serverUrl.replace(/\/$/, "")}/api/websites/${encodeURIComponent(normalizedWebsiteId)}/dev-mode/verify`,
2586
+ {
2587
+ method: "POST",
2588
+ headers: {
2589
+ "Content-Type": "application/json"
2590
+ },
2591
+ body: JSON.stringify({ key: normalizedDevModeKey })
2592
+ }
2593
+ ).then(async (response) => {
2594
+ if (!response.ok) return false;
2595
+ const payload = await response.json().catch(() => null);
2596
+ return payload?.enabled === true;
2597
+ }).catch(() => false);
2598
+ devModeValidationCache.set(cacheKey, validationPromise);
2599
+ return validationPromise;
2600
+ }
2601
+
2119
2602
  // src/hooks/useRunCommand.ts
2120
2603
  import { useCallback as useCallback5, useContext as useContext2 } from "react";
2121
2604
  function searchTaggedElementsForQuery(store, query, limit = 8) {
@@ -2158,7 +2641,10 @@ function useRunCommand(serverUrlOverride) {
2158
2641
  if (!res.ok) {
2159
2642
  throw new Error(data?.error ?? `Request failed: ${res.status}`);
2160
2643
  }
2161
- return data;
2644
+ return {
2645
+ ...data,
2646
+ debug: sanitizeAgentDebug(data?.debug)
2647
+ };
2162
2648
  },
2163
2649
  [baseUrl, tagStore]
2164
2650
  );
@@ -2362,135 +2848,6 @@ function readPreviewSessionSuppression() {
2362
2848
  }
2363
2849
  }
2364
2850
 
2365
- // src/utils/tourDiscovery.ts
2366
- async function fetchTours(serverUrl, toursApiBase, websiteId, userType, userId, experienceType = "tour") {
2367
- try {
2368
- const params = new URLSearchParams({
2369
- websiteId,
2370
- userType
2371
- });
2372
- if (userId) params.set("userId", userId);
2373
- if (experienceType !== "tour") params.set("type", experienceType);
2374
- const res = await fetch(getTourApiUrl(serverUrl, toursApiBase, `/tours?${params.toString()}`));
2375
- if (!res.ok) return [];
2376
- const data = await res.json();
2377
- return data.tours ?? [];
2378
- } catch {
2379
- return [];
2380
- }
2381
- }
2382
- async function fetchTourById(serverUrl, toursApiBase, tourId, experienceType = "tour", websiteId) {
2383
- try {
2384
- const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}`), websiteId));
2385
- if (experienceType !== "tour") {
2386
- url.searchParams.set("type", experienceType);
2387
- }
2388
- const res = await fetch(url.toString());
2389
- if (!res.ok) {
2390
- return null;
2391
- }
2392
- const data = await res.json();
2393
- return data.tour ?? null;
2394
- } catch {
2395
- return null;
2396
- }
2397
- }
2398
- async function markTourComplete(serverUrl, toursApiBase, tourId, userId, experienceType = "tour", websiteId) {
2399
- try {
2400
- const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/complete`), websiteId));
2401
- if (experienceType !== "tour") {
2402
- url.searchParams.set("type", experienceType);
2403
- }
2404
- await fetch(url.toString(), {
2405
- method: "POST",
2406
- headers: { "Content-Type": "application/json" },
2407
- body: JSON.stringify({ userId })
2408
- });
2409
- } catch {
2410
- }
2411
- }
2412
- async function markTourDismissed(serverUrl, toursApiBase, tourId, userId, experienceType = "tour", websiteId) {
2413
- try {
2414
- const url = new URL(withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/dismiss`), websiteId));
2415
- if (experienceType !== "tour") {
2416
- url.searchParams.set("type", experienceType);
2417
- }
2418
- await fetch(url.toString(), {
2419
- method: "POST",
2420
- headers: { "Content-Type": "application/json" },
2421
- body: JSON.stringify({ userId })
2422
- });
2423
- } catch {
2424
- }
2425
- }
2426
- async function recordTourEvent(serverUrl, toursApiBase, tourId, userId, eventType, websiteId) {
2427
- try {
2428
- const url = withWebsiteId(getTourApiUrl(serverUrl, toursApiBase, `/tours/${tourId}/events`), websiteId);
2429
- await fetch(url, {
2430
- method: "POST",
2431
- headers: { "Content-Type": "application/json" },
2432
- body: JSON.stringify({ userId, eventType })
2433
- });
2434
- } catch {
2435
- }
2436
- }
2437
- function getToursBaseUrl(serverUrl, toursApiBase) {
2438
- if (toursApiBase?.startsWith("/")) {
2439
- return `${typeof window !== "undefined" ? window.location.origin : ""}${toursApiBase.replace(/\/$/, "")}`;
2440
- }
2441
- return serverUrl.replace(/\/$/, "");
2442
- }
2443
- function getTourApiUrl(serverUrl, toursApiBase, path) {
2444
- const base = getToursBaseUrl(serverUrl, toursApiBase);
2445
- return toursApiBase ? `${base}${path}` : `${base}/api${path}`;
2446
- }
2447
- function withWebsiteId(url, websiteId) {
2448
- if (!websiteId) {
2449
- return url;
2450
- }
2451
- const baseOrigin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
2452
- const resolved = new URL(url, baseOrigin);
2453
- resolved.searchParams.set("websiteId", websiteId);
2454
- return resolved.toString();
2455
- }
2456
- function normalizeTrigger(trigger) {
2457
- if (trigger === "feature_launch" || trigger === "feature_unlocked" || trigger === "feature_unlock" || trigger === "new_feature") {
2458
- return "feature_launch";
2459
- }
2460
- if (trigger === "return_visit") {
2461
- return "return_visit";
2462
- }
2463
- return trigger ?? "first_visit";
2464
- }
2465
- function getStartPolicy(tour) {
2466
- return tour.startPolicy ?? (normalizeTrigger(tour.trigger) === "manual" ? "immediate_start" : "prompt_only");
2467
- }
2468
- function getNotificationType(tour) {
2469
- return tour.notificationType ?? "bubble_card";
2470
- }
2471
- function getUserProfileFeatures(userProfile) {
2472
- const directFeatures = Array.isArray(userProfile.features) ? userProfile.features : [];
2473
- const nestedFeatures = Array.isArray(userProfile.tourFacts?.features) ? userProfile.tourFacts.features : [];
2474
- return [...new Set([...directFeatures, ...nestedFeatures].filter((value) => Boolean(value)))];
2475
- }
2476
- function isTourEligible(tour, userProfile) {
2477
- switch (normalizeTrigger(tour.trigger)) {
2478
- case "first_visit":
2479
- return !!userProfile.isNewUser;
2480
- case "return_visit":
2481
- return userProfile.isNewUser === false;
2482
- case "feature_launch": {
2483
- const featureKey = tour.featureKey?.trim();
2484
- if (!featureKey) return false;
2485
- return getUserProfileFeatures(userProfile).includes(featureKey);
2486
- }
2487
- case "manual":
2488
- return false;
2489
- default:
2490
- return false;
2491
- }
2492
- }
2493
-
2494
2851
  // src/hooks/useTourPlayback.ts
2495
2852
  import { useState as useState7, useRef as useRef8, useCallback as useCallback7, useEffect as useEffect11, useContext as useContext4 } from "react";
2496
2853
 
@@ -3195,7 +3552,9 @@ function useTourPlayback({
3195
3552
  const socket = tourSocketPool.acquire(serverUrl);
3196
3553
  socketRef.current = socket;
3197
3554
  const handleConnect = () => {
3198
- console.log("[TourClient] Connected to tour agent server:", socket.id);
3555
+ emitSdkDebugLog("[TourClient] Connected to tour agent server", {
3556
+ socketId: socket.id ?? null
3557
+ }, { devMode: devModeRef.current });
3199
3558
  const profile = userProfileRef.current;
3200
3559
  const currentWebsiteId = websiteIdRef.current;
3201
3560
  if (currentWebsiteId && profile?.userId) {
@@ -3212,7 +3571,9 @@ function useTourPlayback({
3212
3571
  setServerState(payload);
3213
3572
  };
3214
3573
  const handleCommandCancel = (payload) => {
3215
- console.log("[TourClient] Received command_cancel:", payload);
3574
+ emitSdkDebugLog("[TourClient] Received command cancel", {
3575
+ commandBatchId: payload.commandBatchId ?? null
3576
+ }, { devMode: devModeRef.current });
3216
3577
  if (payload.commandBatchId && activeCommandBatchIdRef.current === payload.commandBatchId) {
3217
3578
  activeCommandBatchIdRef.current = null;
3218
3579
  activeExecutionTokenRef.current++;
@@ -3227,14 +3588,22 @@ function useTourPlayback({
3227
3588
  const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, experienceTypeRef.current);
3228
3589
  if (!shouldExecuteTourCommandBatch(isActiveRef.current) || !hasTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
3229
3590
  const activeType = experienceTypeRef.current;
3230
- console.log("[TourClient] Ignoring command batch for inactive playback hook:", activeType, payload.stepIndex);
3591
+ emitSdkDebugLog("[TourClient] Ignoring command batch for inactive playback hook", {
3592
+ experienceType: activeType,
3593
+ stepIndex: payload.stepIndex
3594
+ }, { devMode: devModeRef.current });
3231
3595
  return;
3232
3596
  }
3233
3597
  const emitIfOpen = (ev, data) => {
3234
3598
  if (socketRef.current !== socket) return;
3235
3599
  emitSocketEvent(socket, ev, data);
3236
3600
  };
3237
- console.log("[TourClient] Received command batch:", payload.stepIndex, payload.commands);
3601
+ const commandBatchId = payload.commandBatchId ?? null;
3602
+ emitSdkDebugLog("[TourClient] Received command batch", {
3603
+ stepIndex: payload.stepIndex,
3604
+ commandCount: Array.isArray(payload.commands) ? payload.commands.length : 0,
3605
+ commandBatchId
3606
+ }, { devMode: devModeRef.current });
3238
3607
  runCleanup(pendingManualWaitCleanupRef.current);
3239
3608
  pendingManualWaitCleanupRef.current = null;
3240
3609
  if (voiceInputResolveRef.current) {
@@ -3244,7 +3613,6 @@ function useTourPlayback({
3244
3613
  }
3245
3614
  setPlaybackState("executing");
3246
3615
  commandInFlightRef.current = true;
3247
- const commandBatchId = payload.commandBatchId ?? null;
3248
3616
  turnIdRef.current = payload.turnId ?? turnIdRef.current;
3249
3617
  const clearCommandBatchId = () => {
3250
3618
  if (activeCommandBatchIdRef.current === commandBatchId) {
@@ -3290,7 +3658,7 @@ function useTourPlayback({
3290
3658
  }
3291
3659
  }
3292
3660
  if (!payload.commands || !Array.isArray(payload.commands)) {
3293
- console.warn("[TourClient] Payload commands is not an array:", payload);
3661
+ console.warn("[TourClient] Command batch payload was invalid.");
3294
3662
  commandInFlightRef.current = false;
3295
3663
  emitIfOpen("tour:action_result", {
3296
3664
  success: false,
@@ -3339,7 +3707,9 @@ function useTourPlayback({
3339
3707
  };
3340
3708
  const executeOne = async (action) => {
3341
3709
  assertNotInterrupted();
3342
- console.log("[TourClient] Executing action:", action?.type, action?.params ? JSON.stringify(action.params).slice(0, 120) : "");
3710
+ emitSdkDebugLog("[TourClient] Executing action", {
3711
+ actionType: action?.type ?? "unknown"
3712
+ }, { devMode: devModeRef.current });
3343
3713
  const currentStep = tourRef.current?.steps?.[stepIndexRef.current] ?? null;
3344
3714
  const executeTimeline = async (params = {}) => {
3345
3715
  const segments = Array.isArray(params.segments) ? params.segments : [];
@@ -3443,7 +3813,7 @@ function useTourPlayback({
3443
3813
  }
3444
3814
  removeHighlight();
3445
3815
  await performInteractiveClick(targetEl);
3446
- const { waitForDomSettle: waitForDomSettleClick } = await import("./dom-sync-L5KIP45X.mjs");
3816
+ const { waitForDomSettle: waitForDomSettleClick } = await import("./dom-sync-GABDEODR.mjs");
3447
3817
  await waitForDomSettleClick({ timeoutMs: 3e3, debounceMs: 300 });
3448
3818
  return { result: "clicked" };
3449
3819
  }
@@ -3480,7 +3850,7 @@ function useTourPlayback({
3480
3850
  throw new Error("navigate_to_url missing url");
3481
3851
  }
3482
3852
  await navigateToTourUrl(nextUrl);
3483
- const { waitForDomSettle: waitForDomSettleNav } = await import("./dom-sync-L5KIP45X.mjs");
3853
+ const { waitForDomSettle: waitForDomSettleNav } = await import("./dom-sync-GABDEODR.mjs");
3484
3854
  await waitForDomSettleNav({ timeoutMs: 3e3, debounceMs: 300 });
3485
3855
  return { result: nextUrl };
3486
3856
  }
@@ -3504,7 +3874,7 @@ function useTourPlayback({
3504
3874
  if (action.params?.wait !== false) {
3505
3875
  await res.json();
3506
3876
  }
3507
- const { waitForDomSettle } = await import("./dom-sync-L5KIP45X.mjs");
3877
+ const { waitForDomSettle } = await import("./dom-sync-GABDEODR.mjs");
3508
3878
  await waitForDomSettle({ timeoutMs: 3e3, debounceMs: 300 });
3509
3879
  await syncAOM();
3510
3880
  return { result: action.params?.command ?? "executed" };
@@ -3526,7 +3896,7 @@ function useTourPlayback({
3526
3896
  handleTourEnd();
3527
3897
  return { result: "ended" };
3528
3898
  }
3529
- console.warn("[TourClient] Unknown action type:", action?.type, "- skipping");
3899
+ console.warn(`[TourClient] Unknown action type: ${String(action?.type ?? "unknown")}. Skipping.`);
3530
3900
  return { result: "unknown_action_skipped" };
3531
3901
  };
3532
3902
  try {
@@ -3582,14 +3952,15 @@ function useTourPlayback({
3582
3952
  clearCommandBatchId();
3583
3953
  return;
3584
3954
  }
3585
- console.error("[TourClient] Command batch execution failed:", err);
3955
+ const errMsg = err instanceof Error ? err.message : String(err);
3956
+ console.error(`[TourClient] Command batch execution failed: ${errMsg}`);
3586
3957
  if (reviewModeRef.current && activeTourId && activePreviewRunId) {
3587
3958
  void logPreviewEvent(serverUrl, toursApiBaseRef.current, activeTourId, activePreviewRunId, websiteId, {
3588
3959
  stepOrder: stepIndexRef.current,
3589
3960
  eventType: "command_batch_failed",
3590
3961
  payload: {
3591
3962
  commandBatchId,
3592
- error: String(err),
3963
+ error: errMsg,
3593
3964
  partialResults: results
3594
3965
  },
3595
3966
  status: "active",
@@ -3599,7 +3970,7 @@ function useTourPlayback({
3599
3970
  emitIfOpen("tour:action_result", {
3600
3971
  success: false,
3601
3972
  reason: "execution_error",
3602
- error: String(err),
3973
+ error: errMsg,
3603
3974
  results,
3604
3975
  commandBatchId,
3605
3976
  runId: runIdRef.current,
@@ -3625,7 +3996,10 @@ function useTourPlayback({
3625
3996
  let manualWaitTarget = await resolveTargetElement2(waitTargetHints, currentStep);
3626
3997
  if (inputLikeWait && preferredWaitTarget && manualWaitTarget && manualWaitTarget !== preferredWaitTarget && !isEditableWaitTarget(manualWaitTarget) && isEditableWaitTarget(preferredWaitTarget)) {
3627
3998
  manualWaitTarget = preferredWaitTarget;
3628
- console.log("[TourClient] wait_for_input: preferring current editable target over hinted step target", manualWaitTarget);
3999
+ emitSdkDebugLog("[TourClient] wait_for_input preferred highlighted editable target", {
4000
+ stepIndex: stepIndexRef.current,
4001
+ event: waitEvent
4002
+ }, { devMode: devModeRef.current });
3629
4003
  }
3630
4004
  if (manualWaitTarget) {
3631
4005
  const manualWait = createManualWaitForTarget(manualWaitTarget, waitEvent, currentStep);
@@ -3639,7 +4013,10 @@ function useTourPlayback({
3639
4013
  manualWaitPromise = manualWait.promise;
3640
4014
  manualWaitKind = manualWait.kind;
3641
4015
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
3642
- console.log("[TourClient] wait_for_input: using current editable target as fallback wait target", preferredWaitTarget);
4016
+ emitSdkDebugLog("[TourClient] wait_for_input using fallback editable target", {
4017
+ stepIndex: stepIndexRef.current,
4018
+ event: waitEvent
4019
+ }, { devMode: devModeRef.current });
3643
4020
  }
3644
4021
  if (!manualWaitPromise && inputLikeWait) {
3645
4022
  const firstInput = document.querySelector(
@@ -3650,7 +4027,10 @@ function useTourPlayback({
3650
4027
  manualWaitPromise = manualWait.promise;
3651
4028
  manualWaitKind = manualWait.kind;
3652
4029
  pendingManualWaitCleanupRef.current = manualWait.cleanup;
3653
- console.log("[TourClient] wait_for_input: no target found, falling back to first visible editable element", firstInput);
4030
+ emitSdkDebugLog("[TourClient] wait_for_input falling back to first editable element", {
4031
+ stepIndex: stepIndexRef.current,
4032
+ event: waitEvent
4033
+ }, { devMode: devModeRef.current });
3654
4034
  }
3655
4035
  }
3656
4036
  setPlaybackState(manualWaitKind ? "waiting_input" : "waiting_voice");
@@ -3695,7 +4075,7 @@ function useTourPlayback({
3695
4075
  if (!transcript) {
3696
4076
  return;
3697
4077
  }
3698
- const { waitForDomSettle } = await import("./dom-sync-L5KIP45X.mjs");
4078
+ const { waitForDomSettle } = await import("./dom-sync-GABDEODR.mjs");
3699
4079
  await waitForDomSettle({ timeoutMs: 1500, debounceMs: 200 });
3700
4080
  await syncAOM();
3701
4081
  emitIfOpen("tour:user_input", {
@@ -3732,12 +4112,17 @@ function useTourPlayback({
3732
4112
  const tour = tourData.tourContext ?? tourRef.current;
3733
4113
  const expType = experienceTypeRef.current;
3734
4114
  if (tour?.type && tour.type !== expType) {
3735
- console.log(`[TourClient] Ignoring ${tour.type} start (this hook is for ${expType})`);
4115
+ emitSdkDebugLog("[TourClient] Ignoring tour start for mismatched experience type", {
4116
+ incomingType: tour.type,
4117
+ hookType: expType
4118
+ }, { devMode: devModeRef.current });
3736
4119
  return;
3737
4120
  }
3738
4121
  const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, expType);
3739
4122
  if (!claimTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
3740
- console.log(`[TourClient] Ignoring ${expType} start because another hook already owns playback`);
4123
+ emitSdkDebugLog("[TourClient] Ignoring tour start because playback ownership is already claimed", {
4124
+ experienceType: expType
4125
+ }, { devMode: devModeRef.current });
3741
4126
  return;
3742
4127
  }
3743
4128
  claimedPlaybackOwnerKeyRef.current = ownerKey;
@@ -3762,6 +4147,8 @@ function useTourPlayback({
3762
4147
  },
3763
4148
  currentStepOrder: 0
3764
4149
  });
4150
+ } else if (tour?.id && userProfile?.userId) {
4151
+ void recordTourEvent(serverUrl, toursApiBaseRef.current, tour.id, userProfile.userId, "started", websiteId);
3765
4152
  }
3766
4153
  try {
3767
4154
  const { generateMinifiedAOM: generateMinifiedAOM2 } = await import("./aom-HDYNCIOY.mjs");
@@ -3774,7 +4161,8 @@ function useTourPlayback({
3774
4161
  });
3775
4162
  }
3776
4163
  } catch (e) {
3777
- console.warn("[TourClient] Initial DOM sync failed:", e);
4164
+ const errMsg = e instanceof Error ? e.message : String(e);
4165
+ console.warn(`[TourClient] Initial DOM sync failed: ${errMsg}`);
3778
4166
  }
3779
4167
  };
3780
4168
  const handleTourUpdate = (payload) => {
@@ -3812,12 +4200,11 @@ function useTourPlayback({
3812
4200
  return;
3813
4201
  }
3814
4202
  if (isDev) {
3815
- console.log(`%c[ModelNex Tour] ${entry.type}`, "color: #3b82f6; font-weight: bold", entry);
3816
- if (typeof window !== "undefined") {
3817
- window.dispatchEvent(new CustomEvent("modelnex-debug", {
3818
- detail: { msg: `[Tour Timeline] ${entry.type}`, data: entry }
3819
- }));
3820
- }
4203
+ emitSdkDebugLog(`[Tour Timeline] ${entry.type}`, {
4204
+ stepIndex: entry?.data?.stepIndex ?? null,
4205
+ runId: entry?.data?.runId ?? null,
4206
+ turnId: entry?.data?.turnId ?? null
4207
+ }, { devMode: devModeRef.current, dispatchEvent: true });
3821
4208
  }
3822
4209
  };
3823
4210
  socket.on("connect", handleConnect);
@@ -3828,7 +4215,7 @@ function useTourPlayback({
3828
4215
  socket.on("tour:update", handleTourUpdate);
3829
4216
  socket.on("tour:end", handleTourEndEvent);
3830
4217
  socket.on("tour:debug_log", handleDebugLog);
3831
- console.log("[ModelNex SDK] Tour playback initialized. Debug logs enabled:", devModeRef.current || process.env.NODE_ENV === "development");
4218
+ emitSdkDebugLog("[ModelNex SDK] Tour playback initialized", void 0, { devMode: devModeRef.current });
3832
4219
  return () => {
3833
4220
  socket.off("connect", handleConnect);
3834
4221
  socket.off("tour:server_state", handleServerState);
@@ -3939,6 +4326,8 @@ function useTourPlayback({
3939
4326
  status: "stopped",
3940
4327
  currentStepOrder: stepIndexRef.current
3941
4328
  });
4329
+ } else if (tourRef.current?.id && userProfile?.userId) {
4330
+ void recordTourEvent(serverUrl, toursApiBaseRef.current, tourRef.current.id, userProfile.userId, "cancelled", websiteId);
3942
4331
  }
3943
4332
  if (reviewModeRef.current) {
3944
4333
  if (tourRef.current?.id) {
@@ -4011,7 +4400,9 @@ function useTourPlayback({
4011
4400
  isPlaybackActive: isActiveRef.current,
4012
4401
  startRequested: startRequestedRef.current
4013
4402
  })) {
4014
- console.log("[TourClient] Ignoring duplicate start request while playback is already active or starting:", tour.id);
4403
+ emitSdkDebugLog("[TourClient] Ignoring duplicate start request while playback is already active or starting", {
4404
+ tourId: tour.id
4405
+ }, { devMode: devModeRef.current });
4015
4406
  return;
4016
4407
  }
4017
4408
  setPendingTour(null);
@@ -4027,7 +4418,9 @@ function useTourPlayback({
4027
4418
  }
4028
4419
  const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, tour.type ?? experienceTypeRef.current);
4029
4420
  if (!claimTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
4030
- console.log("[TourClient] Ignoring duplicate start request because another hook already owns this experience:", tour.id);
4421
+ emitSdkDebugLog("[TourClient] Ignoring duplicate start request because another hook already owns this experience", {
4422
+ tourId: tour.id
4423
+ }, { devMode: devModeRef.current });
4031
4424
  return;
4032
4425
  }
4033
4426
  claimedPlaybackOwnerKeyRef.current = ownerKey;
@@ -4050,7 +4443,8 @@ function useTourPlayback({
4050
4443
  setPreviewRunId(previewRun.id);
4051
4444
  previewRunIdRef.current = previewRun.id;
4052
4445
  } catch (err) {
4053
- console.warn("[TourClient] Failed to create preview run:", err);
4446
+ const errMsg = err instanceof Error ? err.message : String(err);
4447
+ console.warn(`[TourClient] Failed to create preview run: ${errMsg}`);
4054
4448
  setReviewStatusMessage("Preview review logging is unavailable.");
4055
4449
  setPreviewRunId(null);
4056
4450
  previewRunIdRef.current = null;
@@ -4099,7 +4493,7 @@ function useTourPlayback({
4099
4493
  if (!tour) {
4100
4494
  clearActiveDraftPreview(experienceType);
4101
4495
  persistSuppressedDraftPreview(draftPreview);
4102
- console.warn("[ModelNex] Tour fetch failed:", tourId, experienceType, "(Check ModelNex server is running and CORS allows this origin)");
4496
+ console.warn(`[ModelNex] Tour fetch failed for ${experienceType}:${tourId}. Check the ModelNex server and CORS configuration.`);
4103
4497
  return;
4104
4498
  }
4105
4499
  if (cancelled) {
@@ -4122,7 +4516,8 @@ function useTourPlayback({
4122
4516
  await runTour(tour, previewOptions);
4123
4517
  }
4124
4518
  } catch (err) {
4125
- console.warn("[ModelNex] Tour test failed:", err);
4519
+ const errMsg = err instanceof Error ? err.message : String(err);
4520
+ console.warn(`[ModelNex] Tour test failed: ${errMsg}`);
4126
4521
  }
4127
4522
  })();
4128
4523
  return () => {
@@ -4232,7 +4627,8 @@ function useTourPlayback({
4232
4627
  setPlaybackState("executing");
4233
4628
  }
4234
4629
  } catch (err) {
4235
- console.warn("[TourClient] Failed to submit review feedback:", err);
4630
+ const errMsg = err instanceof Error ? err.message : String(err);
4631
+ console.warn(`[TourClient] Failed to submit review feedback: ${errMsg}`);
4236
4632
  setReviewStatusMessage("Unable to save correction.");
4237
4633
  } finally {
4238
4634
  setReviewSubmitting(false);
@@ -4273,23 +4669,29 @@ function useTourPlayback({
4273
4669
  }, [voice]);
4274
4670
  const handleVoiceInput = useCallback7((transcript) => {
4275
4671
  const text = transcript.trim();
4276
- console.log("[TourAgent] handleVoiceInput called with text:", text);
4672
+ emitSdkDebugLog("[TourAgent] Voice input received", {
4673
+ textLength: text.length
4674
+ }, { devMode: devModeRef.current });
4277
4675
  if (!text) return;
4278
4676
  if (interruptExecution(text)) {
4279
4677
  return;
4280
4678
  }
4281
4679
  if (voiceInputResolveRef.current) {
4282
- console.log("[TourAgent] Resolving loop waiting_voice with text:", text);
4680
+ emitSdkDebugLog("[TourAgent] Resolving waiting voice input", void 0, { devMode: devModeRef.current });
4283
4681
  voiceInputResolveRef.current(text);
4284
4682
  } else if (isSocketWritable(socketRef.current)) {
4285
- console.log("[TourAgent] Forwarding ambient voice to server:", text);
4683
+ emitSdkDebugLog("[TourAgent] Forwarding ambient voice input to server", {
4684
+ textLength: text.length
4685
+ }, { devMode: devModeRef.current });
4286
4686
  emitSocketEvent(socketRef.current, "tour:user_input", {
4287
4687
  transcript: text,
4288
4688
  runId: runIdRef.current,
4289
4689
  turnId: turnIdRef.current
4290
4690
  });
4291
4691
  } else {
4292
- console.log("[TourAgent] buffering voice input because socket is not ready:", text);
4692
+ emitSdkDebugLog("[TourAgent] Buffering voice input until socket is ready", {
4693
+ textLength: text.length
4694
+ }, { devMode: devModeRef.current });
4293
4695
  pendingInputBufRef.current = text;
4294
4696
  }
4295
4697
  }, [interruptExecution]);
@@ -5444,7 +5846,6 @@ function useVoice(serverUrl) {
5444
5846
  setIsListening(false);
5445
5847
  }, [stopLiveSttTransport]);
5446
5848
  const startListening = useCallback9((onResult, onInterruption, onError, options = {}) => {
5447
- console.log("[Voice] startListening called, options:", options);
5448
5849
  stopListening();
5449
5850
  listeningSessionIdRef.current = createVoiceDebugId("stt");
5450
5851
  listeningStartedAtRef.current = performance.now();
@@ -5658,7 +6059,9 @@ function useVoice(serverUrl) {
5658
6059
  recorder.start(LIVE_STT_TIMESLICE_MS);
5659
6060
  }
5660
6061
  setIsListening(true);
5661
- console.log("[Voice] Live STT pipeline active (Deepgram streaming + WebRTC loopback)");
6062
+ emitVoiceDebug("stt_live_pipeline_active", {
6063
+ listeningSessionId: listeningSessionIdRef.current
6064
+ });
5662
6065
  } catch (err) {
5663
6066
  const normalizedError = normalizeSttError(err);
5664
6067
  console.warn("[Voice] Failed to start live STT recorder:", normalizedError);
@@ -8405,6 +8808,7 @@ function Tooltip({ children, title }) {
8405
8808
  );
8406
8809
  }
8407
8810
  var BUBBLE_EXPANDED_STORAGE_KEY = "modelnex-chat-bubble-expanded";
8811
+ var BUBBLE_DOCKED_STORAGE_KEY = "modelnex-chat-bubble-docked";
8408
8812
  var TOUR_MINIMIZED_STORAGE_KEY = "modelnex-tour-bubble-minimized";
8409
8813
  var BotIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "var(--modelnex-bubble-icon-size, 20px)", height: "var(--modelnex-bubble-icon-size, 20px)", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
8410
8814
  /* @__PURE__ */ jsx4("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" }),
@@ -8418,6 +8822,18 @@ var CloseIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "var(--modelnex-bubb
8418
8822
  /* @__PURE__ */ jsx4("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
8419
8823
  ] });
8420
8824
  var MinimizeIcon = () => /* @__PURE__ */ jsx4("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("path", { d: "M8 18h8" }) });
8825
+ var DockIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
8826
+ /* @__PURE__ */ jsx4("path", { d: "M12 3v10" }),
8827
+ /* @__PURE__ */ jsx4("path", { d: "m8 9 4 4 4-4" }),
8828
+ /* @__PURE__ */ jsx4("path", { d: "M4 17h16" }),
8829
+ /* @__PURE__ */ jsx4("path", { d: "M6 21h12" })
8830
+ ] });
8831
+ var UndockIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
8832
+ /* @__PURE__ */ jsx4("path", { d: "M12 21V11" }),
8833
+ /* @__PURE__ */ jsx4("path", { d: "m8 15 4-4 4 4" }),
8834
+ /* @__PURE__ */ jsx4("path", { d: "M4 7h16" }),
8835
+ /* @__PURE__ */ jsx4("path", { d: "M6 3h12" })
8836
+ ] });
8421
8837
  var TrashIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
8422
8838
  /* @__PURE__ */ jsx4("path", { d: "M3 6h18" }),
8423
8839
  /* @__PURE__ */ jsx4("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
@@ -8466,7 +8882,7 @@ function AgentTraces({ debug, command, defaultExpanded = true }) {
8466
8882
  },
8467
8883
  children: [
8468
8884
  /* @__PURE__ */ jsxs3("span", { children: [
8469
- "Agent traces (",
8885
+ "Execution summary (",
8470
8886
  traces.length,
8471
8887
  " step",
8472
8888
  traces.length !== 1 ? "s" : "",
@@ -8482,21 +8898,9 @@ function AgentTraces({ debug, command, defaultExpanded = true }) {
8482
8898
  /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: command })
8483
8899
  ] }),
8484
8900
  !hasTraceContent ? /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "12px", padding: "8px", background: "#fef3c7", borderRadius: "4px", borderLeft: "3px solid #f59e0b" }, children: [
8485
- /* @__PURE__ */ jsx4("div", { style: { fontWeight: 600, color: "#92400e", marginBottom: "4px" }, children: "No trace data" }),
8486
- /* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#71717a", marginBottom: "6px" }, children: "The server may not have OPENROUTER_API_KEY set, or the request failed before the agent ran." }),
8487
- /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#fff", borderRadius: "4px", fontSize: "10px", overflow: "auto", maxHeight: "120px", color: "#27272a" }, children: JSON.stringify(debug, null, 2) })
8901
+ /* @__PURE__ */ jsx4("div", { style: { fontWeight: 600, color: "#92400e", marginBottom: "4px" }, children: "No safe debug summary" }),
8902
+ /* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#71717a" }, children: "Verbose prompts, reasoning, and raw tool payloads are intentionally hidden by the SDK." })
8488
8903
  ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
8489
- debug.llmInput && traces.length === 0 && /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "12px" }, children: [
8490
- /* @__PURE__ */ jsx4("div", { style: { fontWeight: 600, color: "#3f3f46", marginBottom: "4px" }, children: "Input \u2192 agent" }),
8491
- /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
8492
- /* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#a1a1aa", marginBottom: "2px" }, children: "System" }),
8493
- /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "80px", overflow: "auto" }, children: debug.llmInput.systemPrompt ?? "(empty)" })
8494
- ] }),
8495
- /* @__PURE__ */ jsxs3("div", { children: [
8496
- /* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#a1a1aa", marginBottom: "2px" }, children: "User" }),
8497
- /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "80px", overflow: "auto" }, children: debug.llmInput.userMessage ?? "(empty)" })
8498
- ] })
8499
- ] }),
8500
8904
  traces.map((t) => {
8501
8905
  const isStepExpanded = expandedSteps.has(t.step);
8502
8906
  return /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "12px", paddingBottom: "12px", borderBottom: "1px solid #e4e4e7" }, children: [
@@ -8529,31 +8933,6 @@ function AgentTraces({ debug, command, defaultExpanded = true }) {
8529
8933
  }
8530
8934
  ),
8531
8935
  isStepExpanded && /* @__PURE__ */ jsxs3(Fragment2, { children: [
8532
- t.reasoning && /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
8533
- /* @__PURE__ */ jsx4("div", { style: { color: "#71717a", marginBottom: "2px" }, children: "Reasoning" }),
8534
- /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#fef3c7", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "100px", overflow: "auto", borderLeft: "3px solid #f59e0b", color: "#451a03" }, children: t.reasoning })
8535
- ] }),
8536
- t.llmInput && /* @__PURE__ */ jsxs3(Fragment2, { children: [
8537
- /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
8538
- /* @__PURE__ */ jsx4("div", { style: { color: "#71717a", marginBottom: "2px" }, children: "Input \u2192 agent" }),
8539
- /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "4px" }, children: [
8540
- /* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#a1a1aa", marginBottom: "2px" }, children: "System" }),
8541
- /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "80px", overflow: "auto" }, children: t.llmInput.systemPrompt ?? "(empty)" })
8542
- ] }),
8543
- /* @__PURE__ */ jsxs3("div", { children: [
8544
- /* @__PURE__ */ jsx4("div", { style: { fontSize: "10px", color: "#a1a1aa", marginBottom: "2px" }, children: "User" }),
8545
- /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "60px", overflow: "auto" }, children: t.llmInput.userMessage ?? "(empty)" })
8546
- ] })
8547
- ] }),
8548
- /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
8549
- /* @__PURE__ */ jsx4("div", { style: { color: "#71717a", marginBottom: "2px" }, children: "Output \u2190 agent" }),
8550
- /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "60px", overflow: "auto" }, children: t.llmOutput ?? "(empty)" })
8551
- ] })
8552
- ] }),
8553
- !t.llmInput && /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
8554
- /* @__PURE__ */ jsx4("div", { style: { color: "#71717a", marginBottom: "2px" }, children: "LLM output" }),
8555
- /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "60px", overflow: "auto" }, children: t.llmOutput ?? "(empty)" })
8556
- ] }),
8557
8936
  /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "6px" }, children: [
8558
8937
  /* @__PURE__ */ jsx4("div", { style: { color: "#71717a", marginBottom: "2px" }, children: "Actions" }),
8559
8938
  /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "6px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: JSON.stringify(t.actions, null, 2) })
@@ -8565,10 +8944,6 @@ function AgentTraces({ debug, command, defaultExpanded = true }) {
8565
8944
  ] })
8566
8945
  ] }, t.step);
8567
8946
  }),
8568
- traces.length === 0 && debug.llmOutput && debug.llmOutput.length > 0 && /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "12px" }, children: [
8569
- /* @__PURE__ */ jsx4("div", { style: { fontWeight: 600, color: "#3f3f46", marginBottom: "4px" }, children: "LLM output" }),
8570
- /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: debug.llmOutput.join("\n\n") })
8571
- ] }),
8572
8947
  traces.length === 0 && (debug.actions?.length ?? 0) > 0 && /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "12px" }, children: [
8573
8948
  /* @__PURE__ */ jsx4("div", { style: { fontWeight: 600, color: "#3f3f46", marginBottom: "4px" }, children: "Executed actions" }),
8574
8949
  /* @__PURE__ */ jsx4("pre", { style: { margin: 0, padding: "8px", background: "#f4f4f5", borderRadius: "4px", whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: JSON.stringify(debug.actions, null, 2) })
@@ -8638,6 +9013,7 @@ function ModelNexChatBubble({
8638
9013
  const ctx = useContext5(ModelNexContext);
8639
9014
  const [hydrated, setHydrated] = useState13(false);
8640
9015
  const [expanded, setExpanded] = useState13(false);
9016
+ const [docked, setDocked] = useState13(false);
8641
9017
  const [input, setInput] = useState13("");
8642
9018
  const messages = ctx?.chatMessages ?? [];
8643
9019
  const setMessages = ctx?.setChatMessages ?? (() => {
@@ -8692,6 +9068,11 @@ function ModelNexChatBubble({
8692
9068
  const activePlayback = playbackController.playback;
8693
9069
  const activeExperienceType = playbackController.activeExperienceType;
8694
9070
  const startingExperienceType = playbackController.startingExperienceType;
9071
+ useEffect17(() => {
9072
+ return registerExperienceToolBridge({
9073
+ startExperience: playbackController.startExperience
9074
+ });
9075
+ }, [playbackController.startExperience]);
8695
9076
  const createPlaybackView = useCallback12((experienceType) => {
8696
9077
  const isActiveExperience = activePlayback.isActive && activeExperienceType === experienceType;
8697
9078
  const pendingTour = playbackController.pendingPrompt?.experienceType === experienceType ? playbackController.pendingPrompt.tour : null;
@@ -8758,7 +9139,6 @@ function ModelNexChatBubble({
8758
9139
  tagStore.setTagsBatch(data.tags, true);
8759
9140
  lastAutoTaggedUrlRef.current = currentUrl;
8760
9141
  localStorage.setItem(storageKey, "true");
8761
- console.log(`[ModelNex] Auto-tagged ${data.tags.length} elements for ${currentUrl}`);
8762
9142
  }
8763
9143
  } catch (err) {
8764
9144
  console.warn("[ModelNex] Auto-tag error:", err);
@@ -8789,8 +9169,10 @@ function ModelNexChatBubble({
8789
9169
  setHydrated(true);
8790
9170
  try {
8791
9171
  setExpanded(sessionStorage.getItem(BUBBLE_EXPANDED_STORAGE_KEY) === "true");
9172
+ setDocked(sessionStorage.getItem(BUBBLE_DOCKED_STORAGE_KEY) === "true");
8792
9173
  } catch {
8793
9174
  setExpanded(false);
9175
+ setDocked(false);
8794
9176
  }
8795
9177
  }, []);
8796
9178
  const sttActiveRef = useRef13(false);
@@ -8850,6 +9232,13 @@ function ModelNexChatBubble({
8850
9232
  } catch {
8851
9233
  }
8852
9234
  }, [tourPlayback.isActive, onboardingPlayback.isActive]);
9235
+ const setDockedState = useCallback12((next) => {
9236
+ setDocked(next);
9237
+ try {
9238
+ sessionStorage.setItem(BUBBLE_DOCKED_STORAGE_KEY, String(next));
9239
+ } catch {
9240
+ }
9241
+ }, []);
8853
9242
  useEffect17(() => {
8854
9243
  if (shouldAutoExpandForPendingPrompt({
8855
9244
  pendingPrompt,
@@ -8913,12 +9302,9 @@ function ModelNexChatBubble({
8913
9302
  };
8914
9303
  const listeningExperience = resolveTourListeningExperience(preferredExperience, listeningState);
8915
9304
  preferredListeningExperienceRef.current = listeningExperience;
8916
- console.log("[ChatBubble] startTourListening called. listeningState:", listeningState, "preferredExperience:", preferredExperience, "listeningExperience:", listeningExperience);
8917
9305
  if (!canStartTourListening(listeningState)) {
8918
- console.log("[ChatBubble] startTourListening bailed out early.");
8919
9306
  return;
8920
9307
  }
8921
- console.log("[ChatBubble] Proceeding to startTourListening...");
8922
9308
  sttActiveRef.current = true;
8923
9309
  updateTourSttError(null);
8924
9310
  resetFloatingLiveTranscriptSuppression();
@@ -9124,7 +9510,7 @@ function ModelNexChatBubble({
9124
9510
  content: summary || fallbackContent,
9125
9511
  summary: summary ?? void 0,
9126
9512
  nextSteps: nextSteps ?? void 0,
9127
- debug: data?.debug ?? void 0
9513
+ debug: devMode ? data?.debug ?? void 0 : void 0
9128
9514
  }
9129
9515
  ]);
9130
9516
  } catch (err) {
@@ -9211,11 +9597,13 @@ function ModelNexChatBubble({
9211
9597
  fontFamily: "var(--modelnex-font)",
9212
9598
  ...themeStyles
9213
9599
  };
9600
+ const desktopPanelHeight = docked ? "min(calc(100vh - 48px), calc(var(--modelnex-panel-max-height, 600px) + 180px))" : "var(--modelnex-panel-max-height, 600px)";
9601
+ const desktopPanelMaxHeight = docked ? "calc(100vh - 48px)" : "calc(100vh - 120px)";
9214
9602
  const panelStyle = {
9215
9603
  width: isMobile ? "100%" : "var(--modelnex-panel-width, 380px)",
9216
9604
  maxWidth: "calc(100vw - 32px)",
9217
- height: isMobile ? "100%" : "var(--modelnex-panel-max-height, 600px)",
9218
- maxHeight: "calc(100vh - 120px)",
9605
+ height: isMobile ? "100%" : desktopPanelHeight,
9606
+ maxHeight: isMobile ? "100%" : desktopPanelMaxHeight,
9219
9607
  borderRadius: isMobile ? "0" : "var(--modelnex-radius-panel, 20px)",
9220
9608
  border: isMobile ? "none" : "1px solid var(--modelnex-border, #e4e4e7)",
9221
9609
  background: "var(--modelnex-bg, #ffffff)",
@@ -9367,6 +9755,31 @@ function ModelNexChatBubble({
9367
9755
  ] })
9368
9756
  ] }) }),
9369
9757
  /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [
9758
+ !isMobile && /* @__PURE__ */ jsx4(Tooltip, { title: docked ? "Undock" : "Dock", children: /* @__PURE__ */ jsxs3(
9759
+ "button",
9760
+ {
9761
+ onClick: () => setDockedState(!docked),
9762
+ style: {
9763
+ padding: "8px 10px",
9764
+ borderRadius: "10px",
9765
+ border: "none",
9766
+ background: docked ? "rgba(79,70,229,0.08)" : "transparent",
9767
+ cursor: "pointer",
9768
+ color: docked ? "var(--modelnex-accent, #4f46e5)" : "#71717a",
9769
+ transition: "all 0.2s",
9770
+ display: "flex",
9771
+ alignItems: "center",
9772
+ gap: "6px",
9773
+ fontSize: "12px",
9774
+ fontWeight: 700
9775
+ },
9776
+ "aria-label": docked ? "Undock chat bubble" : "Dock chat bubble",
9777
+ children: [
9778
+ docked ? /* @__PURE__ */ jsx4(UndockIcon, {}) : /* @__PURE__ */ jsx4(DockIcon, {}),
9779
+ /* @__PURE__ */ jsx4("span", { children: docked ? "Undock" : "Dock" })
9780
+ ]
9781
+ }
9782
+ ) }),
9370
9783
  (tourPlayback.isActive || onboardingPlayback.isActive || voice.isSpeaking) && /* @__PURE__ */ jsx4(Tooltip, { title: voice.isMuted ? "Unmute" : "Mute", children: /* @__PURE__ */ jsx4(
9371
9784
  "button",
9372
9785
  {
@@ -10140,7 +10553,7 @@ function ModelNexChatBubble({
10140
10553
  )
10141
10554
  }
10142
10555
  ),
10143
- msg.role === "assistant" && msg.debug && /* @__PURE__ */ jsx4(AgentTraces, { debug: msg.debug, command: messages[i - 1]?.content ?? "" })
10556
+ msg.role === "assistant" && devMode && msg.debug && /* @__PURE__ */ jsx4(AgentTraces, { debug: msg.debug, command: messages[i - 1]?.content ?? "" })
10144
10557
  ] }, i)),
10145
10558
  loading && /* @__PURE__ */ jsx4("div", { style: { display: "flex", justifyContent: "flex-start" }, children: /* @__PURE__ */ jsxs3(
10146
10559
  "div",
@@ -11119,6 +11532,7 @@ var ModelNexProvider = ({
11119
11532
  userProfile,
11120
11533
  toursApiBase,
11121
11534
  devMode,
11535
+ devModeKey,
11122
11536
  serverUrl: serverUrlProp
11123
11537
  }) => {
11124
11538
  const serverUrl = serverUrlProp ?? DEFAULT_MODELNEX_SERVER_URL;
@@ -11141,6 +11555,25 @@ var ModelNexProvider = ({
11141
11555
  const [voiceMuted, setVoiceMuted] = useState15(false);
11142
11556
  const [socketId, setSocketId] = useState15(null);
11143
11557
  const [actions, setActions] = useState15(/* @__PURE__ */ new Map());
11558
+ const [validatedBrowserDevMode, setValidatedBrowserDevMode] = useState15(false);
11559
+ const resolvedDevModeKey = useMemo5(() => resolveInjectedDevModeKey(devModeKey), [devModeKey]);
11560
+ useEffect19(() => {
11561
+ let cancelled = false;
11562
+ if (!websiteId || !resolvedDevModeKey) {
11563
+ setValidatedBrowserDevMode(false);
11564
+ return () => {
11565
+ cancelled = true;
11566
+ };
11567
+ }
11568
+ void validateInjectedDevModeKey(serverUrl, websiteId, resolvedDevModeKey).then((enabled) => {
11569
+ if (cancelled) return;
11570
+ setValidatedBrowserDevMode(enabled);
11571
+ });
11572
+ return () => {
11573
+ cancelled = true;
11574
+ };
11575
+ }, [resolvedDevModeKey, serverUrl, websiteId]);
11576
+ const effectiveDevMode = Boolean(devMode) || validatedBrowserDevMode;
11144
11577
  const registerAction = useCallback14((action) => {
11145
11578
  setActions((prev) => {
11146
11579
  const next = new Map(prev);
@@ -11155,30 +11588,41 @@ var ModelNexProvider = ({
11155
11588
  return next;
11156
11589
  });
11157
11590
  }, []);
11158
- const extractedElements = useAutoExtract();
11591
+ const extractedElements = useAutoExtract(effectiveDevMode);
11159
11592
  const tagStore = useTagStore({ serverUrl, websiteId });
11160
- useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId);
11593
+ useBuiltinActions(registerAction, unregisterAction, tagStore, serverUrl, websiteId, toursApiBase, userProfile);
11161
11594
  const CHAT_STORAGE_KEY = "modelnex-chat-messages";
11162
11595
  const [chatMessages, setChatMessagesRaw] = useState15([]);
11163
11596
  useEffect19(() => {
11164
11597
  try {
11165
11598
  const stored = sessionStorage.getItem(CHAT_STORAGE_KEY);
11166
11599
  if (stored) {
11167
- setChatMessagesRaw(JSON.parse(stored));
11600
+ setChatMessagesRaw(sanitizeChatMessages(JSON.parse(stored), effectiveDevMode));
11168
11601
  }
11169
11602
  } catch {
11170
11603
  }
11171
- }, []);
11604
+ }, [effectiveDevMode]);
11605
+ useEffect19(() => {
11606
+ setChatMessagesRaw((prev) => {
11607
+ const next = sanitizeChatMessages(prev, effectiveDevMode);
11608
+ try {
11609
+ sessionStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(next));
11610
+ } catch {
11611
+ }
11612
+ return next;
11613
+ });
11614
+ }, [effectiveDevMode]);
11172
11615
  const setChatMessages = useCallback14((action) => {
11173
11616
  setChatMessagesRaw((prev) => {
11174
- const next = typeof action === "function" ? action(prev) : action;
11617
+ const resolved = typeof action === "function" ? action(prev) : action;
11618
+ const next = sanitizeChatMessages(resolved, effectiveDevMode);
11175
11619
  try {
11176
11620
  sessionStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(next));
11177
11621
  } catch {
11178
11622
  }
11179
11623
  return next;
11180
11624
  });
11181
- }, []);
11625
+ }, [effectiveDevMode]);
11182
11626
  useModelNexSocket({
11183
11627
  serverUrl,
11184
11628
  actions,
@@ -11190,7 +11634,8 @@ var ModelNexProvider = ({
11190
11634
  setStagingFields,
11191
11635
  setExecutedFields,
11192
11636
  onSocketId: setSocketId,
11193
- websiteId
11637
+ websiteId,
11638
+ devMode: effectiveDevMode
11194
11639
  });
11195
11640
  useFieldHighlight(stagingFields, executedFields, setExecutedFields);
11196
11641
  useEffect19(() => {
@@ -11225,9 +11670,9 @@ var ModelNexProvider = ({
11225
11670
  voiceMuted,
11226
11671
  setVoiceMuted,
11227
11672
  socketId,
11228
- devMode
11673
+ devMode: effectiveDevMode
11229
11674
  }),
11230
- [serverUrl, commandUrl, registerAction, unregisterAction, activeAgentActions, stagingFields, highlightActions, studioMode, recordingMode, extractedElements, tagStore, chatMessages, websiteId, userProfile?.userId, userProfile?.type, userProfile?.isNewUser, toursApiBase, voiceMuted, socketId, devMode]
11675
+ [serverUrl, commandUrl, registerAction, unregisterAction, activeAgentActions, stagingFields, highlightActions, studioMode, recordingMode, extractedElements, tagStore, chatMessages, websiteId, userProfile?.userId, userProfile?.type, userProfile?.isNewUser, toursApiBase, voiceMuted, socketId, effectiveDevMode]
11231
11676
  );
11232
11677
  return React8.createElement(
11233
11678
  ModelNexContext.Provider,