@skippr/live-agent-sdk 0.32.0 → 0.34.0

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.
@@ -8,7 +8,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
 
9
9
  // src/components/LiveAgent.tsx
10
10
  import { LiveKitRoom, RoomAudioRenderer } from "@livekit/components-react";
11
- import { useCallback as useCallback7, useEffect as useEffect14, useMemo as useMemo5, useRef as useRef8, useState as useState9 } from "react";
11
+ import { useCallback as useCallback8, useMemo as useMemo5, useState as useState11 } from "react";
12
12
 
13
13
  // src/context/LiveAgentContext.tsx
14
14
  import { createContext } from "react";
@@ -155,11 +155,58 @@ function useAuth({ appKey }) {
155
155
  };
156
156
  }
157
157
 
158
- // src/hooks/useSession.ts
158
+ // src/hooks/useAvailableModules.ts
159
159
  import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
160
160
  var API_URL2 = "https://specialist.skippr.ai/api";
161
+ function useAvailableModules({
162
+ enabled,
163
+ bearerToken
164
+ }) {
165
+ const [modules, setModules] = useState2([]);
166
+ const [isLoading, setIsLoading] = useState2(false);
167
+ const [error, setError] = useState2(null);
168
+ const fetchModules = useCallback2(async () => {
169
+ if (!bearerToken) {
170
+ setError("No auth token available");
171
+ return;
172
+ }
173
+ setIsLoading(true);
174
+ setError(null);
175
+ try {
176
+ const resp = await fetch(`${API_URL2}/v1/modules/available`, {
177
+ credentials: "omit",
178
+ headers: { Authorization: `Bearer ${bearerToken}` }
179
+ });
180
+ if (!resp.ok) {
181
+ const body = await resp.json().catch(() => null);
182
+ throw new Error(body?.detail || `Failed to load modules: ${resp.status}`);
183
+ }
184
+ const data = await resp.json();
185
+ setModules(Array.isArray(data?.modules) ? data.modules : []);
186
+ } catch (e) {
187
+ setError(e instanceof Error ? e.message : "Failed to load modules");
188
+ } finally {
189
+ setIsLoading(false);
190
+ }
191
+ }, [bearerToken]);
192
+ useEffect2(() => {
193
+ if (!enabled || !bearerToken)
194
+ return;
195
+ fetchModules();
196
+ }, [enabled, bearerToken, fetchModules]);
197
+ return {
198
+ modules,
199
+ isLoading,
200
+ error,
201
+ refetch: fetchModules
202
+ };
203
+ }
204
+
205
+ // src/hooks/useSession.ts
206
+ import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
207
+ var API_URL3 = "https://specialist.skippr.ai/api";
161
208
  async function exchangeForBearerToken(appKey, userToken) {
162
- const resp = await fetch(`${API_URL2}/v1/auth/token-exchange`, {
209
+ const resp = await fetch(`${API_URL3}/v1/auth/token-exchange`, {
163
210
  method: "POST",
164
211
  credentials: "omit",
165
212
  headers: {
@@ -174,22 +221,24 @@ async function exchangeForBearerToken(appKey, userToken) {
174
221
  return token;
175
222
  }
176
223
  function useSession({
177
- agentId,
178
224
  captureMode = "screenshare",
179
- agentControls,
180
225
  authToken,
181
226
  appKey,
182
- userToken
227
+ userToken,
228
+ onStart,
229
+ onStartError,
230
+ onDisconnect
183
231
  }) {
184
- const [connection, setConnection] = useState2(null);
185
- const [shouldConnect, setShouldConnect] = useState2(false);
186
- const [isStarting, setIsStarting] = useState2(false);
187
- const [error, setError] = useState2("");
188
- const [errorCode, setErrorCode] = useState2(null);
189
- const [sessionId, setSessionId] = useState2(null);
190
- const [bearerToken, setBearerToken] = useState2(authToken ?? null);
191
- const [pendingScreenStream, setPendingScreenStream] = useState2(null);
192
- useEffect2(() => {
232
+ const [connection, setConnection] = useState3(null);
233
+ const [shouldConnect, setShouldConnect] = useState3(false);
234
+ const [isStarting, setIsStarting] = useState3(false);
235
+ const [isDisconnecting, setIsDisconnecting] = useState3(false);
236
+ const [error, setError] = useState3("");
237
+ const [errorCode, setErrorCode] = useState3(null);
238
+ const [sessionId, setSessionId] = useState3(null);
239
+ const [bearerToken, setBearerToken] = useState3(authToken ?? null);
240
+ const [pendingScreenStream, setPendingScreenStream] = useState3(null);
241
+ useEffect3(() => {
193
242
  let stale = false;
194
243
  if (authToken) {
195
244
  setBearerToken(authToken);
@@ -208,15 +257,19 @@ function useSession({
208
257
  stale = true;
209
258
  };
210
259
  }, [authToken, appKey, userToken]);
211
- const highlightOptIn = agentControls?.highlight;
212
- const startSession = useCallback2(async () => {
260
+ const startSession = useCallback3(async ({ agentId, agentControls }) => {
213
261
  if (!bearerToken) {
214
262
  setError("No auth token available");
215
263
  return;
216
264
  }
265
+ if (!agentId) {
266
+ setError("No agent selected");
267
+ return;
268
+ }
217
269
  setIsStarting(true);
218
270
  setError("");
219
271
  setErrorCode(null);
272
+ onStart?.();
220
273
  let screenStream = null;
221
274
  if (captureMode === "screenshare") {
222
275
  try {
@@ -227,10 +280,10 @@ function useSession({
227
280
  screenStream = null;
228
281
  }
229
282
  }
230
- const requestAgentControls = highlightOptIn === true ? { highlight: true } : undefined;
283
+ const requestAgentControls = agentControls?.highlight === true ? { highlight: true } : undefined;
231
284
  const headers = { Authorization: `Bearer ${bearerToken}` };
232
285
  try {
233
- const createResp = await fetch(`${API_URL2}/v1/sessions`, {
286
+ const createResp = await fetch(`${API_URL3}/v1/sessions`, {
234
287
  method: "POST",
235
288
  credentials: "omit",
236
289
  headers: { "Content-Type": "application/json", ...headers },
@@ -246,7 +299,7 @@ function useSession({
246
299
  throw new Error(body?.detail || `Failed to create session: ${createResp.status}`);
247
300
  }
248
301
  const { session } = await createResp.json();
249
- const startResp = await fetch(`${API_URL2}/v1/sessions/${session.id}/start`, {
302
+ const startResp = await fetch(`${API_URL3}/v1/sessions/${session.id}/start`, {
250
303
  method: "POST",
251
304
  credentials: "omit",
252
305
  headers
@@ -270,40 +323,49 @@ function useSession({
270
323
  track.stop();
271
324
  }
272
325
  setError(e instanceof Error ? e.message : "Failed to start session");
326
+ onStartError?.();
273
327
  } finally {
274
328
  setIsStarting(false);
275
329
  }
276
- }, [agentId, captureMode, highlightOptIn, bearerToken]);
277
- const disconnect = useCallback2(async () => {
278
- if (sessionId && bearerToken) {
279
- try {
280
- await fetch(`${API_URL2}/v1/sessions/${sessionId}/complete`, {
281
- method: "POST",
282
- credentials: "omit",
283
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
284
- body: JSON.stringify({})
285
- });
286
- } catch {}
287
- }
288
- if (pendingScreenStream) {
289
- for (const track of pendingScreenStream.getTracks())
290
- track.stop();
330
+ }, [captureMode, bearerToken, onStart, onStartError]);
331
+ const disconnect = useCallback3(async () => {
332
+ setIsDisconnecting(true);
333
+ try {
334
+ if (sessionId && bearerToken) {
335
+ try {
336
+ await fetch(`${API_URL3}/v1/sessions/${sessionId}/complete`, {
337
+ method: "POST",
338
+ credentials: "omit",
339
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
340
+ body: JSON.stringify({})
341
+ });
342
+ } catch {}
343
+ }
344
+ if (pendingScreenStream) {
345
+ for (const track of pendingScreenStream.getTracks())
346
+ track.stop();
347
+ }
348
+ setError("");
349
+ setShouldConnect(false);
350
+ setConnection(null);
351
+ setSessionId(null);
352
+ setPendingScreenStream(null);
353
+ onDisconnect?.();
354
+ } finally {
355
+ setIsDisconnecting(false);
291
356
  }
292
- setError("");
293
- setShouldConnect(false);
294
- setConnection(null);
295
- setSessionId(null);
296
- setPendingScreenStream(null);
297
- }, [sessionId, bearerToken, pendingScreenStream]);
357
+ }, [sessionId, bearerToken, pendingScreenStream, onDisconnect]);
298
358
  return {
299
359
  connection,
300
360
  shouldConnect,
301
361
  isStarting,
362
+ isDisconnecting,
302
363
  error,
303
364
  errorCode,
304
365
  startSession,
305
366
  disconnect,
306
- pendingScreenStream
367
+ pendingScreenStream,
368
+ bearerToken
307
369
  };
308
370
  }
309
371
 
@@ -320,12 +382,12 @@ var NAME_MAX_CHARS = 80;
320
382
  // src/components/AutoStartMedia.tsx
321
383
  import { useConnectionState, useLocalParticipant } from "@livekit/components-react/hooks";
322
384
  import { ConnectionState, Track } from "livekit-client";
323
- import { useEffect as useEffect3, useRef } from "react";
385
+ import { useEffect as useEffect4, useRef } from "react";
324
386
  function AutoStartMedia({ pendingScreenStream }) {
325
387
  const { localParticipant } = useLocalParticipant();
326
388
  const connectionState = useConnectionState();
327
389
  const didStartRef = useRef(false);
328
- useEffect3(() => {
390
+ useEffect4(() => {
329
391
  if (didStartRef.current)
330
392
  return;
331
393
  if (connectionState !== ConnectionState.Connected)
@@ -350,7 +412,7 @@ function AutoStartMedia({ pendingScreenStream }) {
350
412
  // src/components/DomCapture.tsx
351
413
  import { useConnectionState as useConnectionState2, useLocalParticipant as useLocalParticipant2 } from "@livekit/components-react/hooks";
352
414
  import { ConnectionState as ConnectionState2, ScreenSharePresets, Track as Track2 } from "livekit-client";
353
- import { useEffect as useEffect4, useRef as useRef2 } from "react";
415
+ import { useEffect as useEffect5, useRef as useRef2 } from "react";
354
416
 
355
417
  // src/capture/a11yUtils.ts
356
418
  var ROLE_BY_TAG = {
@@ -1098,7 +1160,7 @@ function DomCapture() {
1098
1160
  const { localParticipant } = useLocalParticipant2();
1099
1161
  const connectionState = useConnectionState2();
1100
1162
  const didStartRef = useRef2(false);
1101
- useEffect4(() => {
1163
+ useEffect5(() => {
1102
1164
  if (didStartRef.current)
1103
1165
  return;
1104
1166
  if (connectionState !== ConnectionState2.Connected)
@@ -1194,255 +1256,6 @@ function DomCapture() {
1194
1256
 
1195
1257
  // src/components/HighlightOverlay.tsx
1196
1258
  import { useDataChannel } from "@livekit/components-react/hooks";
1197
- import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef3, useState as useState3 } from "react";
1198
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
1199
- var TOOLTIP_MAX_WIDTH = 280;
1200
- var TOOLTIP_GAP = 8;
1201
- var VIEWPORT_MARGIN = 8;
1202
- var TOOLTIP_TOP_THRESHOLD = 48;
1203
- var Z_INDEX = 2147483646;
1204
- var RING_INNER = "oklab(0.585 0.0288678 -0.231205 / 0.95)";
1205
- var RING_OUTER = "oklab(0.585 0.0288678 -0.231205 / 0.25)";
1206
- var textDecoder = new TextDecoder;
1207
- function parseHighlightMessage(payload) {
1208
- try {
1209
- const json = JSON.parse(textDecoder.decode(payload));
1210
- if (!json || typeof json !== "object")
1211
- return null;
1212
- if (json.type === "show" && typeof json.ref === "string") {
1213
- return {
1214
- type: "show",
1215
- ref: json.ref,
1216
- message: typeof json.message === "string" ? json.message : ""
1217
- };
1218
- }
1219
- if (json.type === "clear")
1220
- return { type: "clear" };
1221
- return null;
1222
- } catch {
1223
- return null;
1224
- }
1225
- }
1226
- function safeGetIframeDocument(iframe) {
1227
- try {
1228
- return iframe.contentDocument;
1229
- } catch {
1230
- return null;
1231
- }
1232
- }
1233
- function safeQuerySelector(doc, selector) {
1234
- try {
1235
- return doc.querySelector(selector);
1236
- } catch {
1237
- return null;
1238
- }
1239
- }
1240
- function safeQueryAllIframes(doc) {
1241
- try {
1242
- return Array.from(doc.querySelectorAll("iframe"));
1243
- } catch {
1244
- return [];
1245
- }
1246
- }
1247
- function findElementInDocumentTree(rootDoc, selector) {
1248
- const hit = safeQuerySelector(rootDoc, selector);
1249
- if (hit)
1250
- return hit;
1251
- for (const iframe of safeQueryAllIframes(rootDoc)) {
1252
- const innerDoc = safeGetIframeDocument(iframe);
1253
- if (!innerDoc)
1254
- continue;
1255
- const nested = findElementInDocumentTree(innerDoc, selector);
1256
- if (nested)
1257
- return nested;
1258
- }
1259
- return null;
1260
- }
1261
- function findElementByRef(ref) {
1262
- const selector = `[${REF_ATTR}="${CSS.escape(ref)}"]`;
1263
- return findElementInDocumentTree(document, selector);
1264
- }
1265
- function findIframeHostingDocument(targetDoc) {
1266
- const queue = [document];
1267
- while (queue.length > 0) {
1268
- const doc = queue.shift();
1269
- if (!doc)
1270
- continue;
1271
- for (const iframe of safeQueryAllIframes(doc)) {
1272
- if (safeGetIframeDocument(iframe) === targetDoc)
1273
- return iframe;
1274
- const innerDoc = safeGetIframeDocument(iframe);
1275
- if (innerDoc)
1276
- queue.push(innerDoc);
1277
- }
1278
- }
1279
- return null;
1280
- }
1281
- function getRectInTopViewport(el) {
1282
- let rect = el.getBoundingClientRect();
1283
- let ownerDoc = el.ownerDocument;
1284
- while (ownerDoc && ownerDoc !== document) {
1285
- const hostingIframe = findIframeHostingDocument(ownerDoc);
1286
- if (!hostingIframe)
1287
- return rect;
1288
- const iframeRect = hostingIframe.getBoundingClientRect();
1289
- rect = new DOMRect(rect.left + iframeRect.left, rect.top + iframeRect.top, rect.width, rect.height);
1290
- ownerDoc = hostingIframe.ownerDocument;
1291
- }
1292
- return rect;
1293
- }
1294
- function findScrollableAncestor(el) {
1295
- const win = el.ownerDocument?.defaultView ?? window;
1296
- let node = el.parentElement;
1297
- while (node) {
1298
- const overflow = win.getComputedStyle(node).overflow;
1299
- if (/auto|scroll|overlay/.test(overflow))
1300
- return node;
1301
- node = node.parentElement;
1302
- }
1303
- return el.ownerDocument?.documentElement ?? document.documentElement;
1304
- }
1305
- function HighlightOverlay() {
1306
- const [overlayState, setOverlayState] = useState3(null);
1307
- const targetElementRef = useRef3(null);
1308
- const pendingFrameRef = useRef3(null);
1309
- const clearOverlay = useCallback3(() => {
1310
- targetElementRef.current = null;
1311
- setOverlayState(null);
1312
- }, []);
1313
- const recomputeRect = useCallback3(() => {
1314
- pendingFrameRef.current = null;
1315
- const target = targetElementRef.current;
1316
- if (!target)
1317
- return;
1318
- if (!target.isConnected) {
1319
- clearOverlay();
1320
- return;
1321
- }
1322
- const rect2 = getRectInTopViewport(target);
1323
- setOverlayState((prev) => prev ? { ...prev, rect: rect2 } : prev);
1324
- }, [clearOverlay]);
1325
- const scheduleRecompute = useCallback3(() => {
1326
- if (pendingFrameRef.current !== null)
1327
- return;
1328
- pendingFrameRef.current = window.requestAnimationFrame(recomputeRect);
1329
- }, [recomputeRect]);
1330
- const onHighlightMessage = useCallback3((msg) => {
1331
- const parsed = parseHighlightMessage(msg.payload);
1332
- if (!parsed)
1333
- return;
1334
- if (parsed.type === "clear") {
1335
- clearOverlay();
1336
- return;
1337
- }
1338
- const target = findElementByRef(parsed.ref);
1339
- if (!target) {
1340
- clearOverlay();
1341
- return;
1342
- }
1343
- targetElementRef.current = target;
1344
- setOverlayState({
1345
- ref: parsed.ref,
1346
- message: parsed.message ?? "",
1347
- rect: getRectInTopViewport(target)
1348
- });
1349
- }, [clearOverlay]);
1350
- useDataChannel(HIGHLIGHT_TOPIC, onHighlightMessage);
1351
- useEffect5(() => {
1352
- if (!overlayState)
1353
- return;
1354
- const target = targetElementRef.current;
1355
- if (!target)
1356
- return;
1357
- const onScroll = () => scheduleRecompute();
1358
- const onResize = () => scheduleRecompute();
1359
- const watchedWindows = new Set;
1360
- watchedWindows.add(window);
1361
- const ownerWindow = target.ownerDocument?.defaultView;
1362
- if (ownerWindow && ownerWindow !== window)
1363
- watchedWindows.add(ownerWindow);
1364
- for (const win of watchedWindows) {
1365
- win.addEventListener("scroll", onScroll, { capture: true, passive: true });
1366
- win.addEventListener("resize", onResize, { passive: true });
1367
- }
1368
- const resizeObserver = new ResizeObserver(scheduleRecompute);
1369
- resizeObserver.observe(target);
1370
- const scrollContainer = findScrollableAncestor(target);
1371
- const mutationObserver = new MutationObserver(() => {
1372
- if (!targetElementRef.current?.isConnected) {
1373
- clearOverlay();
1374
- return;
1375
- }
1376
- scheduleRecompute();
1377
- });
1378
- mutationObserver.observe(scrollContainer, { childList: true, subtree: true });
1379
- return () => {
1380
- for (const win of watchedWindows) {
1381
- win.removeEventListener("scroll", onScroll, { capture: true });
1382
- win.removeEventListener("resize", onResize);
1383
- }
1384
- resizeObserver.disconnect();
1385
- mutationObserver.disconnect();
1386
- if (pendingFrameRef.current !== null) {
1387
- window.cancelAnimationFrame(pendingFrameRef.current);
1388
- pendingFrameRef.current = null;
1389
- }
1390
- };
1391
- }, [overlayState, scheduleRecompute, clearOverlay]);
1392
- if (!overlayState)
1393
- return null;
1394
- const { rect, message } = overlayState;
1395
- const ringStyle = {
1396
- position: "fixed",
1397
- left: `${rect.left}px`,
1398
- top: `${rect.top}px`,
1399
- width: `${rect.width}px`,
1400
- height: `${rect.height}px`,
1401
- pointerEvents: "none",
1402
- zIndex: Z_INDEX,
1403
- boxShadow: `0 0 0 2px ${RING_INNER}, 0 0 0 6px ${RING_OUTER}`,
1404
- borderRadius: "4px",
1405
- transition: "none"
1406
- };
1407
- const showTooltipBelow = rect.top < TOOLTIP_TOP_THRESHOLD;
1408
- const tooltipTop = showTooltipBelow ? rect.bottom + TOOLTIP_GAP : rect.top - TOOLTIP_GAP;
1409
- const tooltipTransform = showTooltipBelow ? "translateY(0)" : "translateY(-100%)";
1410
- const tooltipLeft = Math.min(Math.max(rect.left, VIEWPORT_MARGIN), window.innerWidth - TOOLTIP_MAX_WIDTH - VIEWPORT_MARGIN);
1411
- const tooltipStyle = {
1412
- position: "fixed",
1413
- left: `${tooltipLeft}px`,
1414
- top: `${tooltipTop}px`,
1415
- transform: tooltipTransform,
1416
- maxWidth: `${TOOLTIP_MAX_WIDTH}px`,
1417
- pointerEvents: "none",
1418
- zIndex: Z_INDEX,
1419
- background: "rgba(45, 43, 61, 0.96)",
1420
- color: "#ffffff",
1421
- padding: "6px 10px",
1422
- borderRadius: "8px",
1423
- fontSize: "13px",
1424
- lineHeight: "1.35",
1425
- fontFamily: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Inter, sans-serif',
1426
- boxShadow: "0 6px 20px rgba(0, 0, 0, 0.25)",
1427
- whiteSpace: "normal",
1428
- wordWrap: "break-word"
1429
- };
1430
- return /* @__PURE__ */ jsxs(Fragment, {
1431
- children: [
1432
- /* @__PURE__ */ jsx("div", {
1433
- "data-skippr-private": "true",
1434
- style: ringStyle,
1435
- "aria-hidden": "true"
1436
- }),
1437
- message && /* @__PURE__ */ jsx("div", {
1438
- "data-skippr-private": "true",
1439
- style: tooltipStyle,
1440
- role: "tooltip",
1441
- children: message
1442
- })
1443
- ]
1444
- });
1445
- }
1446
1259
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/createLucideIcon.js
1447
1260
  import { forwardRef as forwardRef2, createElement as createElement3 } from "react";
1448
1261
 
@@ -1557,28 +1370,63 @@ var __iconNode3 = [
1557
1370
  ["circle", { cx: "4", cy: "20", r: "2", key: "6kqj1y" }]
1558
1371
  ];
1559
1372
  var Sparkles = createLucideIcon("sparkles", __iconNode3);
1560
- // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/calendar.js
1373
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/bot.js
1561
1374
  var __iconNode4 = [
1375
+ ["path", { d: "M12 8V4H8", key: "hb8ula" }],
1376
+ ["rect", { width: "16", height: "12", x: "4", y: "8", rx: "2", key: "enze0r" }],
1377
+ ["path", { d: "M2 14h2", key: "vft8re" }],
1378
+ ["path", { d: "M20 14h2", key: "4cs60a" }],
1379
+ ["path", { d: "M15 13v2", key: "1xurst" }],
1380
+ ["path", { d: "M9 13v2", key: "rq6x2g" }]
1381
+ ];
1382
+ var Bot = createLucideIcon("bot", __iconNode4);
1383
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/calendar.js
1384
+ var __iconNode5 = [
1562
1385
  ["path", { d: "M8 2v4", key: "1cmpym" }],
1563
1386
  ["path", { d: "M16 2v4", key: "4m81vk" }],
1564
1387
  ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2", key: "1hopcy" }],
1565
1388
  ["path", { d: "M3 10h18", key: "8toen8" }]
1566
1389
  ];
1567
- var Calendar = createLucideIcon("calendar", __iconNode4);
1390
+ var Calendar = createLucideIcon("calendar", __iconNode5);
1568
1391
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/chevron-down.js
1569
- var __iconNode5 = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
1570
- var ChevronDown = createLucideIcon("chevron-down", __iconNode5);
1392
+ var __iconNode6 = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
1393
+ var ChevronDown = createLucideIcon("chevron-down", __iconNode6);
1571
1394
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/circle.js
1572
- var __iconNode6 = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
1573
- var Circle = createLucideIcon("circle", __iconNode6);
1395
+ var __iconNode7 = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
1396
+ var Circle = createLucideIcon("circle", __iconNode7);
1397
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/graduation-cap.js
1398
+ var __iconNode8 = [
1399
+ [
1400
+ "path",
1401
+ {
1402
+ d: "M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z",
1403
+ key: "j76jl0"
1404
+ }
1405
+ ],
1406
+ ["path", { d: "M22 10v6", key: "1lu8f3" }],
1407
+ ["path", { d: "M6 12.5V16a6 3 0 0 0 12 0v-3.5", key: "1r8lef" }]
1408
+ ];
1409
+ var GraduationCap = createLucideIcon("graduation-cap", __iconNode8);
1410
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/headset.js
1411
+ var __iconNode9 = [
1412
+ [
1413
+ "path",
1414
+ {
1415
+ d: "M3 11h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-5Zm0 0a9 9 0 1 1 18 0m0 0v5a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3Z",
1416
+ key: "12oyoe"
1417
+ }
1418
+ ],
1419
+ ["path", { d: "M21 16v2a4 4 0 0 1-4 4h-5", key: "1x7m43" }]
1420
+ ];
1421
+ var Headset = createLucideIcon("headset", __iconNode9);
1574
1422
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mail.js
1575
- var __iconNode7 = [
1423
+ var __iconNode10 = [
1576
1424
  ["path", { d: "m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7", key: "132q7q" }],
1577
1425
  ["rect", { x: "2", y: "4", width: "20", height: "16", rx: "2", key: "izxlao" }]
1578
1426
  ];
1579
- var Mail = createLucideIcon("mail", __iconNode7);
1427
+ var Mail = createLucideIcon("mail", __iconNode10);
1580
1428
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-circle.js
1581
- var __iconNode8 = [
1429
+ var __iconNode11 = [
1582
1430
  [
1583
1431
  "path",
1584
1432
  {
@@ -1587,9 +1435,9 @@ var __iconNode8 = [
1587
1435
  }
1588
1436
  ]
1589
1437
  ];
1590
- var MessageCircle = createLucideIcon("message-circle", __iconNode8);
1438
+ var MessageCircle = createLucideIcon("message-circle", __iconNode11);
1591
1439
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-square.js
1592
- var __iconNode9 = [
1440
+ var __iconNode12 = [
1593
1441
  [
1594
1442
  "path",
1595
1443
  {
@@ -1598,9 +1446,9 @@ var __iconNode9 = [
1598
1446
  }
1599
1447
  ]
1600
1448
  ];
1601
- var MessageSquare = createLucideIcon("message-square", __iconNode9);
1449
+ var MessageSquare = createLucideIcon("message-square", __iconNode12);
1602
1450
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic-off.js
1603
- var __iconNode10 = [
1451
+ var __iconNode13 = [
1604
1452
  ["path", { d: "M12 19v3", key: "npa21l" }],
1605
1453
  ["path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33", key: "1gzdoj" }],
1606
1454
  ["path", { d: "M16.95 16.95A7 7 0 0 1 5 12v-2", key: "cqa7eg" }],
@@ -1608,71 +1456,341 @@ var __iconNode10 = [
1608
1456
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
1609
1457
  ["path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12", key: "r2i35w" }]
1610
1458
  ];
1611
- var MicOff = createLucideIcon("mic-off", __iconNode10);
1459
+ var MicOff = createLucideIcon("mic-off", __iconNode13);
1612
1460
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic.js
1613
- var __iconNode11 = [
1461
+ var __iconNode14 = [
1614
1462
  ["path", { d: "M12 19v3", key: "npa21l" }],
1615
1463
  ["path", { d: "M19 10v2a7 7 0 0 1-14 0v-2", key: "1vc78b" }],
1616
1464
  ["rect", { x: "9", y: "2", width: "6", height: "13", rx: "3", key: "s6n7sd" }]
1617
1465
  ];
1618
- var Mic = createLucideIcon("mic", __iconNode11);
1466
+ var Mic = createLucideIcon("mic", __iconNode14);
1619
1467
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/minimize-2.js
1620
- var __iconNode12 = [
1468
+ var __iconNode15 = [
1621
1469
  ["path", { d: "m14 10 7-7", key: "oa77jy" }],
1622
1470
  ["path", { d: "M20 10h-6V4", key: "mjg0md" }],
1623
1471
  ["path", { d: "m3 21 7-7", key: "tjx5ai" }],
1624
1472
  ["path", { d: "M4 14h6v6", key: "rmj7iw" }]
1625
1473
  ];
1626
- var Minimize2 = createLucideIcon("minimize-2", __iconNode12);
1474
+ var Minimize2 = createLucideIcon("minimize-2", __iconNode15);
1627
1475
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor-off.js
1628
- var __iconNode13 = [
1476
+ var __iconNode16 = [
1629
1477
  ["path", { d: "M12 17v4", key: "1riwvh" }],
1630
1478
  ["path", { d: "M17 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 1.184-1.826", key: "cv7jms" }],
1631
1479
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
1632
1480
  ["path", { d: "M8 21h8", key: "1ev6f3" }],
1633
1481
  ["path", { d: "M8.656 3H20a2 2 0 0 1 2 2v10a2 2 0 0 1-.293 1.042", key: "z8ni2w" }]
1634
1482
  ];
1635
- var MonitorOff = createLucideIcon("monitor-off", __iconNode13);
1483
+ var MonitorOff = createLucideIcon("monitor-off", __iconNode16);
1636
1484
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor.js
1637
- var __iconNode14 = [
1485
+ var __iconNode17 = [
1638
1486
  ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2", key: "48i651" }],
1639
1487
  ["line", { x1: "8", x2: "16", y1: "21", y2: "21", key: "1svkeh" }],
1640
1488
  ["line", { x1: "12", x2: "12", y1: "17", y2: "21", key: "vw1qmm" }]
1641
1489
  ];
1642
- var Monitor = createLucideIcon("monitor", __iconNode14);
1490
+ var Monitor = createLucideIcon("monitor", __iconNode17);
1491
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mouse-pointer-2.js
1492
+ var __iconNode18 = [
1493
+ [
1494
+ "path",
1495
+ {
1496
+ d: "M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z",
1497
+ key: "edeuup"
1498
+ }
1499
+ ]
1500
+ ];
1501
+ var MousePointer2 = createLucideIcon("mouse-pointer-2", __iconNode18);
1643
1502
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/phone-off.js
1644
- var __iconNode15 = [
1503
+ var __iconNode19 = [
1645
1504
  [
1646
1505
  "path",
1647
1506
  {
1648
1507
  d: "M10.1 13.9a14 14 0 0 0 3.732 2.668 1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2 18 18 0 0 1-12.728-5.272",
1649
1508
  key: "1wngk7"
1650
1509
  }
1651
- ],
1652
- ["path", { d: "M22 2 2 22", key: "y4kqgn" }],
1653
- [
1654
- "path",
1655
- {
1656
- d: "M4.76 13.582A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 .244.473",
1657
- key: "10hv5p"
1510
+ ],
1511
+ ["path", { d: "M22 2 2 22", key: "y4kqgn" }],
1512
+ [
1513
+ "path",
1514
+ {
1515
+ d: "M4.76 13.582A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 .244.473",
1516
+ key: "10hv5p"
1517
+ }
1518
+ ]
1519
+ ];
1520
+ var PhoneOff = createLucideIcon("phone-off", __iconNode19);
1521
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/rocket.js
1522
+ var __iconNode20 = [
1523
+ ["path", { d: "M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5", key: "qeys4" }],
1524
+ [
1525
+ "path",
1526
+ {
1527
+ d: "M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09",
1528
+ key: "u4xsad"
1529
+ }
1530
+ ],
1531
+ [
1532
+ "path",
1533
+ {
1534
+ d: "M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z",
1535
+ key: "676m9"
1536
+ }
1537
+ ],
1538
+ ["path", { d: "M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05", key: "92ym6u" }]
1539
+ ];
1540
+ var Rocket = createLucideIcon("rocket", __iconNode20);
1541
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/send.js
1542
+ var __iconNode21 = [
1543
+ [
1544
+ "path",
1545
+ {
1546
+ d: "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z",
1547
+ key: "1ffxy3"
1548
+ }
1549
+ ],
1550
+ ["path", { d: "m21.854 2.147-10.94 10.939", key: "12cjpa" }]
1551
+ ];
1552
+ var Send = createLucideIcon("send", __iconNode21);
1553
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/user-plus.js
1554
+ var __iconNode22 = [
1555
+ ["path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2", key: "1yyitq" }],
1556
+ ["circle", { cx: "9", cy: "7", r: "4", key: "nufk8" }],
1557
+ ["line", { x1: "19", x2: "19", y1: "8", y2: "14", key: "1bvyxn" }],
1558
+ ["line", { x1: "22", x2: "16", y1: "11", y2: "11", key: "1shjgl" }]
1559
+ ];
1560
+ var UserPlus = createLucideIcon("user-plus", __iconNode22);
1561
+ // src/components/HighlightOverlay.tsx
1562
+ import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef3, useState as useState4 } from "react";
1563
+ import { jsx, jsxs } from "react/jsx-runtime";
1564
+ var Z_INDEX = 2147483646;
1565
+ var HIGHLIGHT_PADDING = 6;
1566
+ var textDecoder = new TextDecoder;
1567
+ function parseHighlightMessage(payload) {
1568
+ try {
1569
+ const json = JSON.parse(textDecoder.decode(payload));
1570
+ if (!json || typeof json !== "object")
1571
+ return null;
1572
+ if (json.type === "show" && typeof json.ref === "string") {
1573
+ return {
1574
+ type: "show",
1575
+ ref: json.ref,
1576
+ message: typeof json.message === "string" ? json.message : undefined
1577
+ };
1578
+ }
1579
+ if (json.type === "clear")
1580
+ return { type: "clear" };
1581
+ return null;
1582
+ } catch {
1583
+ return null;
1584
+ }
1585
+ }
1586
+ function safeGetIframeDocument(iframe) {
1587
+ try {
1588
+ return iframe.contentDocument;
1589
+ } catch {
1590
+ return null;
1591
+ }
1592
+ }
1593
+ function safeQuerySelector(doc, selector) {
1594
+ try {
1595
+ return doc.querySelector(selector);
1596
+ } catch {
1597
+ return null;
1598
+ }
1599
+ }
1600
+ function safeQueryAllIframes(doc) {
1601
+ try {
1602
+ return Array.from(doc.querySelectorAll("iframe"));
1603
+ } catch {
1604
+ return [];
1605
+ }
1606
+ }
1607
+ function findElementInDocumentTree(rootDoc, selector) {
1608
+ const hit = safeQuerySelector(rootDoc, selector);
1609
+ if (hit)
1610
+ return hit;
1611
+ for (const iframe of safeQueryAllIframes(rootDoc)) {
1612
+ const innerDoc = safeGetIframeDocument(iframe);
1613
+ if (!innerDoc)
1614
+ continue;
1615
+ const nested = findElementInDocumentTree(innerDoc, selector);
1616
+ if (nested)
1617
+ return nested;
1618
+ }
1619
+ return null;
1620
+ }
1621
+ function findElementByRef(ref) {
1622
+ const selector = `[${REF_ATTR}="${CSS.escape(ref)}"]`;
1623
+ return findElementInDocumentTree(document, selector);
1624
+ }
1625
+ function findIframeHostingDocument(targetDoc) {
1626
+ const queue = [document];
1627
+ while (queue.length > 0) {
1628
+ const doc = queue.shift();
1629
+ if (!doc)
1630
+ continue;
1631
+ for (const iframe of safeQueryAllIframes(doc)) {
1632
+ if (safeGetIframeDocument(iframe) === targetDoc)
1633
+ return iframe;
1634
+ const innerDoc = safeGetIframeDocument(iframe);
1635
+ if (innerDoc)
1636
+ queue.push(innerDoc);
1637
+ }
1638
+ }
1639
+ return null;
1640
+ }
1641
+ function getRectInTopViewport(el) {
1642
+ let rect = el.getBoundingClientRect();
1643
+ let ownerDoc = el.ownerDocument;
1644
+ while (ownerDoc && ownerDoc !== document) {
1645
+ const hostingIframe = findIframeHostingDocument(ownerDoc);
1646
+ if (!hostingIframe)
1647
+ return rect;
1648
+ const iframeRect = hostingIframe.getBoundingClientRect();
1649
+ rect = new DOMRect(rect.left + iframeRect.left, rect.top + iframeRect.top, rect.width, rect.height);
1650
+ ownerDoc = hostingIframe.ownerDocument;
1651
+ }
1652
+ return rect;
1653
+ }
1654
+ function findScrollableAncestor(el) {
1655
+ const win = el.ownerDocument?.defaultView ?? window;
1656
+ let node = el.parentElement;
1657
+ while (node) {
1658
+ const overflow = win.getComputedStyle(node).overflow;
1659
+ if (/auto|scroll|overlay/.test(overflow))
1660
+ return node;
1661
+ node = node.parentElement;
1662
+ }
1663
+ return el.ownerDocument?.documentElement ?? document.documentElement;
1664
+ }
1665
+ function HighlightOverlay() {
1666
+ const [overlayState, setOverlayState] = useState4(null);
1667
+ const targetElementRef = useRef3(null);
1668
+ const pendingFrameRef = useRef3(null);
1669
+ const clearOverlay = useCallback4(() => {
1670
+ targetElementRef.current = null;
1671
+ setOverlayState(null);
1672
+ }, []);
1673
+ const recomputeRect = useCallback4(() => {
1674
+ pendingFrameRef.current = null;
1675
+ const target = targetElementRef.current;
1676
+ if (!target)
1677
+ return;
1678
+ if (!target.isConnected) {
1679
+ clearOverlay();
1680
+ return;
1681
+ }
1682
+ const rect2 = getRectInTopViewport(target);
1683
+ setOverlayState((prev) => prev ? { ...prev, rect: rect2 } : prev);
1684
+ }, [clearOverlay]);
1685
+ const scheduleRecompute = useCallback4(() => {
1686
+ if (pendingFrameRef.current !== null)
1687
+ return;
1688
+ pendingFrameRef.current = window.requestAnimationFrame(recomputeRect);
1689
+ }, [recomputeRect]);
1690
+ const onHighlightMessage = useCallback4((msg) => {
1691
+ const parsed = parseHighlightMessage(msg.payload);
1692
+ if (!parsed)
1693
+ return;
1694
+ if (parsed.type === "clear") {
1695
+ clearOverlay();
1696
+ return;
1658
1697
  }
1659
- ]
1660
- ];
1661
- var PhoneOff = createLucideIcon("phone-off", __iconNode15);
1662
- // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/send.js
1663
- var __iconNode16 = [
1664
- [
1665
- "path",
1666
- {
1667
- d: "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z",
1668
- key: "1ffxy3"
1698
+ const target = findElementByRef(parsed.ref);
1699
+ if (!target) {
1700
+ clearOverlay();
1701
+ return;
1669
1702
  }
1670
- ],
1671
- ["path", { d: "m21.854 2.147-10.94 10.939", key: "12cjpa" }]
1672
- ];
1673
- var Send = createLucideIcon("send", __iconNode16);
1703
+ targetElementRef.current = target;
1704
+ setOverlayState({
1705
+ ref: parsed.ref,
1706
+ message: parsed.message || "Click here",
1707
+ rect: getRectInTopViewport(target)
1708
+ });
1709
+ }, [clearOverlay]);
1710
+ useDataChannel(HIGHLIGHT_TOPIC, onHighlightMessage);
1711
+ useEffect6(() => {
1712
+ if (!overlayState)
1713
+ return;
1714
+ const target = targetElementRef.current;
1715
+ if (!target)
1716
+ return;
1717
+ const onScroll = () => scheduleRecompute();
1718
+ const onResize = () => scheduleRecompute();
1719
+ const watchedWindows = new Set;
1720
+ watchedWindows.add(window);
1721
+ const ownerWindow = target.ownerDocument?.defaultView;
1722
+ if (ownerWindow && ownerWindow !== window)
1723
+ watchedWindows.add(ownerWindow);
1724
+ for (const win of watchedWindows) {
1725
+ win.addEventListener("scroll", onScroll, { capture: true, passive: true });
1726
+ win.addEventListener("resize", onResize, { passive: true });
1727
+ }
1728
+ const resizeObserver = new ResizeObserver(scheduleRecompute);
1729
+ resizeObserver.observe(target);
1730
+ const scrollContainer = findScrollableAncestor(target);
1731
+ const mutationObserver = new MutationObserver(() => {
1732
+ if (!targetElementRef.current?.isConnected) {
1733
+ clearOverlay();
1734
+ return;
1735
+ }
1736
+ scheduleRecompute();
1737
+ });
1738
+ mutationObserver.observe(scrollContainer, { childList: true, subtree: true });
1739
+ return () => {
1740
+ for (const win of watchedWindows) {
1741
+ win.removeEventListener("scroll", onScroll, { capture: true });
1742
+ win.removeEventListener("resize", onResize);
1743
+ }
1744
+ resizeObserver.disconnect();
1745
+ mutationObserver.disconnect();
1746
+ if (pendingFrameRef.current !== null) {
1747
+ window.cancelAnimationFrame(pendingFrameRef.current);
1748
+ pendingFrameRef.current = null;
1749
+ }
1750
+ };
1751
+ }, [overlayState, scheduleRecompute, clearOverlay]);
1752
+ if (!overlayState)
1753
+ return null;
1754
+ const { rect, message } = overlayState;
1755
+ const containerStyle = {
1756
+ position: "fixed",
1757
+ left: `${rect.left - HIGHLIGHT_PADDING}px`,
1758
+ top: `${rect.top - HIGHLIGHT_PADDING}px`,
1759
+ width: `${rect.width + HIGHLIGHT_PADDING * 2}px`,
1760
+ height: `${rect.height + HIGHLIGHT_PADDING * 2}px`,
1761
+ pointerEvents: "none",
1762
+ zIndex: Z_INDEX
1763
+ };
1764
+ return /* @__PURE__ */ jsxs("div", {
1765
+ "data-skippr-private": "true",
1766
+ style: containerStyle,
1767
+ className: "skippr:relative",
1768
+ children: [
1769
+ /* @__PURE__ */ jsx("div", {
1770
+ "aria-hidden": "true",
1771
+ className: "skippr:absolute skippr:inset-0 skippr:rounded-[1rem] skippr:border-2 skippr:border-cyan-400 skippr:animate-skippr-annotation-pulse"
1772
+ }),
1773
+ /* @__PURE__ */ jsxs("div", {
1774
+ role: "tooltip",
1775
+ className: "skippr:absolute skippr:-bottom-9 skippr:left-1/2 skippr:-translate-x-1/2 skippr:flex skippr:items-center skippr:gap-1.5 skippr:rounded-full skippr:bg-cyan-400 skippr:px-2.5 skippr:py-1 skippr:shadow-[0_4px_12px_rgba(0,0,0,0.12)]",
1776
+ children: [
1777
+ /* @__PURE__ */ jsx(MousePointer2, {
1778
+ "aria-hidden": "true",
1779
+ className: "skippr:h-3 skippr:w-3 skippr:text-cyan-900",
1780
+ fill: "currentColor"
1781
+ }),
1782
+ /* @__PURE__ */ jsx("span", {
1783
+ className: "skippr:text-[11px] skippr:font-medium skippr:text-cyan-900 skippr:whitespace-nowrap",
1784
+ children: message
1785
+ })
1786
+ ]
1787
+ })
1788
+ ]
1789
+ });
1790
+ }
1791
+
1674
1792
  // src/components/MinimizedBubble.tsx
1675
- import { useEffect as useEffect6 } from "react";
1793
+ import { useEffect as useEffect7 } from "react";
1676
1794
 
1677
1795
  // src/hooks/useLiveAgent.ts
1678
1796
  import { use } from "react";
@@ -1688,7 +1806,7 @@ function useLiveAgent() {
1688
1806
  // src/hooks/useMediaControls.ts
1689
1807
  import { useLocalParticipant as useLocalParticipant3 } from "@livekit/components-react/hooks";
1690
1808
  import { ScreenSharePresets as ScreenSharePresets2 } from "livekit-client";
1691
- import { useCallback as useCallback4 } from "react";
1809
+ import { useCallback as useCallback5 } from "react";
1692
1810
  var SCREEN_SHARE_OPTIONS = {
1693
1811
  video: { displaySurface: "browser" },
1694
1812
  resolution: ScreenSharePresets2.h720fps30.resolution,
@@ -1698,14 +1816,14 @@ function useMediaControls() {
1698
1816
  const { localParticipant } = useLocalParticipant3();
1699
1817
  const isMuted = !localParticipant.isMicrophoneEnabled;
1700
1818
  const isScreenSharing = localParticipant.isScreenShareEnabled;
1701
- const toggleMute = useCallback4(async () => {
1819
+ const toggleMute = useCallback5(async () => {
1702
1820
  try {
1703
1821
  await localParticipant.setMicrophoneEnabled(isMuted);
1704
1822
  } catch (error) {
1705
1823
  console.error("Failed to toggle microphone:", error);
1706
1824
  }
1707
1825
  }, [localParticipant, isMuted]);
1708
- const toggleScreenShare = useCallback4(async () => {
1826
+ const toggleScreenShare = useCallback5(async () => {
1709
1827
  try {
1710
1828
  await localParticipant.setScreenShareEnabled(!isScreenSharing, SCREEN_SHARE_OPTIONS);
1711
1829
  } catch (error) {
@@ -1734,7 +1852,7 @@ function useAgentVoiceState() {
1734
1852
  }
1735
1853
 
1736
1854
  // src/components/LauncherStatusPill.tsx
1737
- import { jsx as jsx2, jsxs as jsxs2, Fragment as Fragment2 } from "react/jsx-runtime";
1855
+ import { jsx as jsx2, jsxs as jsxs2, Fragment } from "react/jsx-runtime";
1738
1856
  function pillStatusFromAgent(state, canSeePage) {
1739
1857
  if (state === "speaking")
1740
1858
  return "talking";
@@ -1743,26 +1861,36 @@ function pillStatusFromAgent(state, canSeePage) {
1743
1861
  return canSeePage ? "observing" : "connected";
1744
1862
  }
1745
1863
  function LauncherStatusPill() {
1746
- const { isConnected, expandPanel, setSidebarTab, position, captureMode } = useLiveAgent();
1864
+ const { isConnected, isStarting, expandPanel, setSidebarTab, position, captureMode } = useLiveAgent();
1747
1865
  const { state } = useAgentVoiceState();
1748
1866
  const { isScreenSharing } = useMediaControls();
1749
- if (!isConnected)
1867
+ const isStandingBy = isStarting && !isConnected;
1868
+ if (!isConnected && !isStandingBy)
1750
1869
  return null;
1751
1870
  const canSeePage = captureMode === "auto" || isScreenSharing;
1752
- const status = pillStatusFromAgent(state, canSeePage);
1753
- const openChat = () => {
1754
- setSidebarTab("chat");
1871
+ const status = isStandingBy ? "standing-by" : pillStatusFromAgent(state, canSeePage);
1872
+ const handleClick = () => {
1873
+ if (!isStandingBy)
1874
+ setSidebarTab("chat");
1755
1875
  expandPanel();
1756
1876
  };
1757
1877
  return /* @__PURE__ */ jsx2("button", {
1758
1878
  type: "button",
1759
- onClick: openChat,
1879
+ onClick: handleClick,
1760
1880
  className: cn("skippr:fixed skippr:bottom-20 skippr:z-[9999]", "skippr:flex skippr:items-center skippr:gap-2", "skippr:rounded-full skippr:bg-bubble/95 skippr:backdrop-blur-sm", "skippr:px-3 skippr:py-1.5", "skippr:text-xs skippr:font-medium skippr:text-white", "skippr:shadow-[0_8px_24px_rgba(45,43,61,0.35)]", "skippr:cursor-pointer skippr:transition-colors skippr:hover:bg-bubble", "skippr:animate-[skippr-bubble-in_0.28s_ease-out]", position === "right" ? "skippr:right-6" : "skippr:left-6"),
1761
- "aria-label": `Skippr is ${status} — click to open chat`,
1881
+ "aria-label": isStandingBy ? "Skippr is standing by — click to open" : `Skippr is ${status} — click to open chat`,
1762
1882
  children: /* @__PURE__ */ jsxs2("span", {
1763
1883
  className: "skippr:flex skippr:items-center skippr:gap-2 skippr:animate-[skippr-pill-content_0.22s_ease-out]",
1764
1884
  children: [
1765
- status === "observing" && /* @__PURE__ */ jsxs2(Fragment2, {
1885
+ status === "standing-by" && /* @__PURE__ */ jsxs2(Fragment, {
1886
+ children: [
1887
+ /* @__PURE__ */ jsx2("span", {
1888
+ children: "Standing by"
1889
+ }),
1890
+ /* @__PURE__ */ jsx2(ThinkingDots, {})
1891
+ ]
1892
+ }),
1893
+ status === "observing" && /* @__PURE__ */ jsxs2(Fragment, {
1766
1894
  children: [
1767
1895
  /* @__PURE__ */ jsx2(ObservingIcon, {}),
1768
1896
  /* @__PURE__ */ jsx2("span", {
@@ -1770,7 +1898,7 @@ function LauncherStatusPill() {
1770
1898
  })
1771
1899
  ]
1772
1900
  }),
1773
- status === "connected" && /* @__PURE__ */ jsxs2(Fragment2, {
1901
+ status === "connected" && /* @__PURE__ */ jsxs2(Fragment, {
1774
1902
  children: [
1775
1903
  /* @__PURE__ */ jsx2(ConnectedDot, {}),
1776
1904
  /* @__PURE__ */ jsx2("span", {
@@ -1778,7 +1906,7 @@ function LauncherStatusPill() {
1778
1906
  })
1779
1907
  ]
1780
1908
  }),
1781
- status === "talking" && /* @__PURE__ */ jsxs2(Fragment2, {
1909
+ status === "talking" && /* @__PURE__ */ jsxs2(Fragment, {
1782
1910
  children: [
1783
1911
  /* @__PURE__ */ jsx2(SpeakingBars, {}),
1784
1912
  /* @__PURE__ */ jsx2("span", {
@@ -1786,7 +1914,7 @@ function LauncherStatusPill() {
1786
1914
  })
1787
1915
  ]
1788
1916
  }),
1789
- status === "thinking" && /* @__PURE__ */ jsxs2(Fragment2, {
1917
+ status === "thinking" && /* @__PURE__ */ jsxs2(Fragment, {
1790
1918
  children: [
1791
1919
  /* @__PURE__ */ jsx2(ThinkingDots, {}),
1792
1920
  /* @__PURE__ */ jsx2("span", {
@@ -1958,7 +2086,7 @@ function Logo({ className }) {
1958
2086
  }
1959
2087
 
1960
2088
  // src/components/MinimizedBubble.tsx
1961
- import { jsx as jsx4, jsxs as jsxs4, Fragment as Fragment3 } from "react/jsx-runtime";
2089
+ import { jsx as jsx4, jsxs as jsxs4, Fragment as Fragment2 } from "react/jsx-runtime";
1962
2090
  var CONTROL_BUTTON = "skippr:flex skippr:size-12 skippr:items-center skippr:justify-center skippr:rounded-[14px] skippr:cursor-pointer skippr:transition-all skippr:hover:-translate-y-0.5 skippr:active:translate-y-0";
1963
2091
  var CONTROL_SHADOW = "skippr:shadow-[0_4px_16px_rgba(0,0,0,0.15),0_2px_4px_rgba(0,0,0,0.1)]";
1964
2092
  function ConnectedLauncher() {
@@ -1969,7 +2097,7 @@ function ConnectedLauncher() {
1969
2097
  setSidebarTab("chat");
1970
2098
  expandPanel();
1971
2099
  };
1972
- return /* @__PURE__ */ jsxs4(Fragment3, {
2100
+ return /* @__PURE__ */ jsxs4(Fragment2, {
1973
2101
  children: [
1974
2102
  /* @__PURE__ */ jsx4("button", {
1975
2103
  type: "button",
@@ -2031,7 +2159,7 @@ function WelcomeBubble({
2031
2159
  position,
2032
2160
  onDismiss
2033
2161
  }) {
2034
- useEffect6(() => {
2162
+ useEffect7(() => {
2035
2163
  const timer = setTimeout(onDismiss, 5000);
2036
2164
  return () => clearTimeout(timer);
2037
2165
  }, [onDismiss]);
@@ -2054,14 +2182,14 @@ function MinimizedBubble({
2054
2182
  onDismissWelcome
2055
2183
  }) {
2056
2184
  const { isConnected, isStarting, position } = useLiveAgent();
2057
- const inSession = isConnected || isStarting;
2058
- return /* @__PURE__ */ jsxs4(Fragment3, {
2185
+ const inSession = isConnected;
2186
+ return /* @__PURE__ */ jsxs4(Fragment2, {
2059
2187
  children: [
2060
2188
  /* @__PURE__ */ jsx4(LauncherStatusPill, {}),
2061
2189
  /* @__PURE__ */ jsxs4("div", {
2062
2190
  className: cn("skippr:fixed skippr:bottom-6 skippr:z-[9999]", "skippr:flex skippr:items-center skippr:gap-2", position === "right" ? "skippr:right-6" : "skippr:left-6"),
2063
2191
  children: [
2064
- welcomeMessage && !inSession && !welcomeDismissed && /* @__PURE__ */ jsx4(WelcomeBubble, {
2192
+ welcomeMessage && !isConnected && !isStarting && !welcomeDismissed && /* @__PURE__ */ jsx4(WelcomeBubble, {
2065
2193
  message: welcomeMessage,
2066
2194
  position,
2067
2195
  onDismiss: onDismissWelcome
@@ -2074,7 +2202,7 @@ function MinimizedBubble({
2074
2202
  }
2075
2203
 
2076
2204
  // src/components/Sidebar.tsx
2077
- import { useEffect as useEffect13 } from "react";
2205
+ import { useEffect as useEffect15 } from "react";
2078
2206
 
2079
2207
  // src/hooks/useCombinedMessages.ts
2080
2208
  import { useMemo as useMemo4 } from "react";
@@ -2156,15 +2284,15 @@ function useCombinedMessages() {
2156
2284
  }
2157
2285
 
2158
2286
  // src/hooks/usePhaseUpdates.ts
2159
- import { useCallback as useCallback5 } from "react";
2287
+ import { useCallback as useCallback6 } from "react";
2160
2288
 
2161
2289
  // src/hooks/useAgentState.ts
2162
2290
  import { useRemoteParticipants } from "@livekit/components-react/hooks";
2163
- import { useEffect as useEffect7, useState as useState4 } from "react";
2291
+ import { useEffect as useEffect8, useState as useState5 } from "react";
2164
2292
  function useAgentState(attributeKey, parse, initial) {
2165
- const [value, setValue] = useState4(initial);
2293
+ const [value, setValue] = useState5(initial);
2166
2294
  const remoteParticipants = useRemoteParticipants();
2167
- useEffect7(() => {
2295
+ useEffect8(() => {
2168
2296
  const agentParticipant = remoteParticipants.find((p) => p.attributes?.[attributeKey]);
2169
2297
  if (agentParticipant) {
2170
2298
  const attr = agentParticipant.attributes?.[attributeKey];
@@ -2210,13 +2338,13 @@ function parsePhases(json) {
2210
2338
  return null;
2211
2339
  }
2212
2340
  function usePhaseUpdates() {
2213
- const parse = useCallback5(parsePhases, []);
2341
+ const parse = useCallback6(parsePhases, []);
2214
2342
  const phases = useAgentState("phases", parse, []);
2215
2343
  return { phases };
2216
2344
  }
2217
2345
 
2218
2346
  // src/hooks/useSessionRemaining.ts
2219
- import { useEffect as useEffect8, useRef as useRef4, useState as useState5 } from "react";
2347
+ import { useEffect as useEffect9, useRef as useRef4, useState as useState6 } from "react";
2220
2348
 
2221
2349
  // src/lib/format.ts
2222
2350
  function formatTime(seconds) {
@@ -2233,8 +2361,8 @@ function parseNumber(s) {
2233
2361
  function useSessionRemaining() {
2234
2362
  const maxCallDuration = useAgentState("maxCallDuration", parseNumber, null);
2235
2363
  const endTimeRef = useRef4(null);
2236
- const [remaining, setRemaining] = useState5(null);
2237
- useEffect8(() => {
2364
+ const [remaining, setRemaining] = useState6(null);
2365
+ useEffect9(() => {
2238
2366
  if (maxCallDuration === null || endTimeRef.current !== null)
2239
2367
  return;
2240
2368
  const endTime = Date.now() + maxCallDuration * 1000;
@@ -2251,10 +2379,10 @@ function useSessionRemaining() {
2251
2379
  }
2252
2380
 
2253
2381
  // src/hooks/useElapsedSeconds.ts
2254
- import { useEffect as useEffect9, useState as useState6 } from "react";
2382
+ import { useEffect as useEffect10, useState as useState7 } from "react";
2255
2383
  function useElapsedSeconds(isRunning) {
2256
- const [elapsed, setElapsed] = useState6(0);
2257
- useEffect9(() => {
2384
+ const [elapsed, setElapsed] = useState7(0);
2385
+ useEffect10(() => {
2258
2386
  if (!isRunning) {
2259
2387
  setElapsed(0);
2260
2388
  return;
@@ -2352,7 +2480,7 @@ function LoadingDots({ label }) {
2352
2480
  }
2353
2481
 
2354
2482
  // src/components/LoginFlow.tsx
2355
- import { useCallback as useCallback6, useEffect as useEffect10, useRef as useRef5, useState as useState7 } from "react";
2483
+ import { useCallback as useCallback7, useEffect as useEffect11, useRef as useRef5, useState as useState8 } from "react";
2356
2484
 
2357
2485
  // src/components/ui/button.tsx
2358
2486
  import { forwardRef as forwardRef3 } from "react";
@@ -2388,20 +2516,20 @@ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2388
2516
  var OTP_LENGTH = 6;
2389
2517
  var DIGIT_KEYS = ["d0", "d1", "d2", "d3", "d4", "d5"];
2390
2518
  function LoginFlow({ requestOtp, verifyOtp, error, isSubmitting }) {
2391
- const [step, setStep] = useState7("email");
2392
- const [email, setEmail] = useState7("");
2393
- const handleRequestOtp = useCallback6(async (emailValue) => {
2519
+ const [step, setStep] = useState8("email");
2520
+ const [email, setEmail] = useState8("");
2521
+ const handleRequestOtp = useCallback7(async (emailValue) => {
2394
2522
  const success = await requestOtp(emailValue);
2395
2523
  if (success)
2396
2524
  setStep("otp");
2397
2525
  }, [requestOtp]);
2398
- const handleVerifyOtp = useCallback6(async (code) => {
2526
+ const handleVerifyOtp = useCallback7(async (code) => {
2399
2527
  await verifyOtp(email, code);
2400
2528
  }, [verifyOtp, email]);
2401
- const handleBack = useCallback6(() => {
2529
+ const handleBack = useCallback7(() => {
2402
2530
  setStep("email");
2403
2531
  }, []);
2404
- const handleResend = useCallback6(async () => {
2532
+ const handleResend = useCallback7(async () => {
2405
2533
  await requestOtp(email);
2406
2534
  }, [requestOtp, email]);
2407
2535
  if (step === "otp") {
@@ -2478,30 +2606,30 @@ function EmailStep({ email, onEmailChange, onSubmit, error, isSubmitting }) {
2478
2606
  });
2479
2607
  }
2480
2608
  function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
2481
- const [digits, setDigits] = useState7(Array(OTP_LENGTH).fill(""));
2482
- const [resendCooldown, setResendCooldown] = useState7(0);
2609
+ const [digits, setDigits] = useState8(Array(OTP_LENGTH).fill(""));
2610
+ const [resendCooldown, setResendCooldown] = useState8(0);
2483
2611
  const inputRefs = useRef5([]);
2484
2612
  const submittedRef = useRef5(false);
2485
- useEffect10(() => {
2613
+ useEffect11(() => {
2486
2614
  inputRefs.current[0]?.focus();
2487
2615
  }, []);
2488
- useEffect10(() => {
2616
+ useEffect11(() => {
2489
2617
  if (error)
2490
2618
  submittedRef.current = false;
2491
2619
  }, [error]);
2492
- useEffect10(() => {
2620
+ useEffect11(() => {
2493
2621
  if (resendCooldown <= 0)
2494
2622
  return;
2495
2623
  const timer = setTimeout(() => setResendCooldown((c) => c - 1), 1000);
2496
2624
  return () => clearTimeout(timer);
2497
2625
  }, [resendCooldown]);
2498
- const submitCode = useCallback6((code) => {
2626
+ const submitCode = useCallback7((code) => {
2499
2627
  if (submittedRef.current || isSubmitting)
2500
2628
  return;
2501
2629
  submittedRef.current = true;
2502
2630
  onSubmit(code);
2503
2631
  }, [onSubmit, isSubmitting]);
2504
- const handleDigitChange = useCallback6((index2, value) => {
2632
+ const handleDigitChange = useCallback7((index2, value) => {
2505
2633
  const digit = value.replace(/\D/g, "").slice(-1);
2506
2634
  const newDigits = [...digits];
2507
2635
  newDigits[index2] = digit;
@@ -2515,12 +2643,12 @@ function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
2515
2643
  submitCode(code);
2516
2644
  }
2517
2645
  }, [digits, submitCode]);
2518
- const handleKeyDown = useCallback6((index2, e) => {
2646
+ const handleKeyDown = useCallback7((index2, e) => {
2519
2647
  if (e.key === "Backspace" && !digits[index2] && index2 > 0) {
2520
2648
  inputRefs.current[index2 - 1]?.focus();
2521
2649
  }
2522
2650
  }, [digits]);
2523
- const handlePaste = useCallback6((e) => {
2651
+ const handlePaste = useCallback7((e) => {
2524
2652
  e.preventDefault();
2525
2653
  const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, OTP_LENGTH);
2526
2654
  if (pasted.length > 0) {
@@ -2682,17 +2810,17 @@ function MeetingControls({ onHangUp, showScreenShareToggle = true }) {
2682
2810
  }
2683
2811
 
2684
2812
  // src/components/MessageList.tsx
2685
- import { useEffect as useEffect12, useRef as useRef7 } from "react";
2813
+ import { useEffect as useEffect13, useRef as useRef7 } from "react";
2686
2814
 
2687
2815
  // src/components/ChatInput.tsx
2688
- import { useEffect as useEffect11, useRef as useRef6, useState as useState8 } from "react";
2816
+ import { useEffect as useEffect12, useRef as useRef6, useState as useState9 } from "react";
2689
2817
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2690
2818
  var MAX_INPUT_HEIGHT = 60;
2691
2819
  function ChatInput({ sendChatMessage, isSendingChat, autoFocus = false }) {
2692
- const [inputText, setInputText] = useState8("");
2820
+ const [inputText, setInputText] = useState9("");
2693
2821
  const textareaRef = useRef6(null);
2694
2822
  const canSend = inputText.trim().length > 0 && !isSendingChat;
2695
- useEffect11(() => {
2823
+ useEffect12(() => {
2696
2824
  if (autoFocus)
2697
2825
  textareaRef.current?.focus();
2698
2826
  }, [autoFocus]);
@@ -2839,7 +2967,7 @@ function MessageList({
2839
2967
  }) {
2840
2968
  const scrollRef = useRef7(null);
2841
2969
  const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
2842
- useEffect12(() => {
2970
+ useEffect13(() => {
2843
2971
  scrollRef.current?.scrollIntoView({ behavior: "smooth" });
2844
2972
  }, [messages.length, lastMessage?.content]);
2845
2973
  const showTyping = isStreaming && lastMessage?.role === "assistant" && lastMessage.content === "";
@@ -2870,50 +2998,205 @@ function MessageList({
2870
2998
  });
2871
2999
  }
2872
3000
 
2873
- // src/components/SessionAgenda.tsx
3001
+ // src/components/ModuleSelector.tsx
3002
+ import { useEffect as useEffect14, useRef as useRef8, useState as useState10 } from "react";
2874
3003
  import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
3004
+ var AGENT_TYPE_ICONS = {
3005
+ onboarding: UserPlus,
3006
+ demo: Rocket,
3007
+ training: GraduationCap,
3008
+ support: Headset
3009
+ };
3010
+ function getAgentIcon(type) {
3011
+ return AGENT_TYPE_ICONS[type] ?? Bot;
3012
+ }
3013
+ function ModuleSelector() {
3014
+ const {
3015
+ availableModules,
3016
+ isLoadingModules,
3017
+ modulesError,
3018
+ refetchModules,
3019
+ selectModule,
3020
+ isStarting,
3021
+ isDisconnecting,
3022
+ error
3023
+ } = useLiveAgent();
3024
+ const isBusy = isStarting || isDisconnecting;
3025
+ const scrollRef = useRef8(null);
3026
+ const [showScrollHint, setShowScrollHint] = useState10(false);
3027
+ const [isScrolled, setIsScrolled] = useState10(false);
3028
+ useEffect14(() => {
3029
+ const el = scrollRef.current;
3030
+ if (!el)
3031
+ return;
3032
+ function update() {
3033
+ if (!el)
3034
+ return;
3035
+ const overflows = el.scrollHeight > el.clientHeight + 1;
3036
+ const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 4;
3037
+ setShowScrollHint(overflows && !atBottom);
3038
+ setIsScrolled(el.scrollTop > 0);
3039
+ }
3040
+ update();
3041
+ el.addEventListener("scroll", update, { passive: true });
3042
+ const observer = new ResizeObserver(update);
3043
+ observer.observe(el);
3044
+ return () => {
3045
+ el.removeEventListener("scroll", update);
3046
+ observer.disconnect();
3047
+ };
3048
+ }, []);
3049
+ if (isLoadingModules && availableModules.length === 0) {
3050
+ return /* @__PURE__ */ jsx14("div", {
3051
+ className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center skippr:p-6",
3052
+ children: /* @__PURE__ */ jsx14(LoadingDots, {
3053
+ label: "Loading modules..."
3054
+ })
3055
+ });
3056
+ }
3057
+ if (modulesError) {
3058
+ return /* @__PURE__ */ jsxs13("div", {
3059
+ className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:items-center skippr:justify-center skippr:gap-3 skippr:p-6 skippr:text-center",
3060
+ children: [
3061
+ /* @__PURE__ */ jsx14("p", {
3062
+ className: "skippr:text-sm skippr:text-foreground",
3063
+ children: "Couldn't load modules."
3064
+ }),
3065
+ /* @__PURE__ */ jsx14("p", {
3066
+ className: "skippr:text-xs skippr:text-muted-foreground",
3067
+ children: modulesError
3068
+ }),
3069
+ /* @__PURE__ */ jsx14("button", {
3070
+ type: "button",
3071
+ onClick: () => void refetchModules(),
3072
+ className: "skippr:cursor-pointer skippr:rounded-lg skippr:bg-foreground skippr:px-3 skippr:py-1.5 skippr:text-xs skippr:font-medium skippr:text-background",
3073
+ children: "Try again"
3074
+ })
3075
+ ]
3076
+ });
3077
+ }
3078
+ if (availableModules.length === 0) {
3079
+ return /* @__PURE__ */ jsx14("div", {
3080
+ className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center skippr:p-6 skippr:text-center",
3081
+ children: /* @__PURE__ */ jsx14("p", {
3082
+ className: "skippr:text-sm skippr:text-muted-foreground",
3083
+ children: "No experts available."
3084
+ })
3085
+ });
3086
+ }
3087
+ return /* @__PURE__ */ jsxs13("div", {
3088
+ className: "skippr:relative skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col",
3089
+ children: [
3090
+ /* @__PURE__ */ jsxs13("div", {
3091
+ ref: scrollRef,
3092
+ className: "skippr-no-scrollbar skippr:flex-1 skippr:overflow-y-auto",
3093
+ children: [
3094
+ /* @__PURE__ */ jsx14("h3", {
3095
+ className: `skippr:sticky skippr:top-0 skippr:z-10 skippr:bg-background skippr:px-4 skippr:py-3 skippr:text-xs skippr:font-medium skippr:uppercase skippr:tracking-wider skippr:text-muted-foreground skippr:transition-shadow ${isScrolled ? "skippr:border-b skippr:border-border" : ""}`,
3096
+ children: "Choose an agent"
3097
+ }),
3098
+ /* @__PURE__ */ jsxs13("div", {
3099
+ className: "skippr:px-4 skippr:pt-3 skippr:pb-5",
3100
+ children: [
3101
+ error && /* @__PURE__ */ jsx14("div", {
3102
+ role: "alert",
3103
+ className: "skippr:mb-3 skippr:rounded-lg skippr:border skippr:border-destructive/20 skippr:bg-destructive/10 skippr:px-3 skippr:py-2 skippr:text-xs skippr:text-destructive",
3104
+ children: error
3105
+ }),
3106
+ /* @__PURE__ */ jsx14("div", {
3107
+ className: "skippr:grid skippr:grid-cols-2 skippr:gap-2.5",
3108
+ children: availableModules.map((module, index2) => {
3109
+ const isFeatured = index2 === 0;
3110
+ const isWide = index2 === availableModules.length - 1 && availableModules.length % 2 === 1;
3111
+ const Icon2 = getAgentIcon(module.type);
3112
+ const base = "skippr:group skippr:flex skippr:cursor-pointer skippr:gap-3 skippr:rounded-xl skippr:text-left skippr:transition-colors skippr:disabled:cursor-not-allowed skippr:disabled:opacity-50";
3113
+ const layout = isWide ? "skippr:items-center skippr:p-3.5 skippr:pb-5" : "skippr:flex-col skippr:items-start skippr:p-3.5";
3114
+ const variant = isFeatured ? "skippr:bg-primary skippr:text-primary-foreground skippr:hover:bg-primary/90" : "skippr:bg-background skippr:ring-1 skippr:ring-foreground/10 skippr:hover:bg-muted/50";
3115
+ const span = isWide ? "skippr:col-span-2" : "";
3116
+ return /* @__PURE__ */ jsxs13("button", {
3117
+ type: "button",
3118
+ disabled: isBusy,
3119
+ onClick: () => selectModule(module.id),
3120
+ className: `${base} ${layout} ${variant} ${span}`,
3121
+ children: [
3122
+ /* @__PURE__ */ jsx14("div", {
3123
+ className: isFeatured ? "skippr:flex skippr:h-9 skippr:w-9 skippr:shrink-0 skippr:items-center skippr:justify-center skippr:rounded-lg skippr:bg-primary-foreground/15" : "skippr:flex skippr:h-9 skippr:w-9 skippr:shrink-0 skippr:items-center skippr:justify-center skippr:rounded-lg skippr:bg-muted",
3124
+ children: /* @__PURE__ */ jsx14(Icon2, {
3125
+ className: isFeatured ? "skippr:h-4 skippr:w-4" : "skippr:h-4 skippr:w-4 skippr:text-muted-foreground"
3126
+ })
3127
+ }),
3128
+ /* @__PURE__ */ jsxs13("div", {
3129
+ className: "skippr:min-w-0 skippr:w-full skippr:space-y-0.5",
3130
+ children: [
3131
+ /* @__PURE__ */ jsx14("p", {
3132
+ className: "skippr:line-clamp-1 skippr:text-sm skippr:font-semibold",
3133
+ children: module.name
3134
+ }),
3135
+ module.description && /* @__PURE__ */ jsx14("p", {
3136
+ className: isFeatured ? "skippr:line-clamp-2 skippr:text-[11px] skippr:leading-snug skippr:text-primary-foreground/70" : "skippr:line-clamp-2 skippr:text-[11px] skippr:leading-snug skippr:text-muted-foreground",
3137
+ children: module.description
3138
+ })
3139
+ ]
3140
+ })
3141
+ ]
3142
+ }, module.id);
3143
+ })
3144
+ })
3145
+ ]
3146
+ })
3147
+ ]
3148
+ }),
3149
+ showScrollHint && /* @__PURE__ */ jsx14("div", {
3150
+ className: "skippr:pointer-events-none skippr:absolute skippr:bottom-0 skippr:left-0 skippr:right-0 skippr:h-12 skippr:bg-gradient-to-t skippr:from-background skippr:to-transparent"
3151
+ })
3152
+ ]
3153
+ });
3154
+ }
3155
+
3156
+ // src/components/SessionAgenda.tsx
3157
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2875
3158
  function SessionAgenda({ phases, hasStarted }) {
2876
3159
  if (phases.length === 0 || !hasStarted) {
2877
- return /* @__PURE__ */ jsx14("div", {
3160
+ return /* @__PURE__ */ jsx15("div", {
2878
3161
  className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
2879
- children: /* @__PURE__ */ jsx14(LoadingDots, {
3162
+ children: /* @__PURE__ */ jsx15(LoadingDots, {
2880
3163
  label: "Waiting for agenda to load..."
2881
3164
  })
2882
3165
  });
2883
3166
  }
2884
- return /* @__PURE__ */ jsx14("div", {
3167
+ return /* @__PURE__ */ jsx15("div", {
2885
3168
  className: "skippr:flex-1 skippr:overflow-y-auto skippr:px-4 skippr:py-4",
2886
- children: /* @__PURE__ */ jsx14("div", {
3169
+ children: /* @__PURE__ */ jsx15("div", {
2887
3170
  className: "skippr:space-y-1",
2888
3171
  children: phases.map((phase) => {
2889
3172
  const isActive = phase.status === "active";
2890
3173
  const isCompleted = phase.status === "completed";
2891
- return /* @__PURE__ */ jsxs13("div", {
3174
+ return /* @__PURE__ */ jsxs14("div", {
2892
3175
  className: cn("skippr:flex skippr:items-start skippr:gap-2.5 skippr:rounded-lg skippr:p-2 skippr:transition-colors", isActive && "skippr:bg-primary/10"),
2893
3176
  children: [
2894
- /* @__PURE__ */ jsx14("div", {
3177
+ /* @__PURE__ */ jsx15("div", {
2895
3178
  className: "skippr:mt-0.5",
2896
- children: isCompleted ? /* @__PURE__ */ jsx14(CircleCheck, {
3179
+ children: isCompleted ? /* @__PURE__ */ jsx15(CircleCheck, {
2897
3180
  className: "skippr:size-4 skippr:text-chart-3"
2898
- }) : isActive ? /* @__PURE__ */ jsx14(Circle, {
3181
+ }) : isActive ? /* @__PURE__ */ jsx15(Circle, {
2899
3182
  className: "skippr:size-4 skippr:fill-primary/30 skippr:text-primary"
2900
- }) : /* @__PURE__ */ jsx14(Circle, {
3183
+ }) : /* @__PURE__ */ jsx15(Circle, {
2901
3184
  className: "skippr:size-4 skippr:text-muted-foreground/30"
2902
3185
  })
2903
3186
  }),
2904
- /* @__PURE__ */ jsxs13("div", {
3187
+ /* @__PURE__ */ jsxs14("div", {
2905
3188
  className: "skippr:min-w-0 skippr:flex-1",
2906
3189
  children: [
2907
- /* @__PURE__ */ jsx14("p", {
3190
+ /* @__PURE__ */ jsx15("p", {
2908
3191
  className: cn("skippr:text-sm", isCompleted && "skippr:text-muted-foreground skippr:line-through", isActive && "skippr:font-medium skippr:text-foreground", phase.status === "pending" && "skippr:text-muted-foreground"),
2909
3192
  children: phase.name
2910
3193
  }),
2911
- phase.highlights.length > 0 && /* @__PURE__ */ jsx14("ul", {
3194
+ phase.highlights.length > 0 && /* @__PURE__ */ jsx15("ul", {
2912
3195
  className: "skippr:mt-1 skippr:space-y-0.5",
2913
- children: phase.highlights.map((text) => /* @__PURE__ */ jsxs13("li", {
3196
+ children: phase.highlights.map((text) => /* @__PURE__ */ jsxs14("li", {
2914
3197
  className: cn("skippr:flex skippr:items-center skippr:gap-1.5 skippr:text-[11px] skippr:leading-tight", isCompleted ? "skippr:text-muted-foreground/40 skippr:line-through" : "skippr:text-muted-foreground/70"),
2915
3198
  children: [
2916
- /* @__PURE__ */ jsx14("span", {
3199
+ /* @__PURE__ */ jsx15("span", {
2917
3200
  className: "skippr:size-1 skippr:shrink-0 skippr:rounded-full skippr:bg-current"
2918
3201
  }),
2919
3202
  text
@@ -2930,12 +3213,12 @@ function SessionAgenda({ phases, hasStarted }) {
2930
3213
  }
2931
3214
 
2932
3215
  // src/components/SessionWarningBanner.tsx
2933
- import { jsx as jsx15 } from "react/jsx-runtime";
3216
+ import { jsx as jsx16 } from "react/jsx-runtime";
2934
3217
  var SESSION_WARNING_THRESHOLD_SECS = 60;
2935
3218
  function SessionWarningBanner({ remaining }) {
2936
3219
  if (remaining === null || remaining <= 0 || remaining > SESSION_WARNING_THRESHOLD_SECS)
2937
3220
  return null;
2938
- return /* @__PURE__ */ jsx15("div", {
3221
+ return /* @__PURE__ */ jsx16("div", {
2939
3222
  "data-testid": "session-warning-banner",
2940
3223
  className: "skippr:bg-red-50 skippr:px-4 skippr:py-1.5 skippr:text-center skippr:text-xs skippr:font-medium skippr:text-red-700",
2941
3224
  children: "Session ending soon"
@@ -2943,24 +3226,30 @@ function SessionWarningBanner({ remaining }) {
2943
3226
  }
2944
3227
 
2945
3228
  // src/components/StartSessionPrompt.tsx
2946
- import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
3229
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2947
3230
  function StartSessionPrompt({
2948
3231
  onStartSession,
3232
+ agentId,
3233
+ agentControls,
2949
3234
  isStarting,
2950
3235
  error,
2951
3236
  label = "Talk to Skippr"
2952
3237
  }) {
2953
- return /* @__PURE__ */ jsxs14("div", {
3238
+ return /* @__PURE__ */ jsxs15("div", {
2954
3239
  className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:items-center skippr:justify-center skippr:gap-3 skippr:px-4",
2955
3240
  children: [
2956
- /* @__PURE__ */ jsx16("button", {
3241
+ /* @__PURE__ */ jsx17("button", {
2957
3242
  type: "button",
2958
- onClick: onStartSession,
2959
- disabled: isStarting,
3243
+ onClick: () => {
3244
+ if (!agentId)
3245
+ return;
3246
+ onStartSession(agentControls ? { agentId, agentControls } : { agentId });
3247
+ },
3248
+ disabled: isStarting || !agentId,
2960
3249
  className: "skippr:cursor-pointer skippr:rounded-xl skippr:bg-primary skippr:px-8 skippr:py-3 skippr:text-sm skippr:font-medium skippr:text-primary-foreground skippr:transition-all skippr:hover:bg-primary/90 skippr:disabled:cursor-not-allowed skippr:disabled:opacity-60",
2961
3250
  children: isStarting ? "Starting..." : label
2962
3251
  }),
2963
- error && /* @__PURE__ */ jsx16("p", {
3252
+ error && /* @__PURE__ */ jsx17("p", {
2964
3253
  className: "skippr:text-xs skippr:text-destructive",
2965
3254
  children: error
2966
3255
  })
@@ -2969,7 +3258,7 @@ function StartSessionPrompt({
2969
3258
  }
2970
3259
 
2971
3260
  // src/components/Sidebar.tsx
2972
- import { jsx as jsx17, jsxs as jsxs15, Fragment as Fragment4 } from "react/jsx-runtime";
3261
+ import { jsx as jsx18, jsxs as jsxs16, Fragment as Fragment3 } from "react/jsx-runtime";
2973
3262
  function Sidebar({
2974
3263
  hideControls = false,
2975
3264
  hideHeader = false,
@@ -2993,11 +3282,14 @@ function Sidebar({
2993
3282
  sidebarTab: activeTab,
2994
3283
  setSidebarTab: setActiveTab,
2995
3284
  autoFocusChat,
2996
- captureMode
3285
+ captureMode,
3286
+ hasModuleSelector,
3287
+ agentId,
3288
+ agentControls
2997
3289
  } = useLiveAgent();
2998
3290
  const isFloating = variant === "floating";
2999
3291
  const isSidebar = variant === "sidebar";
3000
- useEffect13(() => {
3292
+ useEffect15(() => {
3001
3293
  if (!isSidebar)
3002
3294
  return;
3003
3295
  const prop = position === "right" ? "marginRight" : "marginLeft";
@@ -3011,22 +3303,22 @@ function Sidebar({
3011
3303
  document.body.style.transition = "";
3012
3304
  };
3013
3305
  }, [isSidebar, isPanelOpen, position]);
3014
- return /* @__PURE__ */ jsxs15("div", {
3306
+ return /* @__PURE__ */ jsxs16("div", {
3015
3307
  className: cn("skippr:fixed skippr:z-[9999]", "skippr:bg-card", "skippr:flex skippr:flex-col", "skippr:overflow-hidden", isFloating && "skippr:border skippr:border-border skippr:bottom-[88px] skippr:h-[min(460px,calc(100vh-200px))] skippr:rounded-2xl skippr:shadow-[0_8px_30px_rgba(0,0,0,0.16),0_4px_12px_rgba(0,0,0,0.08)]", isFloating && (position === "right" ? "skippr:right-6" : "skippr:left-6"), isFloating && "skippr:transition-[opacity,transform] skippr:duration-300 skippr:ease-in-out", isFloating && (position === "right" ? "skippr:origin-bottom-right" : "skippr:origin-bottom-left"), isFloating && !isPanelOpen && "skippr:scale-0 skippr:opacity-0 skippr:pointer-events-none", isFloating && isPanelOpen && "skippr:scale-100 skippr:opacity-100", isSidebar && "skippr:top-0 skippr:h-full", isSidebar && "skippr:transition-[width] skippr:duration-300 skippr:ease-in-out", isSidebar && position === "right" && "skippr:right-0 skippr:border-l skippr:border-l-border", isSidebar && position === "left" && "skippr:left-0 skippr:border-r skippr:border-r-border", isSidebar && !isPanelOpen && "skippr:w-0 skippr:border-0"),
3016
3308
  style: { width: isPanelOpen ? SIDEBAR_WIDTH : undefined },
3017
3309
  children: [
3018
- !hideHeader && /* @__PURE__ */ jsx17(ChatHeader, {}),
3019
- !isAuthenticated && isValidating ? /* @__PURE__ */ jsx17("div", {
3310
+ !hideHeader && /* @__PURE__ */ jsx18(ChatHeader, {}),
3311
+ !isAuthenticated && isValidating ? /* @__PURE__ */ jsx18("div", {
3020
3312
  className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
3021
- children: /* @__PURE__ */ jsx17(LoadingDots, {
3313
+ children: /* @__PURE__ */ jsx18(LoadingDots, {
3022
3314
  label: "Loading..."
3023
3315
  })
3024
- }) : !isAuthenticated ? /* @__PURE__ */ jsx17(LoginFlow, {
3316
+ }) : !isAuthenticated ? /* @__PURE__ */ jsx18(LoginFlow, {
3025
3317
  requestOtp,
3026
3318
  verifyOtp,
3027
3319
  error: authError,
3028
3320
  isSubmitting: isAuthSubmitting
3029
- }) : /* @__PURE__ */ jsx17(AuthenticatedContent, {
3321
+ }) : /* @__PURE__ */ jsx18(AuthenticatedContent, {
3030
3322
  isConnected,
3031
3323
  onStartSession: startSession,
3032
3324
  onDisconnect: disconnect,
@@ -3037,7 +3329,10 @@ function Sidebar({
3037
3329
  hideControls,
3038
3330
  startSessionLabel,
3039
3331
  autoFocusChat,
3040
- showScreenShareToggle: captureMode === "screenshare"
3332
+ showScreenShareToggle: captureMode === "screenshare",
3333
+ hasModuleSelector,
3334
+ agentId,
3335
+ agentControls
3041
3336
  })
3042
3337
  ]
3043
3338
  });
@@ -3053,60 +3348,67 @@ function AuthenticatedContent({
3053
3348
  hideControls,
3054
3349
  startSessionLabel,
3055
3350
  autoFocusChat,
3056
- showScreenShareToggle
3351
+ showScreenShareToggle,
3352
+ hasModuleSelector,
3353
+ agentId,
3354
+ agentControls
3057
3355
  }) {
3058
- return /* @__PURE__ */ jsxs15(Fragment4, {
3356
+ const showSelectorAsPrompt = hasModuleSelector && !isConnected && !isStarting;
3357
+ const showTabBar = !showSelectorAsPrompt;
3358
+ return /* @__PURE__ */ jsxs16(Fragment3, {
3059
3359
  children: [
3060
- isConnected && /* @__PURE__ */ jsx17(ConnectedBanner, {}),
3061
- /* @__PURE__ */ jsxs15("div", {
3062
- className: "skippr:flex skippr:gap-2 skippr:border-b skippr:border-border skippr:px-3 skippr:py-2",
3360
+ isConnected && /* @__PURE__ */ jsx18(ConnectedBanner, {}),
3361
+ showTabBar && /* @__PURE__ */ jsxs16("div", {
3362
+ className: "skippr:flex skippr:items-center skippr:gap-2 skippr:border-b skippr:border-border skippr:px-3 skippr:py-2",
3063
3363
  children: [
3064
- /* @__PURE__ */ jsxs15("button", {
3364
+ /* @__PURE__ */ jsxs16("button", {
3065
3365
  type: "button",
3066
3366
  className: cn("skippr:relative skippr:inline-flex skippr:cursor-pointer skippr:items-center skippr:gap-1.5 skippr:rounded-lg skippr:px-3 skippr:py-2 skippr:text-sm skippr:font-medium skippr:transition-all", activeTab === "chat" ? "skippr:text-foreground" : "skippr:text-muted-foreground skippr:hover:text-foreground"),
3067
3367
  onClick: () => onTabChange("chat"),
3068
3368
  children: [
3069
- /* @__PURE__ */ jsx17(MessageCircle, {
3369
+ /* @__PURE__ */ jsx18(MessageCircle, {
3070
3370
  className: "skippr:size-3.5"
3071
3371
  }),
3072
3372
  "Chat",
3073
- activeTab === "chat" && /* @__PURE__ */ jsx17("span", {
3373
+ activeTab === "chat" && /* @__PURE__ */ jsx18("span", {
3074
3374
  className: "skippr:absolute skippr:-bottom-2 skippr:left-3 skippr:right-3 skippr:h-0.5 skippr:rounded-full skippr:bg-foreground"
3075
3375
  })
3076
3376
  ]
3077
3377
  }),
3078
- /* @__PURE__ */ jsxs15("button", {
3378
+ /* @__PURE__ */ jsxs16("button", {
3079
3379
  type: "button",
3080
3380
  className: cn("skippr:relative skippr:inline-flex skippr:cursor-pointer skippr:items-center skippr:gap-1.5 skippr:rounded-lg skippr:px-3 skippr:py-2 skippr:text-sm skippr:font-medium skippr:transition-all", activeTab === "agenda" ? "skippr:text-foreground" : "skippr:text-muted-foreground skippr:hover:text-foreground"),
3081
3381
  onClick: () => onTabChange("agenda"),
3082
3382
  children: [
3083
- /* @__PURE__ */ jsx17(Calendar, {
3383
+ /* @__PURE__ */ jsx18(Calendar, {
3084
3384
  className: "skippr:size-3.5"
3085
3385
  }),
3086
3386
  "Agenda",
3087
- activeTab === "agenda" && /* @__PURE__ */ jsx17("span", {
3387
+ activeTab === "agenda" && /* @__PURE__ */ jsx18("span", {
3088
3388
  className: "skippr:absolute skippr:-bottom-2 skippr:left-3 skippr:right-3 skippr:h-0.5 skippr:rounded-full skippr:bg-foreground"
3089
3389
  })
3090
3390
  ]
3091
3391
  })
3092
3392
  ]
3093
3393
  }),
3094
- /* @__PURE__ */ jsx17("div", {
3394
+ /* @__PURE__ */ jsx18("div", {
3095
3395
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col",
3096
- children: isConnected || isStarting ? /* @__PURE__ */ jsx17(ConnectedBody, {
3396
+ children: showSelectorAsPrompt ? /* @__PURE__ */ jsx18(ModuleSelector, {}) : isConnected || isStarting ? /* @__PURE__ */ jsx18(ConnectedBody, {
3097
3397
  activeTab,
3098
3398
  autoFocusChat
3099
- }) : /* @__PURE__ */ jsx17("div", {
3399
+ }) : /* @__PURE__ */ jsx18("div", {
3100
3400
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col skippr:animate-skippr-tab-fade",
3101
- children: /* @__PURE__ */ jsx17(StartSessionPrompt, {
3401
+ children: /* @__PURE__ */ jsx18(StartSessionPrompt, {
3102
3402
  onStartSession,
3403
+ agentId,
3404
+ agentControls,
3103
3405
  isStarting,
3104
3406
  error,
3105
3407
  label: startSessionLabel
3106
3408
  })
3107
3409
  }, `${activeTab}-empty`)
3108
3410
  }),
3109
- isConnected && !hideControls && /* @__PURE__ */ jsx17(MeetingControls, {
3411
+ isConnected && !hideControls && /* @__PURE__ */ jsx18(MeetingControls, {
3110
3412
  onHangUp: onDisconnect,
3111
3413
  showScreenShareToggle
3112
3414
  })
@@ -3115,7 +3417,7 @@ function AuthenticatedContent({
3115
3417
  }
3116
3418
  function ConnectedBanner() {
3117
3419
  const remaining = useSessionRemaining();
3118
- return /* @__PURE__ */ jsx17(SessionWarningBanner, {
3420
+ return /* @__PURE__ */ jsx18(SessionWarningBanner, {
3119
3421
  remaining
3120
3422
  });
3121
3423
  }
@@ -3126,17 +3428,17 @@ function ConnectedBody({
3126
3428
  const { allMessages, agentState, sendChatMessage, isSendingChat } = useCombinedMessages();
3127
3429
  const { phases } = usePhaseUpdates();
3128
3430
  if (activeTab === "agenda") {
3129
- return /* @__PURE__ */ jsx17("div", {
3431
+ return /* @__PURE__ */ jsx18("div", {
3130
3432
  className: "skippr:min-h-0 skippr:flex-1 skippr:overflow-y-auto skippr:animate-skippr-tab-fade",
3131
- children: /* @__PURE__ */ jsx17(SessionAgenda, {
3433
+ children: /* @__PURE__ */ jsx18(SessionAgenda, {
3132
3434
  phases,
3133
3435
  hasStarted: allMessages.length > 0 || agentState === "speaking"
3134
3436
  })
3135
3437
  }, "agenda");
3136
3438
  }
3137
- return /* @__PURE__ */ jsx17("div", {
3439
+ return /* @__PURE__ */ jsx18("div", {
3138
3440
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col skippr:animate-skippr-tab-fade",
3139
- children: /* @__PURE__ */ jsx17(MessageList, {
3441
+ children: /* @__PURE__ */ jsx18(MessageList, {
3140
3442
  messages: allMessages,
3141
3443
  isStreaming: agentState === "speaking",
3142
3444
  sendChatMessage,
@@ -3147,31 +3449,31 @@ function ConnectedBody({
3147
3449
  }
3148
3450
 
3149
3451
  // src/components/SidebarTrigger.tsx
3150
- import { jsx as jsx18 } from "react/jsx-runtime";
3452
+ import { jsx as jsx19 } from "react/jsx-runtime";
3151
3453
  function SidebarTrigger() {
3152
3454
  const { isPanelOpen, togglePanel, minimizePanel, minimizable, position, isMinimized } = useLiveAgent();
3153
3455
  if (isMinimized)
3154
3456
  return null;
3155
3457
  const handleClick = isPanelOpen && minimizable ? minimizePanel : togglePanel;
3156
- return /* @__PURE__ */ jsx18("button", {
3458
+ return /* @__PURE__ */ jsx19("button", {
3157
3459
  type: "button",
3158
3460
  onClick: handleClick,
3159
3461
  title: isPanelOpen ? "Close chat" : "Open chat",
3160
3462
  "aria-label": isPanelOpen ? "Close chat" : "Open chat",
3161
3463
  className: cn("skippr:fixed skippr:bottom-6 skippr:z-[9998]", "skippr:flex skippr:size-12 skippr:items-center skippr:justify-center", "skippr:rounded-[14px] skippr:bg-bubble skippr:text-white", "skippr:shadow-[0_4px_16px_rgba(45,43,61,0.45),0_2px_4px_rgba(0,0,0,0.1)] skippr:transition-all", "skippr:cursor-pointer skippr:hover:brightness-110 skippr:hover:-translate-y-0.5 skippr:active:translate-y-0", position === "right" ? "skippr:right-6" : "skippr:left-6"),
3162
- children: isPanelOpen ? /* @__PURE__ */ jsx18(ChevronDown, {
3464
+ children: isPanelOpen ? /* @__PURE__ */ jsx19(ChevronDown, {
3163
3465
  className: "skippr:size-5"
3164
- }) : /* @__PURE__ */ jsx18(Logo, {
3466
+ }) : /* @__PURE__ */ jsx19(Logo, {
3165
3467
  className: "skippr:size-7"
3166
3468
  })
3167
3469
  });
3168
3470
  }
3169
3471
 
3170
3472
  // src/components/LiveAgent.tsx
3171
- import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
3473
+ import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
3172
3474
  function LiveAgent(props) {
3173
3475
  const {
3174
- agentId,
3476
+ agentId: hostAgentId,
3175
3477
  authToken: authTokenProp,
3176
3478
  appKey,
3177
3479
  userToken,
@@ -3187,39 +3489,81 @@ function LiveAgent(props) {
3187
3489
  children
3188
3490
  } = props;
3189
3491
  const captureMode = props.captureMode ?? "screenshare";
3190
- let agentControls;
3492
+ let hostAgentControls;
3191
3493
  if ("agentControls" in props && props.agentControls) {
3192
3494
  if (captureMode === "auto") {
3193
- agentControls = props.agentControls;
3495
+ hostAgentControls = props.agentControls;
3194
3496
  } else {
3195
3497
  console.warn('[Skippr] agentControls requires captureMode: "auto"');
3196
3498
  }
3197
3499
  }
3198
3500
  const auth = useAuth({ appKey });
3199
3501
  const effectiveAuthToken = authTokenProp || auth.authToken || undefined;
3502
+ const hasModuleSelector = !hostAgentId;
3503
+ const [activeModule, setActiveModule] = useState11(null);
3504
+ const agentId = hasModuleSelector ? activeModule?.id ?? null : hostAgentId;
3505
+ const agentControls = hasModuleSelector ? activeModule?.controls : hostAgentControls;
3506
+ const minimizeOnSessionStart = useCallback8(() => {
3507
+ if (minimizable) {
3508
+ setIsMinimized(true);
3509
+ setIsPanelOpen(false);
3510
+ }
3511
+ }, [minimizable]);
3512
+ const expandOnSessionStartError = useCallback8(() => {
3513
+ setIsMinimized(false);
3514
+ setIsPanelOpen(true);
3515
+ }, []);
3516
+ const minimizeOnSessionDisconnect = useCallback8(() => {
3517
+ if (hasModuleSelector) {
3518
+ setActiveModule(null);
3519
+ }
3520
+ if (minimizable && !hasModuleSelector) {
3521
+ setIsMinimized(true);
3522
+ setIsPanelOpen(false);
3523
+ }
3524
+ }, [minimizable, hasModuleSelector]);
3200
3525
  const {
3201
3526
  connection,
3202
3527
  shouldConnect,
3203
3528
  isStarting,
3529
+ isDisconnecting,
3204
3530
  error,
3205
3531
  errorCode,
3206
3532
  startSession,
3207
3533
  disconnect,
3208
- pendingScreenStream
3534
+ pendingScreenStream,
3535
+ bearerToken
3209
3536
  } = useSession({
3210
- agentId,
3211
3537
  captureMode,
3212
- agentControls,
3213
3538
  authToken: effectiveAuthToken,
3214
3539
  appKey,
3215
- userToken
3540
+ userToken,
3541
+ onStart: minimizeOnSessionStart,
3542
+ onStartError: expandOnSessionStartError,
3543
+ onDisconnect: minimizeOnSessionDisconnect
3544
+ });
3545
+ const [isPanelOpen, setIsPanelOpen] = useState11(defaultOpen);
3546
+ const [isMinimized, setIsMinimized] = useState11(minimizable && !defaultOpen);
3547
+ const [sidebarTab, setSidebarTab] = useState11("agenda");
3548
+ const {
3549
+ modules: availableModules,
3550
+ isLoading: isLoadingModules,
3551
+ error: modulesError,
3552
+ refetch: refetchModules
3553
+ } = useAvailableModules({
3554
+ enabled: hasModuleSelector && isPanelOpen,
3555
+ bearerToken
3216
3556
  });
3217
- const [isPanelOpen, setIsPanelOpen] = useState9(defaultOpen);
3218
- const [isMinimized, setIsMinimized] = useState9(minimizable && !defaultOpen);
3219
- const [sidebarTab, setSidebarTab] = useState9("agenda");
3220
- const [welcomeDismissed, setWelcomeDismissed] = useState9(false);
3221
- const dismissWelcome = useCallback7(() => setWelcomeDismissed(true), []);
3222
- const [currentPosition, setCurrentPosition] = useState9(() => {
3557
+ const selectModule = useCallback8((moduleId) => {
3558
+ const found = availableModules.find((m) => m.id === moduleId);
3559
+ if (!found)
3560
+ return;
3561
+ setActiveModule(found);
3562
+ startSession({ agentId: found.id, agentControls: found.controls });
3563
+ }, [availableModules, startSession]);
3564
+ const [welcomeDismissed, setWelcomeDismissed] = useState11(false);
3565
+ const dismissWelcome = useCallback8(() => setWelcomeDismissed(true), []);
3566
+ const [currentPosition, setCurrentPosition] = useState11(() => {
3223
3567
  try {
3224
3568
  const saved = localStorage.getItem("skippr_widget_position");
3225
3569
  if (saved === "left" || saved === "right")
@@ -3227,20 +3571,20 @@ function LiveAgent(props) {
3227
3571
  } catch {}
3228
3572
  return position;
3229
3573
  });
3230
- const setPositionWithPersist = useCallback7((pos) => {
3574
+ const setPositionWithPersist = useCallback8((pos) => {
3231
3575
  setCurrentPosition(pos);
3232
3576
  try {
3233
3577
  localStorage.setItem("skippr_widget_position", pos);
3234
3578
  } catch {}
3235
3579
  }, []);
3236
- const openPanel = useCallback7(() => setIsPanelOpen(true), []);
3237
- const closePanel = useCallback7(() => setIsPanelOpen(false), []);
3238
- const togglePanel = useCallback7(() => setIsPanelOpen((prev) => !prev), []);
3239
- const expandPanel = useCallback7(() => {
3580
+ const openPanel = useCallback8(() => setIsPanelOpen(true), []);
3581
+ const closePanel = useCallback8(() => setIsPanelOpen(false), []);
3582
+ const togglePanel = useCallback8(() => setIsPanelOpen((prev) => !prev), []);
3583
+ const expandPanel = useCallback8(() => {
3240
3584
  setIsMinimized(false);
3241
3585
  setIsPanelOpen(true);
3242
3586
  }, []);
3243
- const minimizePanel = useCallback7(() => {
3587
+ const minimizePanel = useCallback8(() => {
3244
3588
  if (!minimizable)
3245
3589
  return;
3246
3590
  setIsMinimized(true);
@@ -3248,20 +3592,12 @@ function LiveAgent(props) {
3248
3592
  }, [minimizable]);
3249
3593
  const isConnected = connection !== null;
3250
3594
  const isAuthenticated = !!userToken || !!authTokenProp || auth.isAuthenticated;
3251
- const prevConnectionRef = useRef8(connection);
3252
- useEffect14(() => {
3253
- const connectionChanged = prevConnectionRef.current !== connection;
3254
- prevConnectionRef.current = connection;
3255
- if (connectionChanged && minimizable) {
3256
- setIsMinimized(true);
3257
- setIsPanelOpen(false);
3258
- }
3259
- }, [connection, minimizable]);
3260
3595
  const ctx = useMemo5(() => ({
3261
3596
  connection,
3262
3597
  shouldConnect,
3263
3598
  isConnected,
3264
3599
  isStarting,
3600
+ isDisconnecting,
3265
3601
  error,
3266
3602
  errorCode,
3267
3603
  startSession,
@@ -3288,12 +3624,21 @@ function LiveAgent(props) {
3288
3624
  setSidebarTab,
3289
3625
  autoFocusChat,
3290
3626
  captureMode,
3291
- agentControls
3627
+ agentId,
3628
+ agentControls,
3629
+ hasModuleSelector,
3630
+ availableModules,
3631
+ activeModule,
3632
+ isLoadingModules,
3633
+ modulesError,
3634
+ refetchModules,
3635
+ selectModule
3292
3636
  }), [
3293
3637
  connection,
3294
3638
  shouldConnect,
3295
3639
  isConnected,
3296
3640
  isStarting,
3641
+ isDisconnecting,
3297
3642
  error,
3298
3643
  errorCode,
3299
3644
  startSession,
@@ -3319,33 +3664,41 @@ function LiveAgent(props) {
3319
3664
  sidebarTab,
3320
3665
  autoFocusChat,
3321
3666
  captureMode,
3322
- agentControls
3667
+ agentId,
3668
+ agentControls,
3669
+ hasModuleSelector,
3670
+ availableModules,
3671
+ activeModule,
3672
+ isLoadingModules,
3673
+ modulesError,
3674
+ refetchModules,
3675
+ selectModule
3323
3676
  ]);
3324
- return /* @__PURE__ */ jsx19(LiveAgentContext.Provider, {
3677
+ return /* @__PURE__ */ jsx20(LiveAgentContext.Provider, {
3325
3678
  value: ctx,
3326
- children: /* @__PURE__ */ jsxs16(LiveKitRoom, {
3679
+ children: /* @__PURE__ */ jsxs17(LiveKitRoom, {
3327
3680
  serverUrl: connection?.livekitUrl,
3328
3681
  token: connection?.token,
3329
3682
  connect: shouldConnect,
3330
3683
  audio: true,
3331
3684
  onDisconnected: disconnect,
3332
3685
  children: [
3333
- connection && /* @__PURE__ */ jsx19(RoomAudioRenderer, {}),
3334
- connection && captureMode === "screenshare" && /* @__PURE__ */ jsx19(AutoStartMedia, {
3686
+ connection && /* @__PURE__ */ jsx20(RoomAudioRenderer, {}),
3687
+ connection && captureMode === "screenshare" && /* @__PURE__ */ jsx20(AutoStartMedia, {
3335
3688
  pendingScreenStream
3336
3689
  }),
3337
- connection && captureMode === "auto" && /* @__PURE__ */ jsx19(DomCapture, {}),
3338
- connection && captureMode === "auto" && agentControls?.highlight && /* @__PURE__ */ jsx19(HighlightOverlay, {}),
3339
- /* @__PURE__ */ jsxs16("div", {
3690
+ connection && captureMode === "auto" && /* @__PURE__ */ jsx20(DomCapture, {}),
3691
+ connection && captureMode === "auto" && agentControls?.highlight && /* @__PURE__ */ jsx20(HighlightOverlay, {}),
3692
+ /* @__PURE__ */ jsxs17("div", {
3340
3693
  id: WIDGET_ROOT_ID,
3341
3694
  children: [
3342
- isMinimized && /* @__PURE__ */ jsx19(MinimizedBubble, {
3695
+ isMinimized && /* @__PURE__ */ jsx20(MinimizedBubble, {
3343
3696
  welcomeMessage,
3344
3697
  welcomeDismissed,
3345
3698
  onDismissWelcome: dismissWelcome
3346
3699
  }),
3347
- /* @__PURE__ */ jsx19(SidebarTrigger, {}),
3348
- /* @__PURE__ */ jsx19(Sidebar, {
3700
+ /* @__PURE__ */ jsx20(SidebarTrigger, {}),
3701
+ /* @__PURE__ */ jsx20(Sidebar, {
3349
3702
  hideControls,
3350
3703
  hideHeader,
3351
3704
  startSessionLabel