@skippr/live-agent-sdk 0.33.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)
@@ -1308,28 +1370,63 @@ var __iconNode3 = [
1308
1370
  ["circle", { cx: "4", cy: "20", r: "2", key: "6kqj1y" }]
1309
1371
  ];
1310
1372
  var Sparkles = createLucideIcon("sparkles", __iconNode3);
1311
- // ../../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
1312
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 = [
1313
1385
  ["path", { d: "M8 2v4", key: "1cmpym" }],
1314
1386
  ["path", { d: "M16 2v4", key: "4m81vk" }],
1315
1387
  ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2", key: "1hopcy" }],
1316
1388
  ["path", { d: "M3 10h18", key: "8toen8" }]
1317
1389
  ];
1318
- var Calendar = createLucideIcon("calendar", __iconNode4);
1390
+ var Calendar = createLucideIcon("calendar", __iconNode5);
1319
1391
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/chevron-down.js
1320
- var __iconNode5 = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
1321
- 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);
1322
1394
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/circle.js
1323
- var __iconNode6 = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
1324
- 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);
1325
1422
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mail.js
1326
- var __iconNode7 = [
1423
+ var __iconNode10 = [
1327
1424
  ["path", { d: "m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7", key: "132q7q" }],
1328
1425
  ["rect", { x: "2", y: "4", width: "20", height: "16", rx: "2", key: "izxlao" }]
1329
1426
  ];
1330
- var Mail = createLucideIcon("mail", __iconNode7);
1427
+ var Mail = createLucideIcon("mail", __iconNode10);
1331
1428
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-circle.js
1332
- var __iconNode8 = [
1429
+ var __iconNode11 = [
1333
1430
  [
1334
1431
  "path",
1335
1432
  {
@@ -1338,9 +1435,9 @@ var __iconNode8 = [
1338
1435
  }
1339
1436
  ]
1340
1437
  ];
1341
- var MessageCircle = createLucideIcon("message-circle", __iconNode8);
1438
+ var MessageCircle = createLucideIcon("message-circle", __iconNode11);
1342
1439
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-square.js
1343
- var __iconNode9 = [
1440
+ var __iconNode12 = [
1344
1441
  [
1345
1442
  "path",
1346
1443
  {
@@ -1349,9 +1446,9 @@ var __iconNode9 = [
1349
1446
  }
1350
1447
  ]
1351
1448
  ];
1352
- var MessageSquare = createLucideIcon("message-square", __iconNode9);
1449
+ var MessageSquare = createLucideIcon("message-square", __iconNode12);
1353
1450
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic-off.js
1354
- var __iconNode10 = [
1451
+ var __iconNode13 = [
1355
1452
  ["path", { d: "M12 19v3", key: "npa21l" }],
1356
1453
  ["path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33", key: "1gzdoj" }],
1357
1454
  ["path", { d: "M16.95 16.95A7 7 0 0 1 5 12v-2", key: "cqa7eg" }],
@@ -1359,40 +1456,40 @@ var __iconNode10 = [
1359
1456
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
1360
1457
  ["path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12", key: "r2i35w" }]
1361
1458
  ];
1362
- var MicOff = createLucideIcon("mic-off", __iconNode10);
1459
+ var MicOff = createLucideIcon("mic-off", __iconNode13);
1363
1460
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic.js
1364
- var __iconNode11 = [
1461
+ var __iconNode14 = [
1365
1462
  ["path", { d: "M12 19v3", key: "npa21l" }],
1366
1463
  ["path", { d: "M19 10v2a7 7 0 0 1-14 0v-2", key: "1vc78b" }],
1367
1464
  ["rect", { x: "9", y: "2", width: "6", height: "13", rx: "3", key: "s6n7sd" }]
1368
1465
  ];
1369
- var Mic = createLucideIcon("mic", __iconNode11);
1466
+ var Mic = createLucideIcon("mic", __iconNode14);
1370
1467
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/minimize-2.js
1371
- var __iconNode12 = [
1468
+ var __iconNode15 = [
1372
1469
  ["path", { d: "m14 10 7-7", key: "oa77jy" }],
1373
1470
  ["path", { d: "M20 10h-6V4", key: "mjg0md" }],
1374
1471
  ["path", { d: "m3 21 7-7", key: "tjx5ai" }],
1375
1472
  ["path", { d: "M4 14h6v6", key: "rmj7iw" }]
1376
1473
  ];
1377
- var Minimize2 = createLucideIcon("minimize-2", __iconNode12);
1474
+ var Minimize2 = createLucideIcon("minimize-2", __iconNode15);
1378
1475
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor-off.js
1379
- var __iconNode13 = [
1476
+ var __iconNode16 = [
1380
1477
  ["path", { d: "M12 17v4", key: "1riwvh" }],
1381
1478
  ["path", { d: "M17 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 1.184-1.826", key: "cv7jms" }],
1382
1479
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
1383
1480
  ["path", { d: "M8 21h8", key: "1ev6f3" }],
1384
1481
  ["path", { d: "M8.656 3H20a2 2 0 0 1 2 2v10a2 2 0 0 1-.293 1.042", key: "z8ni2w" }]
1385
1482
  ];
1386
- var MonitorOff = createLucideIcon("monitor-off", __iconNode13);
1483
+ var MonitorOff = createLucideIcon("monitor-off", __iconNode16);
1387
1484
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor.js
1388
- var __iconNode14 = [
1485
+ var __iconNode17 = [
1389
1486
  ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2", key: "48i651" }],
1390
1487
  ["line", { x1: "8", x2: "16", y1: "21", y2: "21", key: "1svkeh" }],
1391
1488
  ["line", { x1: "12", x2: "12", y1: "17", y2: "21", key: "vw1qmm" }]
1392
1489
  ];
1393
- var Monitor = createLucideIcon("monitor", __iconNode14);
1490
+ var Monitor = createLucideIcon("monitor", __iconNode17);
1394
1491
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mouse-pointer-2.js
1395
- var __iconNode15 = [
1492
+ var __iconNode18 = [
1396
1493
  [
1397
1494
  "path",
1398
1495
  {
@@ -1401,9 +1498,9 @@ var __iconNode15 = [
1401
1498
  }
1402
1499
  ]
1403
1500
  ];
1404
- var MousePointer2 = createLucideIcon("mouse-pointer-2", __iconNode15);
1501
+ var MousePointer2 = createLucideIcon("mouse-pointer-2", __iconNode18);
1405
1502
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/phone-off.js
1406
- var __iconNode16 = [
1503
+ var __iconNode19 = [
1407
1504
  [
1408
1505
  "path",
1409
1506
  {
@@ -1420,9 +1517,29 @@ var __iconNode16 = [
1420
1517
  }
1421
1518
  ]
1422
1519
  ];
1423
- var PhoneOff = createLucideIcon("phone-off", __iconNode16);
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);
1424
1541
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/send.js
1425
- var __iconNode17 = [
1542
+ var __iconNode21 = [
1426
1543
  [
1427
1544
  "path",
1428
1545
  {
@@ -1432,9 +1549,17 @@ var __iconNode17 = [
1432
1549
  ],
1433
1550
  ["path", { d: "m21.854 2.147-10.94 10.939", key: "12cjpa" }]
1434
1551
  ];
1435
- var Send = createLucideIcon("send", __iconNode17);
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);
1436
1561
  // src/components/HighlightOverlay.tsx
1437
- import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef3, useState as useState3 } from "react";
1562
+ import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef3, useState as useState4 } from "react";
1438
1563
  import { jsx, jsxs } from "react/jsx-runtime";
1439
1564
  var Z_INDEX = 2147483646;
1440
1565
  var HIGHLIGHT_PADDING = 6;
@@ -1538,14 +1663,14 @@ function findScrollableAncestor(el) {
1538
1663
  return el.ownerDocument?.documentElement ?? document.documentElement;
1539
1664
  }
1540
1665
  function HighlightOverlay() {
1541
- const [overlayState, setOverlayState] = useState3(null);
1666
+ const [overlayState, setOverlayState] = useState4(null);
1542
1667
  const targetElementRef = useRef3(null);
1543
1668
  const pendingFrameRef = useRef3(null);
1544
- const clearOverlay = useCallback3(() => {
1669
+ const clearOverlay = useCallback4(() => {
1545
1670
  targetElementRef.current = null;
1546
1671
  setOverlayState(null);
1547
1672
  }, []);
1548
- const recomputeRect = useCallback3(() => {
1673
+ const recomputeRect = useCallback4(() => {
1549
1674
  pendingFrameRef.current = null;
1550
1675
  const target = targetElementRef.current;
1551
1676
  if (!target)
@@ -1557,12 +1682,12 @@ function HighlightOverlay() {
1557
1682
  const rect2 = getRectInTopViewport(target);
1558
1683
  setOverlayState((prev) => prev ? { ...prev, rect: rect2 } : prev);
1559
1684
  }, [clearOverlay]);
1560
- const scheduleRecompute = useCallback3(() => {
1685
+ const scheduleRecompute = useCallback4(() => {
1561
1686
  if (pendingFrameRef.current !== null)
1562
1687
  return;
1563
1688
  pendingFrameRef.current = window.requestAnimationFrame(recomputeRect);
1564
1689
  }, [recomputeRect]);
1565
- const onHighlightMessage = useCallback3((msg) => {
1690
+ const onHighlightMessage = useCallback4((msg) => {
1566
1691
  const parsed = parseHighlightMessage(msg.payload);
1567
1692
  if (!parsed)
1568
1693
  return;
@@ -1583,7 +1708,7 @@ function HighlightOverlay() {
1583
1708
  });
1584
1709
  }, [clearOverlay]);
1585
1710
  useDataChannel(HIGHLIGHT_TOPIC, onHighlightMessage);
1586
- useEffect5(() => {
1711
+ useEffect6(() => {
1587
1712
  if (!overlayState)
1588
1713
  return;
1589
1714
  const target = targetElementRef.current;
@@ -1665,7 +1790,7 @@ function HighlightOverlay() {
1665
1790
  }
1666
1791
 
1667
1792
  // src/components/MinimizedBubble.tsx
1668
- import { useEffect as useEffect6 } from "react";
1793
+ import { useEffect as useEffect7 } from "react";
1669
1794
 
1670
1795
  // src/hooks/useLiveAgent.ts
1671
1796
  import { use } from "react";
@@ -1681,7 +1806,7 @@ function useLiveAgent() {
1681
1806
  // src/hooks/useMediaControls.ts
1682
1807
  import { useLocalParticipant as useLocalParticipant3 } from "@livekit/components-react/hooks";
1683
1808
  import { ScreenSharePresets as ScreenSharePresets2 } from "livekit-client";
1684
- import { useCallback as useCallback4 } from "react";
1809
+ import { useCallback as useCallback5 } from "react";
1685
1810
  var SCREEN_SHARE_OPTIONS = {
1686
1811
  video: { displaySurface: "browser" },
1687
1812
  resolution: ScreenSharePresets2.h720fps30.resolution,
@@ -1691,14 +1816,14 @@ function useMediaControls() {
1691
1816
  const { localParticipant } = useLocalParticipant3();
1692
1817
  const isMuted = !localParticipant.isMicrophoneEnabled;
1693
1818
  const isScreenSharing = localParticipant.isScreenShareEnabled;
1694
- const toggleMute = useCallback4(async () => {
1819
+ const toggleMute = useCallback5(async () => {
1695
1820
  try {
1696
1821
  await localParticipant.setMicrophoneEnabled(isMuted);
1697
1822
  } catch (error) {
1698
1823
  console.error("Failed to toggle microphone:", error);
1699
1824
  }
1700
1825
  }, [localParticipant, isMuted]);
1701
- const toggleScreenShare = useCallback4(async () => {
1826
+ const toggleScreenShare = useCallback5(async () => {
1702
1827
  try {
1703
1828
  await localParticipant.setScreenShareEnabled(!isScreenSharing, SCREEN_SHARE_OPTIONS);
1704
1829
  } catch (error) {
@@ -1736,25 +1861,35 @@ function pillStatusFromAgent(state, canSeePage) {
1736
1861
  return canSeePage ? "observing" : "connected";
1737
1862
  }
1738
1863
  function LauncherStatusPill() {
1739
- const { isConnected, expandPanel, setSidebarTab, position, captureMode } = useLiveAgent();
1864
+ const { isConnected, isStarting, expandPanel, setSidebarTab, position, captureMode } = useLiveAgent();
1740
1865
  const { state } = useAgentVoiceState();
1741
1866
  const { isScreenSharing } = useMediaControls();
1742
- if (!isConnected)
1867
+ const isStandingBy = isStarting && !isConnected;
1868
+ if (!isConnected && !isStandingBy)
1743
1869
  return null;
1744
1870
  const canSeePage = captureMode === "auto" || isScreenSharing;
1745
- const status = pillStatusFromAgent(state, canSeePage);
1746
- const openChat = () => {
1747
- setSidebarTab("chat");
1871
+ const status = isStandingBy ? "standing-by" : pillStatusFromAgent(state, canSeePage);
1872
+ const handleClick = () => {
1873
+ if (!isStandingBy)
1874
+ setSidebarTab("chat");
1748
1875
  expandPanel();
1749
1876
  };
1750
1877
  return /* @__PURE__ */ jsx2("button", {
1751
1878
  type: "button",
1752
- onClick: openChat,
1879
+ onClick: handleClick,
1753
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"),
1754
- "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`,
1755
1882
  children: /* @__PURE__ */ jsxs2("span", {
1756
1883
  className: "skippr:flex skippr:items-center skippr:gap-2 skippr:animate-[skippr-pill-content_0.22s_ease-out]",
1757
1884
  children: [
1885
+ status === "standing-by" && /* @__PURE__ */ jsxs2(Fragment, {
1886
+ children: [
1887
+ /* @__PURE__ */ jsx2("span", {
1888
+ children: "Standing by"
1889
+ }),
1890
+ /* @__PURE__ */ jsx2(ThinkingDots, {})
1891
+ ]
1892
+ }),
1758
1893
  status === "observing" && /* @__PURE__ */ jsxs2(Fragment, {
1759
1894
  children: [
1760
1895
  /* @__PURE__ */ jsx2(ObservingIcon, {}),
@@ -2024,7 +2159,7 @@ function WelcomeBubble({
2024
2159
  position,
2025
2160
  onDismiss
2026
2161
  }) {
2027
- useEffect6(() => {
2162
+ useEffect7(() => {
2028
2163
  const timer = setTimeout(onDismiss, 5000);
2029
2164
  return () => clearTimeout(timer);
2030
2165
  }, [onDismiss]);
@@ -2047,14 +2182,14 @@ function MinimizedBubble({
2047
2182
  onDismissWelcome
2048
2183
  }) {
2049
2184
  const { isConnected, isStarting, position } = useLiveAgent();
2050
- const inSession = isConnected || isStarting;
2185
+ const inSession = isConnected;
2051
2186
  return /* @__PURE__ */ jsxs4(Fragment2, {
2052
2187
  children: [
2053
2188
  /* @__PURE__ */ jsx4(LauncherStatusPill, {}),
2054
2189
  /* @__PURE__ */ jsxs4("div", {
2055
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"),
2056
2191
  children: [
2057
- welcomeMessage && !inSession && !welcomeDismissed && /* @__PURE__ */ jsx4(WelcomeBubble, {
2192
+ welcomeMessage && !isConnected && !isStarting && !welcomeDismissed && /* @__PURE__ */ jsx4(WelcomeBubble, {
2058
2193
  message: welcomeMessage,
2059
2194
  position,
2060
2195
  onDismiss: onDismissWelcome
@@ -2067,7 +2202,7 @@ function MinimizedBubble({
2067
2202
  }
2068
2203
 
2069
2204
  // src/components/Sidebar.tsx
2070
- import { useEffect as useEffect13 } from "react";
2205
+ import { useEffect as useEffect15 } from "react";
2071
2206
 
2072
2207
  // src/hooks/useCombinedMessages.ts
2073
2208
  import { useMemo as useMemo4 } from "react";
@@ -2149,15 +2284,15 @@ function useCombinedMessages() {
2149
2284
  }
2150
2285
 
2151
2286
  // src/hooks/usePhaseUpdates.ts
2152
- import { useCallback as useCallback5 } from "react";
2287
+ import { useCallback as useCallback6 } from "react";
2153
2288
 
2154
2289
  // src/hooks/useAgentState.ts
2155
2290
  import { useRemoteParticipants } from "@livekit/components-react/hooks";
2156
- import { useEffect as useEffect7, useState as useState4 } from "react";
2291
+ import { useEffect as useEffect8, useState as useState5 } from "react";
2157
2292
  function useAgentState(attributeKey, parse, initial) {
2158
- const [value, setValue] = useState4(initial);
2293
+ const [value, setValue] = useState5(initial);
2159
2294
  const remoteParticipants = useRemoteParticipants();
2160
- useEffect7(() => {
2295
+ useEffect8(() => {
2161
2296
  const agentParticipant = remoteParticipants.find((p) => p.attributes?.[attributeKey]);
2162
2297
  if (agentParticipant) {
2163
2298
  const attr = agentParticipant.attributes?.[attributeKey];
@@ -2203,13 +2338,13 @@ function parsePhases(json) {
2203
2338
  return null;
2204
2339
  }
2205
2340
  function usePhaseUpdates() {
2206
- const parse = useCallback5(parsePhases, []);
2341
+ const parse = useCallback6(parsePhases, []);
2207
2342
  const phases = useAgentState("phases", parse, []);
2208
2343
  return { phases };
2209
2344
  }
2210
2345
 
2211
2346
  // src/hooks/useSessionRemaining.ts
2212
- 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";
2213
2348
 
2214
2349
  // src/lib/format.ts
2215
2350
  function formatTime(seconds) {
@@ -2226,8 +2361,8 @@ function parseNumber(s) {
2226
2361
  function useSessionRemaining() {
2227
2362
  const maxCallDuration = useAgentState("maxCallDuration", parseNumber, null);
2228
2363
  const endTimeRef = useRef4(null);
2229
- const [remaining, setRemaining] = useState5(null);
2230
- useEffect8(() => {
2364
+ const [remaining, setRemaining] = useState6(null);
2365
+ useEffect9(() => {
2231
2366
  if (maxCallDuration === null || endTimeRef.current !== null)
2232
2367
  return;
2233
2368
  const endTime = Date.now() + maxCallDuration * 1000;
@@ -2244,10 +2379,10 @@ function useSessionRemaining() {
2244
2379
  }
2245
2380
 
2246
2381
  // src/hooks/useElapsedSeconds.ts
2247
- import { useEffect as useEffect9, useState as useState6 } from "react";
2382
+ import { useEffect as useEffect10, useState as useState7 } from "react";
2248
2383
  function useElapsedSeconds(isRunning) {
2249
- const [elapsed, setElapsed] = useState6(0);
2250
- useEffect9(() => {
2384
+ const [elapsed, setElapsed] = useState7(0);
2385
+ useEffect10(() => {
2251
2386
  if (!isRunning) {
2252
2387
  setElapsed(0);
2253
2388
  return;
@@ -2345,7 +2480,7 @@ function LoadingDots({ label }) {
2345
2480
  }
2346
2481
 
2347
2482
  // src/components/LoginFlow.tsx
2348
- 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";
2349
2484
 
2350
2485
  // src/components/ui/button.tsx
2351
2486
  import { forwardRef as forwardRef3 } from "react";
@@ -2381,20 +2516,20 @@ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2381
2516
  var OTP_LENGTH = 6;
2382
2517
  var DIGIT_KEYS = ["d0", "d1", "d2", "d3", "d4", "d5"];
2383
2518
  function LoginFlow({ requestOtp, verifyOtp, error, isSubmitting }) {
2384
- const [step, setStep] = useState7("email");
2385
- const [email, setEmail] = useState7("");
2386
- const handleRequestOtp = useCallback6(async (emailValue) => {
2519
+ const [step, setStep] = useState8("email");
2520
+ const [email, setEmail] = useState8("");
2521
+ const handleRequestOtp = useCallback7(async (emailValue) => {
2387
2522
  const success = await requestOtp(emailValue);
2388
2523
  if (success)
2389
2524
  setStep("otp");
2390
2525
  }, [requestOtp]);
2391
- const handleVerifyOtp = useCallback6(async (code) => {
2526
+ const handleVerifyOtp = useCallback7(async (code) => {
2392
2527
  await verifyOtp(email, code);
2393
2528
  }, [verifyOtp, email]);
2394
- const handleBack = useCallback6(() => {
2529
+ const handleBack = useCallback7(() => {
2395
2530
  setStep("email");
2396
2531
  }, []);
2397
- const handleResend = useCallback6(async () => {
2532
+ const handleResend = useCallback7(async () => {
2398
2533
  await requestOtp(email);
2399
2534
  }, [requestOtp, email]);
2400
2535
  if (step === "otp") {
@@ -2471,30 +2606,30 @@ function EmailStep({ email, onEmailChange, onSubmit, error, isSubmitting }) {
2471
2606
  });
2472
2607
  }
2473
2608
  function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
2474
- const [digits, setDigits] = useState7(Array(OTP_LENGTH).fill(""));
2475
- const [resendCooldown, setResendCooldown] = useState7(0);
2609
+ const [digits, setDigits] = useState8(Array(OTP_LENGTH).fill(""));
2610
+ const [resendCooldown, setResendCooldown] = useState8(0);
2476
2611
  const inputRefs = useRef5([]);
2477
2612
  const submittedRef = useRef5(false);
2478
- useEffect10(() => {
2613
+ useEffect11(() => {
2479
2614
  inputRefs.current[0]?.focus();
2480
2615
  }, []);
2481
- useEffect10(() => {
2616
+ useEffect11(() => {
2482
2617
  if (error)
2483
2618
  submittedRef.current = false;
2484
2619
  }, [error]);
2485
- useEffect10(() => {
2620
+ useEffect11(() => {
2486
2621
  if (resendCooldown <= 0)
2487
2622
  return;
2488
2623
  const timer = setTimeout(() => setResendCooldown((c) => c - 1), 1000);
2489
2624
  return () => clearTimeout(timer);
2490
2625
  }, [resendCooldown]);
2491
- const submitCode = useCallback6((code) => {
2626
+ const submitCode = useCallback7((code) => {
2492
2627
  if (submittedRef.current || isSubmitting)
2493
2628
  return;
2494
2629
  submittedRef.current = true;
2495
2630
  onSubmit(code);
2496
2631
  }, [onSubmit, isSubmitting]);
2497
- const handleDigitChange = useCallback6((index2, value) => {
2632
+ const handleDigitChange = useCallback7((index2, value) => {
2498
2633
  const digit = value.replace(/\D/g, "").slice(-1);
2499
2634
  const newDigits = [...digits];
2500
2635
  newDigits[index2] = digit;
@@ -2508,12 +2643,12 @@ function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
2508
2643
  submitCode(code);
2509
2644
  }
2510
2645
  }, [digits, submitCode]);
2511
- const handleKeyDown = useCallback6((index2, e) => {
2646
+ const handleKeyDown = useCallback7((index2, e) => {
2512
2647
  if (e.key === "Backspace" && !digits[index2] && index2 > 0) {
2513
2648
  inputRefs.current[index2 - 1]?.focus();
2514
2649
  }
2515
2650
  }, [digits]);
2516
- const handlePaste = useCallback6((e) => {
2651
+ const handlePaste = useCallback7((e) => {
2517
2652
  e.preventDefault();
2518
2653
  const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, OTP_LENGTH);
2519
2654
  if (pasted.length > 0) {
@@ -2675,17 +2810,17 @@ function MeetingControls({ onHangUp, showScreenShareToggle = true }) {
2675
2810
  }
2676
2811
 
2677
2812
  // src/components/MessageList.tsx
2678
- import { useEffect as useEffect12, useRef as useRef7 } from "react";
2813
+ import { useEffect as useEffect13, useRef as useRef7 } from "react";
2679
2814
 
2680
2815
  // src/components/ChatInput.tsx
2681
- 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";
2682
2817
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2683
2818
  var MAX_INPUT_HEIGHT = 60;
2684
2819
  function ChatInput({ sendChatMessage, isSendingChat, autoFocus = false }) {
2685
- const [inputText, setInputText] = useState8("");
2820
+ const [inputText, setInputText] = useState9("");
2686
2821
  const textareaRef = useRef6(null);
2687
2822
  const canSend = inputText.trim().length > 0 && !isSendingChat;
2688
- useEffect11(() => {
2823
+ useEffect12(() => {
2689
2824
  if (autoFocus)
2690
2825
  textareaRef.current?.focus();
2691
2826
  }, [autoFocus]);
@@ -2832,7 +2967,7 @@ function MessageList({
2832
2967
  }) {
2833
2968
  const scrollRef = useRef7(null);
2834
2969
  const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
2835
- useEffect12(() => {
2970
+ useEffect13(() => {
2836
2971
  scrollRef.current?.scrollIntoView({ behavior: "smooth" });
2837
2972
  }, [messages.length, lastMessage?.content]);
2838
2973
  const showTyping = isStreaming && lastMessage?.role === "assistant" && lastMessage.content === "";
@@ -2863,50 +2998,205 @@ function MessageList({
2863
2998
  });
2864
2999
  }
2865
3000
 
2866
- // src/components/SessionAgenda.tsx
3001
+ // src/components/ModuleSelector.tsx
3002
+ import { useEffect as useEffect14, useRef as useRef8, useState as useState10 } from "react";
2867
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";
2868
3158
  function SessionAgenda({ phases, hasStarted }) {
2869
3159
  if (phases.length === 0 || !hasStarted) {
2870
- return /* @__PURE__ */ jsx14("div", {
3160
+ return /* @__PURE__ */ jsx15("div", {
2871
3161
  className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
2872
- children: /* @__PURE__ */ jsx14(LoadingDots, {
3162
+ children: /* @__PURE__ */ jsx15(LoadingDots, {
2873
3163
  label: "Waiting for agenda to load..."
2874
3164
  })
2875
3165
  });
2876
3166
  }
2877
- return /* @__PURE__ */ jsx14("div", {
3167
+ return /* @__PURE__ */ jsx15("div", {
2878
3168
  className: "skippr:flex-1 skippr:overflow-y-auto skippr:px-4 skippr:py-4",
2879
- children: /* @__PURE__ */ jsx14("div", {
3169
+ children: /* @__PURE__ */ jsx15("div", {
2880
3170
  className: "skippr:space-y-1",
2881
3171
  children: phases.map((phase) => {
2882
3172
  const isActive = phase.status === "active";
2883
3173
  const isCompleted = phase.status === "completed";
2884
- return /* @__PURE__ */ jsxs13("div", {
3174
+ return /* @__PURE__ */ jsxs14("div", {
2885
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"),
2886
3176
  children: [
2887
- /* @__PURE__ */ jsx14("div", {
3177
+ /* @__PURE__ */ jsx15("div", {
2888
3178
  className: "skippr:mt-0.5",
2889
- children: isCompleted ? /* @__PURE__ */ jsx14(CircleCheck, {
3179
+ children: isCompleted ? /* @__PURE__ */ jsx15(CircleCheck, {
2890
3180
  className: "skippr:size-4 skippr:text-chart-3"
2891
- }) : isActive ? /* @__PURE__ */ jsx14(Circle, {
3181
+ }) : isActive ? /* @__PURE__ */ jsx15(Circle, {
2892
3182
  className: "skippr:size-4 skippr:fill-primary/30 skippr:text-primary"
2893
- }) : /* @__PURE__ */ jsx14(Circle, {
3183
+ }) : /* @__PURE__ */ jsx15(Circle, {
2894
3184
  className: "skippr:size-4 skippr:text-muted-foreground/30"
2895
3185
  })
2896
3186
  }),
2897
- /* @__PURE__ */ jsxs13("div", {
3187
+ /* @__PURE__ */ jsxs14("div", {
2898
3188
  className: "skippr:min-w-0 skippr:flex-1",
2899
3189
  children: [
2900
- /* @__PURE__ */ jsx14("p", {
3190
+ /* @__PURE__ */ jsx15("p", {
2901
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"),
2902
3192
  children: phase.name
2903
3193
  }),
2904
- phase.highlights.length > 0 && /* @__PURE__ */ jsx14("ul", {
3194
+ phase.highlights.length > 0 && /* @__PURE__ */ jsx15("ul", {
2905
3195
  className: "skippr:mt-1 skippr:space-y-0.5",
2906
- children: phase.highlights.map((text) => /* @__PURE__ */ jsxs13("li", {
3196
+ children: phase.highlights.map((text) => /* @__PURE__ */ jsxs14("li", {
2907
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"),
2908
3198
  children: [
2909
- /* @__PURE__ */ jsx14("span", {
3199
+ /* @__PURE__ */ jsx15("span", {
2910
3200
  className: "skippr:size-1 skippr:shrink-0 skippr:rounded-full skippr:bg-current"
2911
3201
  }),
2912
3202
  text
@@ -2923,12 +3213,12 @@ function SessionAgenda({ phases, hasStarted }) {
2923
3213
  }
2924
3214
 
2925
3215
  // src/components/SessionWarningBanner.tsx
2926
- import { jsx as jsx15 } from "react/jsx-runtime";
3216
+ import { jsx as jsx16 } from "react/jsx-runtime";
2927
3217
  var SESSION_WARNING_THRESHOLD_SECS = 60;
2928
3218
  function SessionWarningBanner({ remaining }) {
2929
3219
  if (remaining === null || remaining <= 0 || remaining > SESSION_WARNING_THRESHOLD_SECS)
2930
3220
  return null;
2931
- return /* @__PURE__ */ jsx15("div", {
3221
+ return /* @__PURE__ */ jsx16("div", {
2932
3222
  "data-testid": "session-warning-banner",
2933
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",
2934
3224
  children: "Session ending soon"
@@ -2936,24 +3226,30 @@ function SessionWarningBanner({ remaining }) {
2936
3226
  }
2937
3227
 
2938
3228
  // src/components/StartSessionPrompt.tsx
2939
- import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
3229
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2940
3230
  function StartSessionPrompt({
2941
3231
  onStartSession,
3232
+ agentId,
3233
+ agentControls,
2942
3234
  isStarting,
2943
3235
  error,
2944
3236
  label = "Talk to Skippr"
2945
3237
  }) {
2946
- return /* @__PURE__ */ jsxs14("div", {
3238
+ return /* @__PURE__ */ jsxs15("div", {
2947
3239
  className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:items-center skippr:justify-center skippr:gap-3 skippr:px-4",
2948
3240
  children: [
2949
- /* @__PURE__ */ jsx16("button", {
3241
+ /* @__PURE__ */ jsx17("button", {
2950
3242
  type: "button",
2951
- onClick: onStartSession,
2952
- disabled: isStarting,
3243
+ onClick: () => {
3244
+ if (!agentId)
3245
+ return;
3246
+ onStartSession(agentControls ? { agentId, agentControls } : { agentId });
3247
+ },
3248
+ disabled: isStarting || !agentId,
2953
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",
2954
3250
  children: isStarting ? "Starting..." : label
2955
3251
  }),
2956
- error && /* @__PURE__ */ jsx16("p", {
3252
+ error && /* @__PURE__ */ jsx17("p", {
2957
3253
  className: "skippr:text-xs skippr:text-destructive",
2958
3254
  children: error
2959
3255
  })
@@ -2962,7 +3258,7 @@ function StartSessionPrompt({
2962
3258
  }
2963
3259
 
2964
3260
  // src/components/Sidebar.tsx
2965
- import { jsx as jsx17, jsxs as jsxs15, Fragment as Fragment3 } from "react/jsx-runtime";
3261
+ import { jsx as jsx18, jsxs as jsxs16, Fragment as Fragment3 } from "react/jsx-runtime";
2966
3262
  function Sidebar({
2967
3263
  hideControls = false,
2968
3264
  hideHeader = false,
@@ -2986,11 +3282,14 @@ function Sidebar({
2986
3282
  sidebarTab: activeTab,
2987
3283
  setSidebarTab: setActiveTab,
2988
3284
  autoFocusChat,
2989
- captureMode
3285
+ captureMode,
3286
+ hasModuleSelector,
3287
+ agentId,
3288
+ agentControls
2990
3289
  } = useLiveAgent();
2991
3290
  const isFloating = variant === "floating";
2992
3291
  const isSidebar = variant === "sidebar";
2993
- useEffect13(() => {
3292
+ useEffect15(() => {
2994
3293
  if (!isSidebar)
2995
3294
  return;
2996
3295
  const prop = position === "right" ? "marginRight" : "marginLeft";
@@ -3004,22 +3303,22 @@ function Sidebar({
3004
3303
  document.body.style.transition = "";
3005
3304
  };
3006
3305
  }, [isSidebar, isPanelOpen, position]);
3007
- return /* @__PURE__ */ jsxs15("div", {
3306
+ return /* @__PURE__ */ jsxs16("div", {
3008
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"),
3009
3308
  style: { width: isPanelOpen ? SIDEBAR_WIDTH : undefined },
3010
3309
  children: [
3011
- !hideHeader && /* @__PURE__ */ jsx17(ChatHeader, {}),
3012
- !isAuthenticated && isValidating ? /* @__PURE__ */ jsx17("div", {
3310
+ !hideHeader && /* @__PURE__ */ jsx18(ChatHeader, {}),
3311
+ !isAuthenticated && isValidating ? /* @__PURE__ */ jsx18("div", {
3013
3312
  className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
3014
- children: /* @__PURE__ */ jsx17(LoadingDots, {
3313
+ children: /* @__PURE__ */ jsx18(LoadingDots, {
3015
3314
  label: "Loading..."
3016
3315
  })
3017
- }) : !isAuthenticated ? /* @__PURE__ */ jsx17(LoginFlow, {
3316
+ }) : !isAuthenticated ? /* @__PURE__ */ jsx18(LoginFlow, {
3018
3317
  requestOtp,
3019
3318
  verifyOtp,
3020
3319
  error: authError,
3021
3320
  isSubmitting: isAuthSubmitting
3022
- }) : /* @__PURE__ */ jsx17(AuthenticatedContent, {
3321
+ }) : /* @__PURE__ */ jsx18(AuthenticatedContent, {
3023
3322
  isConnected,
3024
3323
  onStartSession: startSession,
3025
3324
  onDisconnect: disconnect,
@@ -3030,7 +3329,10 @@ function Sidebar({
3030
3329
  hideControls,
3031
3330
  startSessionLabel,
3032
3331
  autoFocusChat,
3033
- showScreenShareToggle: captureMode === "screenshare"
3332
+ showScreenShareToggle: captureMode === "screenshare",
3333
+ hasModuleSelector,
3334
+ agentId,
3335
+ agentControls
3034
3336
  })
3035
3337
  ]
3036
3338
  });
@@ -3046,60 +3348,67 @@ function AuthenticatedContent({
3046
3348
  hideControls,
3047
3349
  startSessionLabel,
3048
3350
  autoFocusChat,
3049
- showScreenShareToggle
3351
+ showScreenShareToggle,
3352
+ hasModuleSelector,
3353
+ agentId,
3354
+ agentControls
3050
3355
  }) {
3051
- return /* @__PURE__ */ jsxs15(Fragment3, {
3356
+ const showSelectorAsPrompt = hasModuleSelector && !isConnected && !isStarting;
3357
+ const showTabBar = !showSelectorAsPrompt;
3358
+ return /* @__PURE__ */ jsxs16(Fragment3, {
3052
3359
  children: [
3053
- isConnected && /* @__PURE__ */ jsx17(ConnectedBanner, {}),
3054
- /* @__PURE__ */ jsxs15("div", {
3055
- 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",
3056
3363
  children: [
3057
- /* @__PURE__ */ jsxs15("button", {
3364
+ /* @__PURE__ */ jsxs16("button", {
3058
3365
  type: "button",
3059
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"),
3060
3367
  onClick: () => onTabChange("chat"),
3061
3368
  children: [
3062
- /* @__PURE__ */ jsx17(MessageCircle, {
3369
+ /* @__PURE__ */ jsx18(MessageCircle, {
3063
3370
  className: "skippr:size-3.5"
3064
3371
  }),
3065
3372
  "Chat",
3066
- activeTab === "chat" && /* @__PURE__ */ jsx17("span", {
3373
+ activeTab === "chat" && /* @__PURE__ */ jsx18("span", {
3067
3374
  className: "skippr:absolute skippr:-bottom-2 skippr:left-3 skippr:right-3 skippr:h-0.5 skippr:rounded-full skippr:bg-foreground"
3068
3375
  })
3069
3376
  ]
3070
3377
  }),
3071
- /* @__PURE__ */ jsxs15("button", {
3378
+ /* @__PURE__ */ jsxs16("button", {
3072
3379
  type: "button",
3073
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"),
3074
3381
  onClick: () => onTabChange("agenda"),
3075
3382
  children: [
3076
- /* @__PURE__ */ jsx17(Calendar, {
3383
+ /* @__PURE__ */ jsx18(Calendar, {
3077
3384
  className: "skippr:size-3.5"
3078
3385
  }),
3079
3386
  "Agenda",
3080
- activeTab === "agenda" && /* @__PURE__ */ jsx17("span", {
3387
+ activeTab === "agenda" && /* @__PURE__ */ jsx18("span", {
3081
3388
  className: "skippr:absolute skippr:-bottom-2 skippr:left-3 skippr:right-3 skippr:h-0.5 skippr:rounded-full skippr:bg-foreground"
3082
3389
  })
3083
3390
  ]
3084
3391
  })
3085
3392
  ]
3086
3393
  }),
3087
- /* @__PURE__ */ jsx17("div", {
3394
+ /* @__PURE__ */ jsx18("div", {
3088
3395
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col",
3089
- children: isConnected || isStarting ? /* @__PURE__ */ jsx17(ConnectedBody, {
3396
+ children: showSelectorAsPrompt ? /* @__PURE__ */ jsx18(ModuleSelector, {}) : isConnected || isStarting ? /* @__PURE__ */ jsx18(ConnectedBody, {
3090
3397
  activeTab,
3091
3398
  autoFocusChat
3092
- }) : /* @__PURE__ */ jsx17("div", {
3399
+ }) : /* @__PURE__ */ jsx18("div", {
3093
3400
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col skippr:animate-skippr-tab-fade",
3094
- children: /* @__PURE__ */ jsx17(StartSessionPrompt, {
3401
+ children: /* @__PURE__ */ jsx18(StartSessionPrompt, {
3095
3402
  onStartSession,
3403
+ agentId,
3404
+ agentControls,
3096
3405
  isStarting,
3097
3406
  error,
3098
3407
  label: startSessionLabel
3099
3408
  })
3100
3409
  }, `${activeTab}-empty`)
3101
3410
  }),
3102
- isConnected && !hideControls && /* @__PURE__ */ jsx17(MeetingControls, {
3411
+ isConnected && !hideControls && /* @__PURE__ */ jsx18(MeetingControls, {
3103
3412
  onHangUp: onDisconnect,
3104
3413
  showScreenShareToggle
3105
3414
  })
@@ -3108,7 +3417,7 @@ function AuthenticatedContent({
3108
3417
  }
3109
3418
  function ConnectedBanner() {
3110
3419
  const remaining = useSessionRemaining();
3111
- return /* @__PURE__ */ jsx17(SessionWarningBanner, {
3420
+ return /* @__PURE__ */ jsx18(SessionWarningBanner, {
3112
3421
  remaining
3113
3422
  });
3114
3423
  }
@@ -3119,17 +3428,17 @@ function ConnectedBody({
3119
3428
  const { allMessages, agentState, sendChatMessage, isSendingChat } = useCombinedMessages();
3120
3429
  const { phases } = usePhaseUpdates();
3121
3430
  if (activeTab === "agenda") {
3122
- return /* @__PURE__ */ jsx17("div", {
3431
+ return /* @__PURE__ */ jsx18("div", {
3123
3432
  className: "skippr:min-h-0 skippr:flex-1 skippr:overflow-y-auto skippr:animate-skippr-tab-fade",
3124
- children: /* @__PURE__ */ jsx17(SessionAgenda, {
3433
+ children: /* @__PURE__ */ jsx18(SessionAgenda, {
3125
3434
  phases,
3126
3435
  hasStarted: allMessages.length > 0 || agentState === "speaking"
3127
3436
  })
3128
3437
  }, "agenda");
3129
3438
  }
3130
- return /* @__PURE__ */ jsx17("div", {
3439
+ return /* @__PURE__ */ jsx18("div", {
3131
3440
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col skippr:animate-skippr-tab-fade",
3132
- children: /* @__PURE__ */ jsx17(MessageList, {
3441
+ children: /* @__PURE__ */ jsx18(MessageList, {
3133
3442
  messages: allMessages,
3134
3443
  isStreaming: agentState === "speaking",
3135
3444
  sendChatMessage,
@@ -3140,31 +3449,31 @@ function ConnectedBody({
3140
3449
  }
3141
3450
 
3142
3451
  // src/components/SidebarTrigger.tsx
3143
- import { jsx as jsx18 } from "react/jsx-runtime";
3452
+ import { jsx as jsx19 } from "react/jsx-runtime";
3144
3453
  function SidebarTrigger() {
3145
3454
  const { isPanelOpen, togglePanel, minimizePanel, minimizable, position, isMinimized } = useLiveAgent();
3146
3455
  if (isMinimized)
3147
3456
  return null;
3148
3457
  const handleClick = isPanelOpen && minimizable ? minimizePanel : togglePanel;
3149
- return /* @__PURE__ */ jsx18("button", {
3458
+ return /* @__PURE__ */ jsx19("button", {
3150
3459
  type: "button",
3151
3460
  onClick: handleClick,
3152
3461
  title: isPanelOpen ? "Close chat" : "Open chat",
3153
3462
  "aria-label": isPanelOpen ? "Close chat" : "Open chat",
3154
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"),
3155
- children: isPanelOpen ? /* @__PURE__ */ jsx18(ChevronDown, {
3464
+ children: isPanelOpen ? /* @__PURE__ */ jsx19(ChevronDown, {
3156
3465
  className: "skippr:size-5"
3157
- }) : /* @__PURE__ */ jsx18(Logo, {
3466
+ }) : /* @__PURE__ */ jsx19(Logo, {
3158
3467
  className: "skippr:size-7"
3159
3468
  })
3160
3469
  });
3161
3470
  }
3162
3471
 
3163
3472
  // src/components/LiveAgent.tsx
3164
- import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
3473
+ import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
3165
3474
  function LiveAgent(props) {
3166
3475
  const {
3167
- agentId,
3476
+ agentId: hostAgentId,
3168
3477
  authToken: authTokenProp,
3169
3478
  appKey,
3170
3479
  userToken,
@@ -3180,39 +3489,81 @@ function LiveAgent(props) {
3180
3489
  children
3181
3490
  } = props;
3182
3491
  const captureMode = props.captureMode ?? "screenshare";
3183
- let agentControls;
3492
+ let hostAgentControls;
3184
3493
  if ("agentControls" in props && props.agentControls) {
3185
3494
  if (captureMode === "auto") {
3186
- agentControls = props.agentControls;
3495
+ hostAgentControls = props.agentControls;
3187
3496
  } else {
3188
3497
  console.warn('[Skippr] agentControls requires captureMode: "auto"');
3189
3498
  }
3190
3499
  }
3191
3500
  const auth = useAuth({ appKey });
3192
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]);
3193
3525
  const {
3194
3526
  connection,
3195
3527
  shouldConnect,
3196
3528
  isStarting,
3529
+ isDisconnecting,
3197
3530
  error,
3198
3531
  errorCode,
3199
3532
  startSession,
3200
3533
  disconnect,
3201
- pendingScreenStream
3534
+ pendingScreenStream,
3535
+ bearerToken
3202
3536
  } = useSession({
3203
- agentId,
3204
3537
  captureMode,
3205
- agentControls,
3206
3538
  authToken: effectiveAuthToken,
3207
3539
  appKey,
3208
- userToken
3540
+ userToken,
3541
+ onStart: minimizeOnSessionStart,
3542
+ onStartError: expandOnSessionStartError,
3543
+ onDisconnect: minimizeOnSessionDisconnect
3209
3544
  });
3210
- const [isPanelOpen, setIsPanelOpen] = useState9(defaultOpen);
3211
- const [isMinimized, setIsMinimized] = useState9(minimizable && !defaultOpen);
3212
- const [sidebarTab, setSidebarTab] = useState9("agenda");
3213
- const [welcomeDismissed, setWelcomeDismissed] = useState9(false);
3214
- const dismissWelcome = useCallback7(() => setWelcomeDismissed(true), []);
3215
- const [currentPosition, setCurrentPosition] = useState9(() => {
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
3556
+ });
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(() => {
3216
3567
  try {
3217
3568
  const saved = localStorage.getItem("skippr_widget_position");
3218
3569
  if (saved === "left" || saved === "right")
@@ -3220,20 +3571,20 @@ function LiveAgent(props) {
3220
3571
  } catch {}
3221
3572
  return position;
3222
3573
  });
3223
- const setPositionWithPersist = useCallback7((pos) => {
3574
+ const setPositionWithPersist = useCallback8((pos) => {
3224
3575
  setCurrentPosition(pos);
3225
3576
  try {
3226
3577
  localStorage.setItem("skippr_widget_position", pos);
3227
3578
  } catch {}
3228
3579
  }, []);
3229
- const openPanel = useCallback7(() => setIsPanelOpen(true), []);
3230
- const closePanel = useCallback7(() => setIsPanelOpen(false), []);
3231
- const togglePanel = useCallback7(() => setIsPanelOpen((prev) => !prev), []);
3232
- 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(() => {
3233
3584
  setIsMinimized(false);
3234
3585
  setIsPanelOpen(true);
3235
3586
  }, []);
3236
- const minimizePanel = useCallback7(() => {
3587
+ const minimizePanel = useCallback8(() => {
3237
3588
  if (!minimizable)
3238
3589
  return;
3239
3590
  setIsMinimized(true);
@@ -3241,20 +3592,12 @@ function LiveAgent(props) {
3241
3592
  }, [minimizable]);
3242
3593
  const isConnected = connection !== null;
3243
3594
  const isAuthenticated = !!userToken || !!authTokenProp || auth.isAuthenticated;
3244
- const prevConnectionRef = useRef8(connection);
3245
- useEffect14(() => {
3246
- const connectionChanged = prevConnectionRef.current !== connection;
3247
- prevConnectionRef.current = connection;
3248
- if (connectionChanged && minimizable) {
3249
- setIsMinimized(true);
3250
- setIsPanelOpen(false);
3251
- }
3252
- }, [connection, minimizable]);
3253
3595
  const ctx = useMemo5(() => ({
3254
3596
  connection,
3255
3597
  shouldConnect,
3256
3598
  isConnected,
3257
3599
  isStarting,
3600
+ isDisconnecting,
3258
3601
  error,
3259
3602
  errorCode,
3260
3603
  startSession,
@@ -3281,12 +3624,21 @@ function LiveAgent(props) {
3281
3624
  setSidebarTab,
3282
3625
  autoFocusChat,
3283
3626
  captureMode,
3284
- agentControls
3627
+ agentId,
3628
+ agentControls,
3629
+ hasModuleSelector,
3630
+ availableModules,
3631
+ activeModule,
3632
+ isLoadingModules,
3633
+ modulesError,
3634
+ refetchModules,
3635
+ selectModule
3285
3636
  }), [
3286
3637
  connection,
3287
3638
  shouldConnect,
3288
3639
  isConnected,
3289
3640
  isStarting,
3641
+ isDisconnecting,
3290
3642
  error,
3291
3643
  errorCode,
3292
3644
  startSession,
@@ -3312,33 +3664,41 @@ function LiveAgent(props) {
3312
3664
  sidebarTab,
3313
3665
  autoFocusChat,
3314
3666
  captureMode,
3315
- agentControls
3667
+ agentId,
3668
+ agentControls,
3669
+ hasModuleSelector,
3670
+ availableModules,
3671
+ activeModule,
3672
+ isLoadingModules,
3673
+ modulesError,
3674
+ refetchModules,
3675
+ selectModule
3316
3676
  ]);
3317
- return /* @__PURE__ */ jsx19(LiveAgentContext.Provider, {
3677
+ return /* @__PURE__ */ jsx20(LiveAgentContext.Provider, {
3318
3678
  value: ctx,
3319
- children: /* @__PURE__ */ jsxs16(LiveKitRoom, {
3679
+ children: /* @__PURE__ */ jsxs17(LiveKitRoom, {
3320
3680
  serverUrl: connection?.livekitUrl,
3321
3681
  token: connection?.token,
3322
3682
  connect: shouldConnect,
3323
3683
  audio: true,
3324
3684
  onDisconnected: disconnect,
3325
3685
  children: [
3326
- connection && /* @__PURE__ */ jsx19(RoomAudioRenderer, {}),
3327
- connection && captureMode === "screenshare" && /* @__PURE__ */ jsx19(AutoStartMedia, {
3686
+ connection && /* @__PURE__ */ jsx20(RoomAudioRenderer, {}),
3687
+ connection && captureMode === "screenshare" && /* @__PURE__ */ jsx20(AutoStartMedia, {
3328
3688
  pendingScreenStream
3329
3689
  }),
3330
- connection && captureMode === "auto" && /* @__PURE__ */ jsx19(DomCapture, {}),
3331
- connection && captureMode === "auto" && agentControls?.highlight && /* @__PURE__ */ jsx19(HighlightOverlay, {}),
3332
- /* @__PURE__ */ jsxs16("div", {
3690
+ connection && captureMode === "auto" && /* @__PURE__ */ jsx20(DomCapture, {}),
3691
+ connection && captureMode === "auto" && agentControls?.highlight && /* @__PURE__ */ jsx20(HighlightOverlay, {}),
3692
+ /* @__PURE__ */ jsxs17("div", {
3333
3693
  id: WIDGET_ROOT_ID,
3334
3694
  children: [
3335
- isMinimized && /* @__PURE__ */ jsx19(MinimizedBubble, {
3695
+ isMinimized && /* @__PURE__ */ jsx20(MinimizedBubble, {
3336
3696
  welcomeMessage,
3337
3697
  welcomeDismissed,
3338
3698
  onDismissWelcome: dismissWelcome
3339
3699
  }),
3340
- /* @__PURE__ */ jsx19(SidebarTrigger, {}),
3341
- /* @__PURE__ */ jsx19(Sidebar, {
3700
+ /* @__PURE__ */ jsx20(SidebarTrigger, {}),
3701
+ /* @__PURE__ */ jsx20(Sidebar, {
3342
3702
  hideControls,
3343
3703
  hideHeader,
3344
3704
  startSessionLabel