@skippr/live-agent-sdk 0.33.0 → 0.35.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 useMemo6, useRef as useRef10, useState as useState11 } from "react";
12
12
 
13
13
  // src/context/LiveAgentContext.tsx
14
14
  import { createContext } from "react";
@@ -155,11 +155,91 @@ 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, useRef, useState as useState3 } from "react";
207
+ var API_URL3 = "https://specialist.skippr.ai/api";
208
+ async function fetchSessionMessages(sessionId, bearerToken) {
209
+ try {
210
+ const resp = await fetch(`${API_URL3}/v1/sessions/${sessionId}/messages`, {
211
+ credentials: "omit",
212
+ headers: { Authorization: `Bearer ${bearerToken}` }
213
+ });
214
+ if (!resp.ok)
215
+ return [];
216
+ const { messages } = await resp.json();
217
+ return messages.filter((message) => message.role !== "system").map((message) => ({
218
+ id: message.id,
219
+ role: message.role === "user" ? "user" : "assistant",
220
+ content: message.content,
221
+ source: "chat",
222
+ timestamp: new Date(message.createdAt).getTime()
223
+ }));
224
+ } catch {
225
+ return [];
226
+ }
227
+ }
228
+ async function requestScreenShare() {
229
+ try {
230
+ return await navigator.mediaDevices.getDisplayMedia({ video: { displaySurface: "browser" } });
231
+ } catch {
232
+ return null;
233
+ }
234
+ }
235
+ function stopStream(stream) {
236
+ if (!stream)
237
+ return;
238
+ for (const track of stream.getTracks())
239
+ track.stop();
240
+ }
161
241
  async function exchangeForBearerToken(appKey, userToken) {
162
- const resp = await fetch(`${API_URL2}/v1/auth/token-exchange`, {
242
+ const resp = await fetch(`${API_URL3}/v1/auth/token-exchange`, {
163
243
  method: "POST",
164
244
  credentials: "omit",
165
245
  headers: {
@@ -174,22 +254,28 @@ async function exchangeForBearerToken(appKey, userToken) {
174
254
  return token;
175
255
  }
176
256
  function useSession({
177
- agentId,
178
257
  captureMode = "screenshare",
179
- agentControls,
180
258
  authToken,
181
259
  appKey,
182
- userToken
260
+ userToken,
261
+ onStart,
262
+ onStartError,
263
+ onDisconnect
183
264
  }) {
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(() => {
265
+ const [connection, setConnection] = useState3(null);
266
+ const [shouldConnect, setShouldConnect] = useState3(false);
267
+ const [isStarting, setIsStarting] = useState3(false);
268
+ const [isDisconnecting, setIsDisconnecting] = useState3(false);
269
+ const [error, setError] = useState3("");
270
+ const [errorCode, setErrorCode] = useState3(null);
271
+ const [sessionId, setSessionId] = useState3(null);
272
+ const [bearerToken, setBearerToken] = useState3(authToken ?? null);
273
+ const [pendingScreenStream, setPendingScreenStream] = useState3(null);
274
+ const [isPaused, setIsPaused] = useState3(false);
275
+ const [isPausing, setIsPausing] = useState3(false);
276
+ const [resumableSessions, setResumableSessions] = useState3([]);
277
+ const [historyMessages, setHistoryMessages] = useState3([]);
278
+ useEffect3(() => {
193
279
  let stale = false;
194
280
  if (authToken) {
195
281
  setBearerToken(authToken);
@@ -208,29 +294,67 @@ function useSession({
208
294
  stale = true;
209
295
  };
210
296
  }, [authToken, appKey, userToken]);
211
- const highlightOptIn = agentControls?.highlight;
212
- const startSession = useCallback2(async () => {
297
+ const refetchResumable = useCallback3(async () => {
298
+ if (!bearerToken) {
299
+ setResumableSessions([]);
300
+ return;
301
+ }
302
+ try {
303
+ const resp = await fetch(`${API_URL3}/v1/sessions/resumable`, {
304
+ credentials: "omit",
305
+ headers: { Authorization: `Bearer ${bearerToken}` }
306
+ });
307
+ if (!resp.ok) {
308
+ setResumableSessions([]);
309
+ return;
310
+ }
311
+ const { sessions } = await resp.json();
312
+ setResumableSessions(sessions);
313
+ } catch {
314
+ setResumableSessions([]);
315
+ }
316
+ }, [bearerToken]);
317
+ useEffect3(() => {
318
+ refetchResumable();
319
+ }, [refetchResumable]);
320
+ const pauseOnUnloadRef = useRef(null);
321
+ pauseOnUnloadRef.current = connection !== null && !isPaused && sessionId && bearerToken ? { sessionId, bearerToken } : null;
322
+ useEffect3(() => {
323
+ const onPageHide = () => {
324
+ const pending = pauseOnUnloadRef.current;
325
+ if (!pending)
326
+ return;
327
+ fetch(`${API_URL3}/v1/sessions/${pending.sessionId}/pause`, {
328
+ method: "POST",
329
+ credentials: "omit",
330
+ keepalive: true,
331
+ headers: { Authorization: `Bearer ${pending.bearerToken}` }
332
+ }).catch(() => {});
333
+ };
334
+ window.addEventListener("pagehide", onPageHide);
335
+ return () => window.removeEventListener("pagehide", onPageHide);
336
+ }, []);
337
+ const startSession = useCallback3(async ({ agentId, agentControls }) => {
213
338
  if (!bearerToken) {
214
339
  setError("No auth token available");
215
340
  return;
216
341
  }
342
+ if (!agentId) {
343
+ setError("No agent selected");
344
+ return;
345
+ }
217
346
  setIsStarting(true);
218
347
  setError("");
219
348
  setErrorCode(null);
349
+ onStart?.();
220
350
  let screenStream = null;
221
351
  if (captureMode === "screenshare") {
222
- try {
223
- screenStream = await navigator.mediaDevices.getDisplayMedia({
224
- video: { displaySurface: "browser" }
225
- });
226
- } catch {
227
- screenStream = null;
228
- }
352
+ screenStream = await requestScreenShare();
229
353
  }
230
- const requestAgentControls = highlightOptIn === true ? { highlight: true } : undefined;
354
+ const requestAgentControls = agentControls?.highlight === true ? { highlight: true } : undefined;
231
355
  const headers = { Authorization: `Bearer ${bearerToken}` };
232
356
  try {
233
- const createResp = await fetch(`${API_URL2}/v1/sessions`, {
357
+ const createResp = await fetch(`${API_URL3}/v1/sessions`, {
234
358
  method: "POST",
235
359
  credentials: "omit",
236
360
  headers: { "Content-Type": "application/json", ...headers },
@@ -246,7 +370,7 @@ function useSession({
246
370
  throw new Error(body?.detail || `Failed to create session: ${createResp.status}`);
247
371
  }
248
372
  const { session } = await createResp.json();
249
- const startResp = await fetch(`${API_URL2}/v1/sessions/${session.id}/start`, {
373
+ const startResp = await fetch(`${API_URL3}/v1/sessions/${session.id}/start`, {
250
374
  method: "POST",
251
375
  credentials: "omit",
252
376
  headers
@@ -258,52 +382,142 @@ function useSession({
258
382
  }
259
383
  const { connection: conn } = await startResp.json();
260
384
  setSessionId(session.id);
385
+ setHistoryMessages([]);
261
386
  setConnection({
262
387
  livekitUrl: conn.livekitUrl,
263
388
  token: conn.token
264
389
  });
265
390
  setPendingScreenStream(screenStream);
266
391
  setShouldConnect(true);
392
+ setIsPaused(false);
267
393
  } catch (e) {
268
- if (screenStream) {
269
- for (const track of screenStream.getTracks())
270
- track.stop();
271
- }
394
+ stopStream(screenStream);
272
395
  setError(e instanceof Error ? e.message : "Failed to start session");
396
+ onStartError?.();
273
397
  } finally {
274
398
  setIsStarting(false);
275
399
  }
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();
400
+ }, [captureMode, bearerToken, onStart, onStartError]);
401
+ const pauseSession = useCallback3(async () => {
402
+ if (!sessionId || !bearerToken)
403
+ return;
404
+ setIsPausing(true);
405
+ try {
406
+ const resp = await fetch(`${API_URL3}/v1/sessions/${sessionId}/pause`, {
407
+ method: "POST",
408
+ credentials: "omit",
409
+ headers: { Authorization: `Bearer ${bearerToken}` }
410
+ });
411
+ if (!resp.ok) {
412
+ const body = await resp.json().catch(() => null);
413
+ throw new Error(body?.detail || `Failed to pause: ${resp.status}`);
414
+ }
415
+ } catch (e) {
416
+ setError(e instanceof Error ? e.message : "Failed to pause session");
417
+ setIsPausing(false);
418
+ return;
291
419
  }
292
- setError("");
420
+ const history2 = await fetchSessionMessages(sessionId, bearerToken);
421
+ stopStream(pendingScreenStream);
422
+ setPendingScreenStream(null);
293
423
  setShouldConnect(false);
294
424
  setConnection(null);
295
- setSessionId(null);
296
- setPendingScreenStream(null);
297
- }, [sessionId, bearerToken, pendingScreenStream]);
425
+ setHistoryMessages(history2);
426
+ setIsPaused(true);
427
+ setIsPausing(false);
428
+ await refetchResumable();
429
+ }, [sessionId, bearerToken, pendingScreenStream, refetchResumable]);
430
+ const resumeSession = useCallback3(async ({
431
+ sessionId: resumeId,
432
+ agentControls
433
+ }) => {
434
+ if (!bearerToken) {
435
+ setError("No auth token available");
436
+ return;
437
+ }
438
+ setIsStarting(true);
439
+ setError("");
440
+ setErrorCode(null);
441
+ onStart?.();
442
+ let screenStream = null;
443
+ if (captureMode === "screenshare") {
444
+ screenStream = await requestScreenShare();
445
+ }
446
+ const requestAgentControls = agentControls?.highlight === true ? { highlight: true } : undefined;
447
+ try {
448
+ const resp = await fetch(`${API_URL3}/v1/sessions/${resumeId}/resume`, {
449
+ method: "POST",
450
+ credentials: "omit",
451
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
452
+ body: JSON.stringify({ captureMode, agentControls: requestAgentControls })
453
+ });
454
+ if (!resp.ok) {
455
+ const body = await resp.json().catch(() => null);
456
+ setErrorCode(resp.status);
457
+ throw new Error(body?.detail || `Failed to resume: ${resp.status}`);
458
+ }
459
+ const { session, connection: conn } = await resp.json();
460
+ const history2 = await fetchSessionMessages(session.id, bearerToken);
461
+ setSessionId(session.id);
462
+ setConnection({ livekitUrl: conn.livekitUrl, token: conn.token });
463
+ setPendingScreenStream(screenStream);
464
+ setHistoryMessages(history2);
465
+ setShouldConnect(true);
466
+ setIsPaused(false);
467
+ setResumableSessions((prev) => prev.filter((s) => s.id !== resumeId));
468
+ } catch (e) {
469
+ stopStream(screenStream);
470
+ setError(e instanceof Error ? e.message : "Failed to resume session");
471
+ onStartError?.();
472
+ } finally {
473
+ setIsStarting(false);
474
+ }
475
+ }, [captureMode, bearerToken, onStart, onStartError]);
476
+ const disconnect = useCallback3(async () => {
477
+ setIsDisconnecting(true);
478
+ try {
479
+ if (sessionId && bearerToken) {
480
+ try {
481
+ await fetch(`${API_URL3}/v1/sessions/${sessionId}/complete`, {
482
+ method: "POST",
483
+ credentials: "omit",
484
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
485
+ body: JSON.stringify({})
486
+ });
487
+ } catch {}
488
+ }
489
+ stopStream(pendingScreenStream);
490
+ setError("");
491
+ setShouldConnect(false);
492
+ setConnection(null);
493
+ setSessionId(null);
494
+ setPendingScreenStream(null);
495
+ setHistoryMessages([]);
496
+ setIsPaused(false);
497
+ onDisconnect?.();
498
+ await refetchResumable();
499
+ } finally {
500
+ setIsDisconnecting(false);
501
+ }
502
+ }, [sessionId, bearerToken, pendingScreenStream, onDisconnect, refetchResumable]);
298
503
  return {
299
504
  connection,
300
505
  shouldConnect,
301
506
  isStarting,
507
+ isDisconnecting,
302
508
  error,
303
509
  errorCode,
304
510
  startSession,
511
+ pauseSession,
512
+ resumeSession,
305
513
  disconnect,
306
- pendingScreenStream
514
+ isPaused,
515
+ isPausing,
516
+ resumableSessions,
517
+ historyMessages,
518
+ refetchResumable,
519
+ pendingScreenStream,
520
+ bearerToken
307
521
  };
308
522
  }
309
523
 
@@ -320,12 +534,12 @@ var NAME_MAX_CHARS = 80;
320
534
  // src/components/AutoStartMedia.tsx
321
535
  import { useConnectionState, useLocalParticipant } from "@livekit/components-react/hooks";
322
536
  import { ConnectionState, Track } from "livekit-client";
323
- import { useEffect as useEffect3, useRef } from "react";
537
+ import { useEffect as useEffect4, useRef as useRef2 } from "react";
324
538
  function AutoStartMedia({ pendingScreenStream }) {
325
539
  const { localParticipant } = useLocalParticipant();
326
540
  const connectionState = useConnectionState();
327
- const didStartRef = useRef(false);
328
- useEffect3(() => {
541
+ const didStartRef = useRef2(false);
542
+ useEffect4(() => {
329
543
  if (didStartRef.current)
330
544
  return;
331
545
  if (connectionState !== ConnectionState.Connected)
@@ -350,7 +564,7 @@ function AutoStartMedia({ pendingScreenStream }) {
350
564
  // src/components/DomCapture.tsx
351
565
  import { useConnectionState as useConnectionState2, useLocalParticipant as useLocalParticipant2 } from "@livekit/components-react/hooks";
352
566
  import { ConnectionState as ConnectionState2, ScreenSharePresets, Track as Track2 } from "livekit-client";
353
- import { useEffect as useEffect4, useRef as useRef2 } from "react";
567
+ import { useEffect as useEffect5, useRef as useRef3 } from "react";
354
568
 
355
569
  // src/capture/a11yUtils.ts
356
570
  var ROLE_BY_TAG = {
@@ -1097,8 +1311,8 @@ async function unpublishAndStopTrack(localParticipant, videoTrack) {
1097
1311
  function DomCapture() {
1098
1312
  const { localParticipant } = useLocalParticipant2();
1099
1313
  const connectionState = useConnectionState2();
1100
- const didStartRef = useRef2(false);
1101
- useEffect4(() => {
1314
+ const didStartRef = useRef3(false);
1315
+ useEffect5(() => {
1102
1316
  if (didStartRef.current)
1103
1317
  return;
1104
1318
  if (connectionState !== ConnectionState2.Connected)
@@ -1308,28 +1522,63 @@ var __iconNode3 = [
1308
1522
  ["circle", { cx: "4", cy: "20", r: "2", key: "6kqj1y" }]
1309
1523
  ];
1310
1524
  var Sparkles = createLucideIcon("sparkles", __iconNode3);
1311
- // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/calendar.js
1525
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/bot.js
1312
1526
  var __iconNode4 = [
1527
+ ["path", { d: "M12 8V4H8", key: "hb8ula" }],
1528
+ ["rect", { width: "16", height: "12", x: "4", y: "8", rx: "2", key: "enze0r" }],
1529
+ ["path", { d: "M2 14h2", key: "vft8re" }],
1530
+ ["path", { d: "M20 14h2", key: "4cs60a" }],
1531
+ ["path", { d: "M15 13v2", key: "1xurst" }],
1532
+ ["path", { d: "M9 13v2", key: "rq6x2g" }]
1533
+ ];
1534
+ var Bot = createLucideIcon("bot", __iconNode4);
1535
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/calendar.js
1536
+ var __iconNode5 = [
1313
1537
  ["path", { d: "M8 2v4", key: "1cmpym" }],
1314
1538
  ["path", { d: "M16 2v4", key: "4m81vk" }],
1315
1539
  ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2", key: "1hopcy" }],
1316
1540
  ["path", { d: "M3 10h18", key: "8toen8" }]
1317
1541
  ];
1318
- var Calendar = createLucideIcon("calendar", __iconNode4);
1542
+ var Calendar = createLucideIcon("calendar", __iconNode5);
1319
1543
  // ../../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);
1544
+ var __iconNode6 = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
1545
+ var ChevronDown = createLucideIcon("chevron-down", __iconNode6);
1322
1546
  // ../../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);
1547
+ var __iconNode7 = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
1548
+ var Circle = createLucideIcon("circle", __iconNode7);
1549
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/graduation-cap.js
1550
+ var __iconNode8 = [
1551
+ [
1552
+ "path",
1553
+ {
1554
+ 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",
1555
+ key: "j76jl0"
1556
+ }
1557
+ ],
1558
+ ["path", { d: "M22 10v6", key: "1lu8f3" }],
1559
+ ["path", { d: "M6 12.5V16a6 3 0 0 0 12 0v-3.5", key: "1r8lef" }]
1560
+ ];
1561
+ var GraduationCap = createLucideIcon("graduation-cap", __iconNode8);
1562
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/headset.js
1563
+ var __iconNode9 = [
1564
+ [
1565
+ "path",
1566
+ {
1567
+ 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",
1568
+ key: "12oyoe"
1569
+ }
1570
+ ],
1571
+ ["path", { d: "M21 16v2a4 4 0 0 1-4 4h-5", key: "1x7m43" }]
1572
+ ];
1573
+ var Headset = createLucideIcon("headset", __iconNode9);
1325
1574
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mail.js
1326
- var __iconNode7 = [
1575
+ var __iconNode10 = [
1327
1576
  ["path", { d: "m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7", key: "132q7q" }],
1328
1577
  ["rect", { x: "2", y: "4", width: "20", height: "16", rx: "2", key: "izxlao" }]
1329
1578
  ];
1330
- var Mail = createLucideIcon("mail", __iconNode7);
1579
+ var Mail = createLucideIcon("mail", __iconNode10);
1331
1580
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-circle.js
1332
- var __iconNode8 = [
1581
+ var __iconNode11 = [
1333
1582
  [
1334
1583
  "path",
1335
1584
  {
@@ -1338,9 +1587,9 @@ var __iconNode8 = [
1338
1587
  }
1339
1588
  ]
1340
1589
  ];
1341
- var MessageCircle = createLucideIcon("message-circle", __iconNode8);
1590
+ var MessageCircle = createLucideIcon("message-circle", __iconNode11);
1342
1591
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-square.js
1343
- var __iconNode9 = [
1592
+ var __iconNode12 = [
1344
1593
  [
1345
1594
  "path",
1346
1595
  {
@@ -1349,9 +1598,9 @@ var __iconNode9 = [
1349
1598
  }
1350
1599
  ]
1351
1600
  ];
1352
- var MessageSquare = createLucideIcon("message-square", __iconNode9);
1601
+ var MessageSquare = createLucideIcon("message-square", __iconNode12);
1353
1602
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic-off.js
1354
- var __iconNode10 = [
1603
+ var __iconNode13 = [
1355
1604
  ["path", { d: "M12 19v3", key: "npa21l" }],
1356
1605
  ["path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33", key: "1gzdoj" }],
1357
1606
  ["path", { d: "M16.95 16.95A7 7 0 0 1 5 12v-2", key: "cqa7eg" }],
@@ -1359,40 +1608,40 @@ var __iconNode10 = [
1359
1608
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
1360
1609
  ["path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12", key: "r2i35w" }]
1361
1610
  ];
1362
- var MicOff = createLucideIcon("mic-off", __iconNode10);
1611
+ var MicOff = createLucideIcon("mic-off", __iconNode13);
1363
1612
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic.js
1364
- var __iconNode11 = [
1613
+ var __iconNode14 = [
1365
1614
  ["path", { d: "M12 19v3", key: "npa21l" }],
1366
1615
  ["path", { d: "M19 10v2a7 7 0 0 1-14 0v-2", key: "1vc78b" }],
1367
1616
  ["rect", { x: "9", y: "2", width: "6", height: "13", rx: "3", key: "s6n7sd" }]
1368
1617
  ];
1369
- var Mic = createLucideIcon("mic", __iconNode11);
1618
+ var Mic = createLucideIcon("mic", __iconNode14);
1370
1619
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/minimize-2.js
1371
- var __iconNode12 = [
1620
+ var __iconNode15 = [
1372
1621
  ["path", { d: "m14 10 7-7", key: "oa77jy" }],
1373
1622
  ["path", { d: "M20 10h-6V4", key: "mjg0md" }],
1374
1623
  ["path", { d: "m3 21 7-7", key: "tjx5ai" }],
1375
1624
  ["path", { d: "M4 14h6v6", key: "rmj7iw" }]
1376
1625
  ];
1377
- var Minimize2 = createLucideIcon("minimize-2", __iconNode12);
1626
+ var Minimize2 = createLucideIcon("minimize-2", __iconNode15);
1378
1627
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor-off.js
1379
- var __iconNode13 = [
1628
+ var __iconNode16 = [
1380
1629
  ["path", { d: "M12 17v4", key: "1riwvh" }],
1381
1630
  ["path", { d: "M17 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 1.184-1.826", key: "cv7jms" }],
1382
1631
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
1383
1632
  ["path", { d: "M8 21h8", key: "1ev6f3" }],
1384
1633
  ["path", { d: "M8.656 3H20a2 2 0 0 1 2 2v10a2 2 0 0 1-.293 1.042", key: "z8ni2w" }]
1385
1634
  ];
1386
- var MonitorOff = createLucideIcon("monitor-off", __iconNode13);
1635
+ var MonitorOff = createLucideIcon("monitor-off", __iconNode16);
1387
1636
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor.js
1388
- var __iconNode14 = [
1637
+ var __iconNode17 = [
1389
1638
  ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2", key: "48i651" }],
1390
1639
  ["line", { x1: "8", x2: "16", y1: "21", y2: "21", key: "1svkeh" }],
1391
1640
  ["line", { x1: "12", x2: "12", y1: "17", y2: "21", key: "vw1qmm" }]
1392
1641
  ];
1393
- var Monitor = createLucideIcon("monitor", __iconNode14);
1642
+ var Monitor = createLucideIcon("monitor", __iconNode17);
1394
1643
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mouse-pointer-2.js
1395
- var __iconNode15 = [
1644
+ var __iconNode18 = [
1396
1645
  [
1397
1646
  "path",
1398
1647
  {
@@ -1401,9 +1650,15 @@ var __iconNode15 = [
1401
1650
  }
1402
1651
  ]
1403
1652
  ];
1404
- var MousePointer2 = createLucideIcon("mouse-pointer-2", __iconNode15);
1653
+ var MousePointer2 = createLucideIcon("mouse-pointer-2", __iconNode18);
1654
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/pause.js
1655
+ var __iconNode19 = [
1656
+ ["rect", { x: "14", y: "3", width: "5", height: "18", rx: "1", key: "kaeet6" }],
1657
+ ["rect", { x: "5", y: "3", width: "5", height: "18", rx: "1", key: "1wsw3u" }]
1658
+ ];
1659
+ var Pause = createLucideIcon("pause", __iconNode19);
1405
1660
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/phone-off.js
1406
- var __iconNode16 = [
1661
+ var __iconNode20 = [
1407
1662
  [
1408
1663
  "path",
1409
1664
  {
@@ -1420,9 +1675,40 @@ var __iconNode16 = [
1420
1675
  }
1421
1676
  ]
1422
1677
  ];
1423
- var PhoneOff = createLucideIcon("phone-off", __iconNode16);
1678
+ var PhoneOff = createLucideIcon("phone-off", __iconNode20);
1679
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/play.js
1680
+ var __iconNode21 = [
1681
+ [
1682
+ "path",
1683
+ {
1684
+ d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z",
1685
+ key: "10ikf1"
1686
+ }
1687
+ ]
1688
+ ];
1689
+ var Play = createLucideIcon("play", __iconNode21);
1690
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/rocket.js
1691
+ var __iconNode22 = [
1692
+ ["path", { d: "M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5", key: "qeys4" }],
1693
+ [
1694
+ "path",
1695
+ {
1696
+ 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",
1697
+ key: "u4xsad"
1698
+ }
1699
+ ],
1700
+ [
1701
+ "path",
1702
+ {
1703
+ 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",
1704
+ key: "676m9"
1705
+ }
1706
+ ],
1707
+ ["path", { d: "M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05", key: "92ym6u" }]
1708
+ ];
1709
+ var Rocket = createLucideIcon("rocket", __iconNode22);
1424
1710
  // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/send.js
1425
- var __iconNode17 = [
1711
+ var __iconNode23 = [
1426
1712
  [
1427
1713
  "path",
1428
1714
  {
@@ -1432,9 +1718,17 @@ var __iconNode17 = [
1432
1718
  ],
1433
1719
  ["path", { d: "m21.854 2.147-10.94 10.939", key: "12cjpa" }]
1434
1720
  ];
1435
- var Send = createLucideIcon("send", __iconNode17);
1721
+ var Send = createLucideIcon("send", __iconNode23);
1722
+ // ../../node_modules/.bun/lucide-react@1.8.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/user-plus.js
1723
+ var __iconNode24 = [
1724
+ ["path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2", key: "1yyitq" }],
1725
+ ["circle", { cx: "9", cy: "7", r: "4", key: "nufk8" }],
1726
+ ["line", { x1: "19", x2: "19", y1: "8", y2: "14", key: "1bvyxn" }],
1727
+ ["line", { x1: "22", x2: "16", y1: "11", y2: "11", key: "1shjgl" }]
1728
+ ];
1729
+ var UserPlus = createLucideIcon("user-plus", __iconNode24);
1436
1730
  // src/components/HighlightOverlay.tsx
1437
- import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef3, useState as useState3 } from "react";
1731
+ import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef4, useState as useState4 } from "react";
1438
1732
  import { jsx, jsxs } from "react/jsx-runtime";
1439
1733
  var Z_INDEX = 2147483646;
1440
1734
  var HIGHLIGHT_PADDING = 6;
@@ -1538,14 +1832,14 @@ function findScrollableAncestor(el) {
1538
1832
  return el.ownerDocument?.documentElement ?? document.documentElement;
1539
1833
  }
1540
1834
  function HighlightOverlay() {
1541
- const [overlayState, setOverlayState] = useState3(null);
1542
- const targetElementRef = useRef3(null);
1543
- const pendingFrameRef = useRef3(null);
1544
- const clearOverlay = useCallback3(() => {
1835
+ const [overlayState, setOverlayState] = useState4(null);
1836
+ const targetElementRef = useRef4(null);
1837
+ const pendingFrameRef = useRef4(null);
1838
+ const clearOverlay = useCallback4(() => {
1545
1839
  targetElementRef.current = null;
1546
1840
  setOverlayState(null);
1547
1841
  }, []);
1548
- const recomputeRect = useCallback3(() => {
1842
+ const recomputeRect = useCallback4(() => {
1549
1843
  pendingFrameRef.current = null;
1550
1844
  const target = targetElementRef.current;
1551
1845
  if (!target)
@@ -1557,12 +1851,12 @@ function HighlightOverlay() {
1557
1851
  const rect2 = getRectInTopViewport(target);
1558
1852
  setOverlayState((prev) => prev ? { ...prev, rect: rect2 } : prev);
1559
1853
  }, [clearOverlay]);
1560
- const scheduleRecompute = useCallback3(() => {
1854
+ const scheduleRecompute = useCallback4(() => {
1561
1855
  if (pendingFrameRef.current !== null)
1562
1856
  return;
1563
1857
  pendingFrameRef.current = window.requestAnimationFrame(recomputeRect);
1564
1858
  }, [recomputeRect]);
1565
- const onHighlightMessage = useCallback3((msg) => {
1859
+ const onHighlightMessage = useCallback4((msg) => {
1566
1860
  const parsed = parseHighlightMessage(msg.payload);
1567
1861
  if (!parsed)
1568
1862
  return;
@@ -1583,7 +1877,7 @@ function HighlightOverlay() {
1583
1877
  });
1584
1878
  }, [clearOverlay]);
1585
1879
  useDataChannel(HIGHLIGHT_TOPIC, onHighlightMessage);
1586
- useEffect5(() => {
1880
+ useEffect6(() => {
1587
1881
  if (!overlayState)
1588
1882
  return;
1589
1883
  const target = targetElementRef.current;
@@ -1665,23 +1959,36 @@ function HighlightOverlay() {
1665
1959
  }
1666
1960
 
1667
1961
  // src/components/MinimizedBubble.tsx
1668
- import { useEffect as useEffect6 } from "react";
1962
+ import { useEffect as useEffect7 } from "react";
1963
+
1964
+ // src/lib/react-compat.ts
1965
+ import * as React from "react";
1966
+ function selectContextHook(use2, useContext3) {
1967
+ return use2 ?? useContext3;
1968
+ }
1969
+ var useContextValue = selectContextHook(React.use, React.useContext);
1669
1970
 
1670
1971
  // src/hooks/useLiveAgent.ts
1671
- import { use } from "react";
1672
1972
  function useLiveAgent() {
1673
- const ctx = use(LiveAgentContext);
1973
+ const ctx = useContextValue(LiveAgentContext);
1674
1974
  if (!ctx) {
1675
1975
  throw new Error("useLiveAgent must be used within a <LiveAgent> provider");
1676
1976
  }
1677
- const { connection, shouldConnect, ...publicValue } = ctx;
1977
+ const {
1978
+ connection,
1979
+ shouldConnect,
1980
+ historyMessages,
1981
+ phasesSnapshot,
1982
+ setPhasesSnapshot,
1983
+ ...publicValue
1984
+ } = ctx;
1678
1985
  return publicValue;
1679
1986
  }
1680
1987
 
1681
1988
  // src/hooks/useMediaControls.ts
1682
1989
  import { useLocalParticipant as useLocalParticipant3 } from "@livekit/components-react/hooks";
1683
1990
  import { ScreenSharePresets as ScreenSharePresets2 } from "livekit-client";
1684
- import { useCallback as useCallback4 } from "react";
1991
+ import { useCallback as useCallback5 } from "react";
1685
1992
  var SCREEN_SHARE_OPTIONS = {
1686
1993
  video: { displaySurface: "browser" },
1687
1994
  resolution: ScreenSharePresets2.h720fps30.resolution,
@@ -1691,14 +1998,14 @@ function useMediaControls() {
1691
1998
  const { localParticipant } = useLocalParticipant3();
1692
1999
  const isMuted = !localParticipant.isMicrophoneEnabled;
1693
2000
  const isScreenSharing = localParticipant.isScreenShareEnabled;
1694
- const toggleMute = useCallback4(async () => {
2001
+ const toggleMute = useCallback5(async () => {
1695
2002
  try {
1696
2003
  await localParticipant.setMicrophoneEnabled(isMuted);
1697
2004
  } catch (error) {
1698
2005
  console.error("Failed to toggle microphone:", error);
1699
2006
  }
1700
2007
  }, [localParticipant, isMuted]);
1701
- const toggleScreenShare = useCallback4(async () => {
2008
+ const toggleScreenShare = useCallback5(async () => {
1702
2009
  try {
1703
2010
  await localParticipant.setScreenShareEnabled(!isScreenSharing, SCREEN_SHARE_OPTIONS);
1704
2011
  } catch (error) {
@@ -1736,25 +2043,50 @@ function pillStatusFromAgent(state, canSeePage) {
1736
2043
  return canSeePage ? "observing" : "connected";
1737
2044
  }
1738
2045
  function LauncherStatusPill() {
1739
- const { isConnected, expandPanel, setSidebarTab, position, captureMode } = useLiveAgent();
2046
+ const { isConnected, isStarting, isPaused, expandPanel, setSidebarTab, position, captureMode } = useLiveAgent();
1740
2047
  const { state } = useAgentVoiceState();
1741
2048
  const { isScreenSharing } = useMediaControls();
1742
- if (!isConnected)
2049
+ const isStandingBy = isStarting && !isConnected;
2050
+ if (!isConnected && !isStandingBy && !isPaused)
1743
2051
  return null;
1744
2052
  const canSeePage = captureMode === "auto" || isScreenSharing;
1745
- const status = pillStatusFromAgent(state, canSeePage);
1746
- const openChat = () => {
1747
- setSidebarTab("chat");
2053
+ let status;
2054
+ if (isPaused) {
2055
+ status = "paused";
2056
+ } else if (isStandingBy) {
2057
+ status = "standing-by";
2058
+ } else {
2059
+ status = pillStatusFromAgent(state, canSeePage);
2060
+ }
2061
+ let ariaLabel;
2062
+ if (isStandingBy) {
2063
+ ariaLabel = "Skippr is standing by — click to open";
2064
+ } else if (isPaused) {
2065
+ ariaLabel = "Session paused — click to open";
2066
+ } else {
2067
+ ariaLabel = `Skippr is ${status} — click to open chat`;
2068
+ }
2069
+ const handleClick = () => {
2070
+ if (!isStandingBy)
2071
+ setSidebarTab("chat");
1748
2072
  expandPanel();
1749
2073
  };
1750
2074
  return /* @__PURE__ */ jsx2("button", {
1751
2075
  type: "button",
1752
- onClick: openChat,
2076
+ onClick: handleClick,
1753
2077
  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`,
2078
+ "aria-label": ariaLabel,
1755
2079
  children: /* @__PURE__ */ jsxs2("span", {
1756
2080
  className: "skippr:flex skippr:items-center skippr:gap-2 skippr:animate-[skippr-pill-content_0.22s_ease-out]",
1757
2081
  children: [
2082
+ status === "standing-by" && /* @__PURE__ */ jsxs2(Fragment, {
2083
+ children: [
2084
+ /* @__PURE__ */ jsx2("span", {
2085
+ children: "Standing by"
2086
+ }),
2087
+ /* @__PURE__ */ jsx2(ThinkingDots, {})
2088
+ ]
2089
+ }),
1758
2090
  status === "observing" && /* @__PURE__ */ jsxs2(Fragment, {
1759
2091
  children: [
1760
2092
  /* @__PURE__ */ jsx2(ObservingIcon, {}),
@@ -1786,6 +2118,14 @@ function LauncherStatusPill() {
1786
2118
  children: "Skippr is thinking"
1787
2119
  })
1788
2120
  ]
2121
+ }),
2122
+ status === "paused" && /* @__PURE__ */ jsxs2(Fragment, {
2123
+ children: [
2124
+ /* @__PURE__ */ jsx2(PausedDot, {}),
2125
+ /* @__PURE__ */ jsx2("span", {
2126
+ children: "Session paused"
2127
+ })
2128
+ ]
1789
2129
  })
1790
2130
  ]
1791
2131
  }, status)
@@ -1804,6 +2144,11 @@ function ConnectedDot() {
1804
2144
  ]
1805
2145
  });
1806
2146
  }
2147
+ function PausedDot() {
2148
+ return /* @__PURE__ */ jsx2("span", {
2149
+ className: "skippr:inline-flex skippr:size-2 skippr:rounded-full skippr:bg-amber-400"
2150
+ });
2151
+ }
1807
2152
  function ObservingIcon() {
1808
2153
  return /* @__PURE__ */ jsxs2("svg", {
1809
2154
  viewBox: "0 0 24 24",
@@ -1955,34 +2300,60 @@ import { jsx as jsx4, jsxs as jsxs4, Fragment as Fragment2 } from "react/jsx-run
1955
2300
  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";
1956
2301
  var CONTROL_SHADOW = "skippr:shadow-[0_4px_16px_rgba(0,0,0,0.15),0_2px_4px_rgba(0,0,0,0.1)]";
1957
2302
  function ConnectedLauncher() {
1958
- const { expandPanel, disconnect, captureMode, setSidebarTab } = useLiveAgent();
2303
+ const {
2304
+ expandPanel,
2305
+ disconnect,
2306
+ pauseSession,
2307
+ resumeSession,
2308
+ isPaused,
2309
+ isPausing,
2310
+ captureMode,
2311
+ setSidebarTab
2312
+ } = useLiveAgent();
1959
2313
  const { isMuted, toggleMute, isScreenSharing, toggleScreenShare } = useMediaControls();
1960
2314
  const showScreenShareToggle = captureMode === "screenshare";
2315
+ const showPaused = isPaused || isPausing;
1961
2316
  const openChat = () => {
1962
2317
  setSidebarTab("chat");
1963
2318
  expandPanel();
1964
2319
  };
1965
2320
  return /* @__PURE__ */ jsxs4(Fragment2, {
1966
2321
  children: [
1967
- /* @__PURE__ */ jsx4("button", {
1968
- type: "button",
1969
- onClick: toggleMute,
1970
- "aria-label": isMuted ? "Unmute" : "Mute",
1971
- className: cn(CONTROL_BUTTON, CONTROL_SHADOW, isMuted ? "skippr:bg-destructive/10 skippr:text-destructive skippr:hover:bg-destructive/20" : "skippr:bg-white skippr:text-foreground skippr:hover:bg-muted"),
1972
- children: isMuted ? /* @__PURE__ */ jsx4(MicOff, {
1973
- className: "skippr:size-5"
1974
- }) : /* @__PURE__ */ jsx4(Mic, {
1975
- className: "skippr:size-5"
1976
- })
2322
+ !showPaused && /* @__PURE__ */ jsxs4(Fragment2, {
2323
+ children: [
2324
+ /* @__PURE__ */ jsx4("button", {
2325
+ type: "button",
2326
+ onClick: toggleMute,
2327
+ "aria-label": isMuted ? "Unmute" : "Mute",
2328
+ className: cn(CONTROL_BUTTON, CONTROL_SHADOW, isMuted ? "skippr:bg-destructive/10 skippr:text-destructive skippr:hover:bg-destructive/20" : "skippr:bg-white skippr:text-foreground skippr:hover:bg-muted"),
2329
+ children: isMuted ? /* @__PURE__ */ jsx4(MicOff, {
2330
+ className: "skippr:size-5"
2331
+ }) : /* @__PURE__ */ jsx4(Mic, {
2332
+ className: "skippr:size-5"
2333
+ })
2334
+ }),
2335
+ showScreenShareToggle && /* @__PURE__ */ jsx4("button", {
2336
+ type: "button",
2337
+ onClick: toggleScreenShare,
2338
+ "aria-label": isScreenSharing ? "Stop sharing screen" : "Share screen",
2339
+ className: cn(CONTROL_BUTTON, CONTROL_SHADOW, isScreenSharing ? "skippr:bg-primary skippr:text-primary-foreground skippr:hover:bg-primary/90" : "skippr:bg-white skippr:text-foreground skippr:hover:bg-muted"),
2340
+ children: isScreenSharing ? /* @__PURE__ */ jsx4(MonitorOff, {
2341
+ className: "skippr:size-5"
2342
+ }) : /* @__PURE__ */ jsx4(Monitor, {
2343
+ className: "skippr:size-5"
2344
+ })
2345
+ })
2346
+ ]
1977
2347
  }),
1978
- showScreenShareToggle && /* @__PURE__ */ jsx4("button", {
2348
+ /* @__PURE__ */ jsx4("button", {
1979
2349
  type: "button",
1980
- onClick: toggleScreenShare,
1981
- "aria-label": isScreenSharing ? "Stop sharing screen" : "Share screen",
1982
- className: cn(CONTROL_BUTTON, CONTROL_SHADOW, isScreenSharing ? "skippr:bg-primary skippr:text-primary-foreground skippr:hover:bg-primary/90" : "skippr:bg-white skippr:text-foreground skippr:hover:bg-muted"),
1983
- children: isScreenSharing ? /* @__PURE__ */ jsx4(MonitorOff, {
2350
+ onClick: () => isPaused ? resumeSession() : pauseSession(),
2351
+ disabled: isPausing,
2352
+ "aria-label": showPaused ? "Resume session" : "Pause session",
2353
+ className: cn(CONTROL_BUTTON, CONTROL_SHADOW, "skippr:bg-white skippr:text-foreground skippr:hover:bg-muted skippr:disabled:opacity-60"),
2354
+ children: showPaused ? /* @__PURE__ */ jsx4(Play, {
1984
2355
  className: "skippr:size-5"
1985
- }) : /* @__PURE__ */ jsx4(Monitor, {
2356
+ }) : /* @__PURE__ */ jsx4(Pause, {
1986
2357
  className: "skippr:size-5"
1987
2358
  })
1988
2359
  }),
@@ -1995,7 +2366,7 @@ function ConnectedLauncher() {
1995
2366
  className: "skippr:size-5"
1996
2367
  })
1997
2368
  }),
1998
- /* @__PURE__ */ jsx4("button", {
2369
+ !showPaused && /* @__PURE__ */ jsx4("button", {
1999
2370
  type: "button",
2000
2371
  onClick: openChat,
2001
2372
  "aria-label": "Open chat",
@@ -2024,7 +2395,7 @@ function WelcomeBubble({
2024
2395
  position,
2025
2396
  onDismiss
2026
2397
  }) {
2027
- useEffect6(() => {
2398
+ useEffect7(() => {
2028
2399
  const timer = setTimeout(onDismiss, 5000);
2029
2400
  return () => clearTimeout(timer);
2030
2401
  }, [onDismiss]);
@@ -2046,15 +2417,15 @@ function MinimizedBubble({
2046
2417
  welcomeDismissed,
2047
2418
  onDismissWelcome
2048
2419
  }) {
2049
- const { isConnected, isStarting, position } = useLiveAgent();
2050
- const inSession = isConnected || isStarting;
2420
+ const { isConnected, isStarting, isPaused, isPausing, position } = useLiveAgent();
2421
+ const inSession = isConnected || isPaused || isPausing;
2051
2422
  return /* @__PURE__ */ jsxs4(Fragment2, {
2052
2423
  children: [
2053
2424
  /* @__PURE__ */ jsx4(LauncherStatusPill, {}),
2054
2425
  /* @__PURE__ */ jsxs4("div", {
2055
2426
  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
2427
  children: [
2057
- welcomeMessage && !inSession && !welcomeDismissed && /* @__PURE__ */ jsx4(WelcomeBubble, {
2428
+ welcomeMessage && !isConnected && !isStarting && !welcomeDismissed && /* @__PURE__ */ jsx4(WelcomeBubble, {
2058
2429
  message: welcomeMessage,
2059
2430
  position,
2060
2431
  onDismiss: onDismissWelcome
@@ -2067,7 +2438,7 @@ function MinimizedBubble({
2067
2438
  }
2068
2439
 
2069
2440
  // src/components/Sidebar.tsx
2070
- import { useEffect as useEffect13 } from "react";
2441
+ import { useEffect as useEffect16 } from "react";
2071
2442
 
2072
2443
  // src/hooks/useCombinedMessages.ts
2073
2444
  import { useMemo as useMemo4 } from "react";
@@ -2138,26 +2509,33 @@ function useCombinedMessages() {
2138
2509
  const { transcriptMessages } = useStreamingTranscript();
2139
2510
  const { chatMessages, sendChatMessage, isSendingChat } = useChatMessages();
2140
2511
  const { state: agentState } = useAgentVoiceState();
2141
- const allMessages = useMemo4(() => {
2512
+ const historyMessages = useContextValue(LiveAgentContext)?.historyMessages ?? [];
2513
+ const liveMessages = useMemo4(() => {
2142
2514
  if (chatMessages.length === 0)
2143
2515
  return transcriptMessages;
2144
2516
  if (transcriptMessages.length === 0)
2145
2517
  return chatMessages;
2146
2518
  return mergeChatsIntoTranscripts(transcriptMessages, chatMessages);
2147
2519
  }, [transcriptMessages, chatMessages]);
2520
+ const allMessages = useMemo4(() => {
2521
+ if (historyMessages.length === 0)
2522
+ return liveMessages;
2523
+ const seenIds = new Set(liveMessages.map((message) => message.id));
2524
+ return [...historyMessages.filter((message) => !seenIds.has(message.id)), ...liveMessages];
2525
+ }, [historyMessages, liveMessages]);
2148
2526
  return { allMessages, agentState, sendChatMessage, isSendingChat };
2149
2527
  }
2150
2528
 
2151
2529
  // src/hooks/usePhaseUpdates.ts
2152
- import { useCallback as useCallback5 } from "react";
2530
+ import { useCallback as useCallback6, useEffect as useEffect9 } from "react";
2153
2531
 
2154
2532
  // src/hooks/useAgentState.ts
2155
2533
  import { useRemoteParticipants } from "@livekit/components-react/hooks";
2156
- import { useEffect as useEffect7, useState as useState4 } from "react";
2534
+ import { useEffect as useEffect8, useState as useState5 } from "react";
2157
2535
  function useAgentState(attributeKey, parse, initial) {
2158
- const [value, setValue] = useState4(initial);
2536
+ const [value, setValue] = useState5(initial);
2159
2537
  const remoteParticipants = useRemoteParticipants();
2160
- useEffect7(() => {
2538
+ useEffect8(() => {
2161
2539
  const agentParticipant = remoteParticipants.find((p) => p.attributes?.[attributeKey]);
2162
2540
  if (agentParticipant) {
2163
2541
  const attr = agentParticipant.attributes?.[attributeKey];
@@ -2203,13 +2581,19 @@ function parsePhases(json) {
2203
2581
  return null;
2204
2582
  }
2205
2583
  function usePhaseUpdates() {
2206
- const parse = useCallback5(parsePhases, []);
2207
- const phases = useAgentState("phases", parse, []);
2584
+ const parse = useCallback6(parsePhases, []);
2585
+ const livePhases = useAgentState("phases", parse, []);
2586
+ const ctx = useContextValue(LiveAgentContext);
2587
+ useEffect9(() => {
2588
+ if (livePhases.length > 0)
2589
+ ctx?.setPhasesSnapshot(livePhases);
2590
+ }, [livePhases, ctx?.setPhasesSnapshot]);
2591
+ const phases = livePhases.length > 0 ? livePhases : ctx?.phasesSnapshot ?? [];
2208
2592
  return { phases };
2209
2593
  }
2210
2594
 
2211
2595
  // src/hooks/useSessionRemaining.ts
2212
- import { useEffect as useEffect8, useRef as useRef4, useState as useState5 } from "react";
2596
+ import { useEffect as useEffect10, useRef as useRef5, useState as useState6 } from "react";
2213
2597
 
2214
2598
  // src/lib/format.ts
2215
2599
  function formatTime(seconds) {
@@ -2225,9 +2609,9 @@ function parseNumber(s) {
2225
2609
  // src/hooks/useSessionRemaining.ts
2226
2610
  function useSessionRemaining() {
2227
2611
  const maxCallDuration = useAgentState("maxCallDuration", parseNumber, null);
2228
- const endTimeRef = useRef4(null);
2229
- const [remaining, setRemaining] = useState5(null);
2230
- useEffect8(() => {
2612
+ const endTimeRef = useRef5(null);
2613
+ const [remaining, setRemaining] = useState6(null);
2614
+ useEffect10(() => {
2231
2615
  if (maxCallDuration === null || endTimeRef.current !== null)
2232
2616
  return;
2233
2617
  const endTime = Date.now() + maxCallDuration * 1000;
@@ -2244,10 +2628,10 @@ function useSessionRemaining() {
2244
2628
  }
2245
2629
 
2246
2630
  // src/hooks/useElapsedSeconds.ts
2247
- import { useEffect as useEffect9, useState as useState6 } from "react";
2631
+ import { useEffect as useEffect11, useState as useState7 } from "react";
2248
2632
  function useElapsedSeconds(isRunning) {
2249
- const [elapsed, setElapsed] = useState6(0);
2250
- useEffect9(() => {
2633
+ const [elapsed, setElapsed] = useState7(0);
2634
+ useEffect11(() => {
2251
2635
  if (!isRunning) {
2252
2636
  setElapsed(0);
2253
2637
  return;
@@ -2345,7 +2729,7 @@ function LoadingDots({ label }) {
2345
2729
  }
2346
2730
 
2347
2731
  // src/components/LoginFlow.tsx
2348
- import { useCallback as useCallback6, useEffect as useEffect10, useRef as useRef5, useState as useState7 } from "react";
2732
+ import { useCallback as useCallback7, useEffect as useEffect12, useRef as useRef6, useState as useState8 } from "react";
2349
2733
 
2350
2734
  // src/components/ui/button.tsx
2351
2735
  import { forwardRef as forwardRef3 } from "react";
@@ -2381,20 +2765,20 @@ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2381
2765
  var OTP_LENGTH = 6;
2382
2766
  var DIGIT_KEYS = ["d0", "d1", "d2", "d3", "d4", "d5"];
2383
2767
  function LoginFlow({ requestOtp, verifyOtp, error, isSubmitting }) {
2384
- const [step, setStep] = useState7("email");
2385
- const [email, setEmail] = useState7("");
2386
- const handleRequestOtp = useCallback6(async (emailValue) => {
2768
+ const [step, setStep] = useState8("email");
2769
+ const [email, setEmail] = useState8("");
2770
+ const handleRequestOtp = useCallback7(async (emailValue) => {
2387
2771
  const success = await requestOtp(emailValue);
2388
2772
  if (success)
2389
2773
  setStep("otp");
2390
2774
  }, [requestOtp]);
2391
- const handleVerifyOtp = useCallback6(async (code) => {
2775
+ const handleVerifyOtp = useCallback7(async (code) => {
2392
2776
  await verifyOtp(email, code);
2393
2777
  }, [verifyOtp, email]);
2394
- const handleBack = useCallback6(() => {
2778
+ const handleBack = useCallback7(() => {
2395
2779
  setStep("email");
2396
2780
  }, []);
2397
- const handleResend = useCallback6(async () => {
2781
+ const handleResend = useCallback7(async () => {
2398
2782
  await requestOtp(email);
2399
2783
  }, [requestOtp, email]);
2400
2784
  if (step === "otp") {
@@ -2471,30 +2855,30 @@ function EmailStep({ email, onEmailChange, onSubmit, error, isSubmitting }) {
2471
2855
  });
2472
2856
  }
2473
2857
  function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
2474
- const [digits, setDigits] = useState7(Array(OTP_LENGTH).fill(""));
2475
- const [resendCooldown, setResendCooldown] = useState7(0);
2476
- const inputRefs = useRef5([]);
2477
- const submittedRef = useRef5(false);
2478
- useEffect10(() => {
2858
+ const [digits, setDigits] = useState8(Array(OTP_LENGTH).fill(""));
2859
+ const [resendCooldown, setResendCooldown] = useState8(0);
2860
+ const inputRefs = useRef6([]);
2861
+ const submittedRef = useRef6(false);
2862
+ useEffect12(() => {
2479
2863
  inputRefs.current[0]?.focus();
2480
2864
  }, []);
2481
- useEffect10(() => {
2865
+ useEffect12(() => {
2482
2866
  if (error)
2483
2867
  submittedRef.current = false;
2484
2868
  }, [error]);
2485
- useEffect10(() => {
2869
+ useEffect12(() => {
2486
2870
  if (resendCooldown <= 0)
2487
2871
  return;
2488
2872
  const timer = setTimeout(() => setResendCooldown((c) => c - 1), 1000);
2489
2873
  return () => clearTimeout(timer);
2490
2874
  }, [resendCooldown]);
2491
- const submitCode = useCallback6((code) => {
2875
+ const submitCode = useCallback7((code) => {
2492
2876
  if (submittedRef.current || isSubmitting)
2493
2877
  return;
2494
2878
  submittedRef.current = true;
2495
2879
  onSubmit(code);
2496
2880
  }, [onSubmit, isSubmitting]);
2497
- const handleDigitChange = useCallback6((index2, value) => {
2881
+ const handleDigitChange = useCallback7((index2, value) => {
2498
2882
  const digit = value.replace(/\D/g, "").slice(-1);
2499
2883
  const newDigits = [...digits];
2500
2884
  newDigits[index2] = digit;
@@ -2508,12 +2892,12 @@ function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
2508
2892
  submitCode(code);
2509
2893
  }
2510
2894
  }, [digits, submitCode]);
2511
- const handleKeyDown = useCallback6((index2, e) => {
2895
+ const handleKeyDown = useCallback7((index2, e) => {
2512
2896
  if (e.key === "Backspace" && !digits[index2] && index2 > 0) {
2513
2897
  inputRefs.current[index2 - 1]?.focus();
2514
2898
  }
2515
2899
  }, [digits]);
2516
- const handlePaste = useCallback6((e) => {
2900
+ const handlePaste = useCallback7((e) => {
2517
2901
  e.preventDefault();
2518
2902
  const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, OTP_LENGTH);
2519
2903
  if (pasted.length > 0) {
@@ -2623,35 +3007,66 @@ function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
2623
3007
  }
2624
3008
 
2625
3009
  // src/components/MeetingControls.tsx
2626
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2627
- var CONTROL_BUTTON2 = "skippr:flex skippr:size-11 skippr:cursor-pointer skippr:items-center skippr:justify-center skippr:rounded-full skippr:transition-colors";
2628
- function MeetingControls({ onHangUp, showScreenShareToggle = true }) {
3010
+ import { jsx as jsx9, jsxs as jsxs8, Fragment as Fragment3 } from "react/jsx-runtime";
3011
+ var CONTROL_BUTTON2 = "skippr:flex skippr:size-11 skippr:cursor-pointer skippr:items-center skippr:justify-center skippr:rounded-full skippr:transition-colors skippr:disabled:cursor-not-allowed skippr:disabled:opacity-60";
3012
+ var MUTED_BUTTON = "skippr:bg-muted skippr:text-foreground skippr:hover:bg-muted/80";
3013
+ function MeetingControls({
3014
+ onHangUp,
3015
+ onPause,
3016
+ onResume,
3017
+ isPaused = false,
3018
+ isPausing = false,
3019
+ showScreenShareToggle = true
3020
+ }) {
2629
3021
  const { isMuted, toggleMute, isScreenSharing, toggleScreenShare } = useMediaControls();
3022
+ const showPaused = isPaused || isPausing;
2630
3023
  return /* @__PURE__ */ jsxs8("div", {
2631
3024
  className: "skippr:shrink-0 skippr:border-t skippr:border-border skippr:bg-background skippr:px-4 skippr:py-4",
2632
3025
  children: [
2633
3026
  /* @__PURE__ */ jsxs8("div", {
2634
3027
  className: "skippr:flex skippr:items-center skippr:justify-center skippr:gap-3",
2635
3028
  children: [
2636
- /* @__PURE__ */ jsx9("button", {
3029
+ !showPaused && /* @__PURE__ */ jsxs8(Fragment3, {
3030
+ children: [
3031
+ /* @__PURE__ */ jsx9("button", {
3032
+ type: "button",
3033
+ onClick: toggleMute,
3034
+ "aria-label": isMuted ? "Unmute" : "Mute",
3035
+ className: cn(CONTROL_BUTTON2, isMuted ? "skippr:bg-destructive/15 skippr:text-destructive skippr:hover:bg-destructive/25" : MUTED_BUTTON),
3036
+ children: isMuted ? /* @__PURE__ */ jsx9(MicOff, {
3037
+ className: "skippr:size-5"
3038
+ }) : /* @__PURE__ */ jsx9(Mic, {
3039
+ className: "skippr:size-5"
3040
+ })
3041
+ }),
3042
+ showScreenShareToggle && /* @__PURE__ */ jsx9("button", {
3043
+ type: "button",
3044
+ onClick: toggleScreenShare,
3045
+ "aria-label": isScreenSharing ? "Stop sharing screen" : "Share screen",
3046
+ className: cn(CONTROL_BUTTON2, isScreenSharing ? "skippr:bg-bubble skippr:text-white skippr:hover:brightness-110" : MUTED_BUTTON),
3047
+ children: isScreenSharing ? /* @__PURE__ */ jsx9(MonitorOff, {
3048
+ className: "skippr:size-5"
3049
+ }) : /* @__PURE__ */ jsx9(Monitor, {
3050
+ className: "skippr:size-5"
3051
+ })
3052
+ })
3053
+ ]
3054
+ }),
3055
+ showPaused ? onResume && /* @__PURE__ */ jsx9("button", {
2637
3056
  type: "button",
2638
- onClick: toggleMute,
2639
- "aria-label": isMuted ? "Unmute" : "Mute",
2640
- className: cn(CONTROL_BUTTON2, isMuted ? "skippr:bg-destructive/15 skippr:text-destructive skippr:hover:bg-destructive/25" : "skippr:bg-muted skippr:text-foreground skippr:hover:bg-muted/80"),
2641
- children: isMuted ? /* @__PURE__ */ jsx9(MicOff, {
2642
- className: "skippr:size-5"
2643
- }) : /* @__PURE__ */ jsx9(Mic, {
3057
+ onClick: onResume,
3058
+ disabled: isPausing,
3059
+ "aria-label": "Resume session",
3060
+ className: cn(CONTROL_BUTTON2, MUTED_BUTTON),
3061
+ children: /* @__PURE__ */ jsx9(Play, {
2644
3062
  className: "skippr:size-5"
2645
3063
  })
2646
- }),
2647
- showScreenShareToggle && /* @__PURE__ */ jsx9("button", {
3064
+ }) : onPause && /* @__PURE__ */ jsx9("button", {
2648
3065
  type: "button",
2649
- onClick: toggleScreenShare,
2650
- "aria-label": isScreenSharing ? "Stop sharing screen" : "Share screen",
2651
- className: cn(CONTROL_BUTTON2, isScreenSharing ? "skippr:bg-bubble skippr:text-white skippr:hover:brightness-110" : "skippr:bg-muted skippr:text-foreground skippr:hover:bg-muted/80"),
2652
- children: isScreenSharing ? /* @__PURE__ */ jsx9(MonitorOff, {
2653
- className: "skippr:size-5"
2654
- }) : /* @__PURE__ */ jsx9(Monitor, {
3066
+ onClick: onPause,
3067
+ "aria-label": "Pause session",
3068
+ className: cn(CONTROL_BUTTON2, MUTED_BUTTON),
3069
+ children: /* @__PURE__ */ jsx9(Pause, {
2655
3070
  className: "skippr:size-5"
2656
3071
  })
2657
3072
  }),
@@ -2675,17 +3090,17 @@ function MeetingControls({ onHangUp, showScreenShareToggle = true }) {
2675
3090
  }
2676
3091
 
2677
3092
  // src/components/MessageList.tsx
2678
- import { useEffect as useEffect12, useRef as useRef7 } from "react";
3093
+ import { useEffect as useEffect14, useRef as useRef8 } from "react";
2679
3094
 
2680
3095
  // src/components/ChatInput.tsx
2681
- import { useEffect as useEffect11, useRef as useRef6, useState as useState8 } from "react";
3096
+ import { useEffect as useEffect13, useRef as useRef7, useState as useState9 } from "react";
2682
3097
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2683
3098
  var MAX_INPUT_HEIGHT = 60;
2684
3099
  function ChatInput({ sendChatMessage, isSendingChat, autoFocus = false }) {
2685
- const [inputText, setInputText] = useState8("");
2686
- const textareaRef = useRef6(null);
3100
+ const [inputText, setInputText] = useState9("");
3101
+ const textareaRef = useRef7(null);
2687
3102
  const canSend = inputText.trim().length > 0 && !isSendingChat;
2688
- useEffect11(() => {
3103
+ useEffect13(() => {
2689
3104
  if (autoFocus)
2690
3105
  textareaRef.current?.focus();
2691
3106
  }, [autoFocus]);
@@ -2830,9 +3245,9 @@ function MessageList({
2830
3245
  isSendingChat,
2831
3246
  autoFocus = false
2832
3247
  }) {
2833
- const scrollRef = useRef7(null);
3248
+ const scrollRef = useRef8(null);
2834
3249
  const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
2835
- useEffect12(() => {
3250
+ useEffect14(() => {
2836
3251
  scrollRef.current?.scrollIntoView({ behavior: "smooth" });
2837
3252
  }, [messages.length, lastMessage?.content]);
2838
3253
  const showTyping = isStreaming && lastMessage?.role === "assistant" && lastMessage.content === "";
@@ -2863,50 +3278,222 @@ function MessageList({
2863
3278
  });
2864
3279
  }
2865
3280
 
2866
- // src/components/SessionAgenda.tsx
3281
+ // src/components/ModuleSelector.tsx
3282
+ import { useEffect as useEffect15, useMemo as useMemo5, useRef as useRef9, useState as useState10 } from "react";
2867
3283
  import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
3284
+ var AGENT_TYPE_ICONS = {
3285
+ onboarding: UserPlus,
3286
+ demo: Rocket,
3287
+ training: GraduationCap,
3288
+ support: Headset
3289
+ };
3290
+ function getAgentIcon(type) {
3291
+ return AGENT_TYPE_ICONS[type] ?? Bot;
3292
+ }
3293
+ function ModuleSelector() {
3294
+ const {
3295
+ availableModules,
3296
+ isLoadingModules,
3297
+ modulesError,
3298
+ refetchModules,
3299
+ selectModule,
3300
+ isStarting,
3301
+ isDisconnecting,
3302
+ error,
3303
+ resumableSessions
3304
+ } = useLiveAgent();
3305
+ const isBusy = isStarting || isDisconnecting;
3306
+ const resumableAgentIds = useMemo5(() => new Set(resumableSessions.map((s) => s.agentId)), [resumableSessions]);
3307
+ const scrollRef = useRef9(null);
3308
+ const [showScrollHint, setShowScrollHint] = useState10(false);
3309
+ const [isScrolled, setIsScrolled] = useState10(false);
3310
+ useEffect15(() => {
3311
+ const el = scrollRef.current;
3312
+ if (!el)
3313
+ return;
3314
+ function update() {
3315
+ if (!el)
3316
+ return;
3317
+ const overflows = el.scrollHeight > el.clientHeight + 1;
3318
+ const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 4;
3319
+ setShowScrollHint(overflows && !atBottom);
3320
+ setIsScrolled(el.scrollTop > 0);
3321
+ }
3322
+ update();
3323
+ el.addEventListener("scroll", update, { passive: true });
3324
+ const observer = new ResizeObserver(update);
3325
+ observer.observe(el);
3326
+ return () => {
3327
+ el.removeEventListener("scroll", update);
3328
+ observer.disconnect();
3329
+ };
3330
+ }, []);
3331
+ if (isLoadingModules && availableModules.length === 0) {
3332
+ return /* @__PURE__ */ jsx14("div", {
3333
+ className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center skippr:p-6",
3334
+ children: /* @__PURE__ */ jsx14(LoadingDots, {
3335
+ label: "Loading modules..."
3336
+ })
3337
+ });
3338
+ }
3339
+ if (modulesError) {
3340
+ return /* @__PURE__ */ jsxs13("div", {
3341
+ className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:items-center skippr:justify-center skippr:gap-3 skippr:p-6 skippr:text-center",
3342
+ children: [
3343
+ /* @__PURE__ */ jsx14("p", {
3344
+ className: "skippr:text-sm skippr:text-foreground",
3345
+ children: "Couldn't load modules."
3346
+ }),
3347
+ /* @__PURE__ */ jsx14("p", {
3348
+ className: "skippr:text-xs skippr:text-muted-foreground",
3349
+ children: modulesError
3350
+ }),
3351
+ /* @__PURE__ */ jsx14("button", {
3352
+ type: "button",
3353
+ onClick: () => void refetchModules(),
3354
+ 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",
3355
+ children: "Try again"
3356
+ })
3357
+ ]
3358
+ });
3359
+ }
3360
+ if (availableModules.length === 0) {
3361
+ return /* @__PURE__ */ jsx14("div", {
3362
+ className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center skippr:p-6 skippr:text-center",
3363
+ children: /* @__PURE__ */ jsx14("p", {
3364
+ className: "skippr:text-sm skippr:text-muted-foreground",
3365
+ children: "No experts available."
3366
+ })
3367
+ });
3368
+ }
3369
+ return /* @__PURE__ */ jsxs13("div", {
3370
+ className: "skippr:relative skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col",
3371
+ children: [
3372
+ /* @__PURE__ */ jsxs13("div", {
3373
+ ref: scrollRef,
3374
+ className: "skippr-no-scrollbar skippr:flex-1 skippr:overflow-y-auto",
3375
+ children: [
3376
+ /* @__PURE__ */ jsx14("h3", {
3377
+ 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" : ""}`,
3378
+ children: "Choose an agent"
3379
+ }),
3380
+ /* @__PURE__ */ jsxs13("div", {
3381
+ className: "skippr:px-4 skippr:pt-3 skippr:pb-5",
3382
+ children: [
3383
+ error && /* @__PURE__ */ jsx14("div", {
3384
+ role: "alert",
3385
+ 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",
3386
+ children: error
3387
+ }),
3388
+ /* @__PURE__ */ jsx14("div", {
3389
+ className: "skippr:grid skippr:grid-cols-2 skippr:gap-2.5",
3390
+ children: availableModules.map((module, index2) => {
3391
+ const isFeatured = index2 === 0;
3392
+ const isWide = index2 === availableModules.length - 1 && availableModules.length % 2 === 1;
3393
+ const Icon2 = getAgentIcon(module.type);
3394
+ const canResume = resumableAgentIds.has(module.id);
3395
+ 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";
3396
+ const layout = isWide ? "skippr:items-center skippr:p-3.5 skippr:pb-5" : "skippr:flex-col skippr:items-start skippr:p-3.5";
3397
+ 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";
3398
+ const span = isWide ? "skippr:col-span-2" : "";
3399
+ return /* @__PURE__ */ jsxs13("button", {
3400
+ type: "button",
3401
+ disabled: isBusy,
3402
+ onClick: () => selectModule(module.id),
3403
+ className: `${base} ${layout} ${variant} ${span}`,
3404
+ children: [
3405
+ /* @__PURE__ */ jsx14("div", {
3406
+ 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",
3407
+ children: /* @__PURE__ */ jsx14(Icon2, {
3408
+ className: isFeatured ? "skippr:h-4 skippr:w-4" : "skippr:h-4 skippr:w-4 skippr:text-muted-foreground"
3409
+ })
3410
+ }),
3411
+ /* @__PURE__ */ jsxs13("div", {
3412
+ className: "skippr:min-w-0 skippr:w-full skippr:space-y-0.5",
3413
+ children: [
3414
+ /* @__PURE__ */ jsxs13("div", {
3415
+ className: "skippr:flex skippr:items-center skippr:gap-1.5",
3416
+ children: [
3417
+ /* @__PURE__ */ jsx14("p", {
3418
+ className: "skippr:line-clamp-1 skippr:text-sm skippr:font-semibold",
3419
+ children: module.name
3420
+ }),
3421
+ canResume && /* @__PURE__ */ jsxs13("span", {
3422
+ className: `skippr:inline-flex skippr:shrink-0 skippr:items-center skippr:gap-0.5 skippr:rounded-full skippr:px-1.5 skippr:py-0.5 skippr:text-[9px] skippr:font-medium skippr:uppercase skippr:tracking-wide ${isFeatured ? "skippr:bg-primary-foreground/20 skippr:text-primary-foreground" : "skippr:bg-bubble/15 skippr:text-bubble"}`,
3423
+ children: [
3424
+ /* @__PURE__ */ jsx14(Play, {
3425
+ className: "skippr:size-2.5"
3426
+ }),
3427
+ "Resume"
3428
+ ]
3429
+ })
3430
+ ]
3431
+ }),
3432
+ module.description && /* @__PURE__ */ jsx14("p", {
3433
+ 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",
3434
+ children: module.description
3435
+ })
3436
+ ]
3437
+ })
3438
+ ]
3439
+ }, module.id);
3440
+ })
3441
+ })
3442
+ ]
3443
+ })
3444
+ ]
3445
+ }),
3446
+ showScrollHint && /* @__PURE__ */ jsx14("div", {
3447
+ 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"
3448
+ })
3449
+ ]
3450
+ });
3451
+ }
3452
+
3453
+ // src/components/SessionAgenda.tsx
3454
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2868
3455
  function SessionAgenda({ phases, hasStarted }) {
2869
3456
  if (phases.length === 0 || !hasStarted) {
2870
- return /* @__PURE__ */ jsx14("div", {
3457
+ return /* @__PURE__ */ jsx15("div", {
2871
3458
  className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
2872
- children: /* @__PURE__ */ jsx14(LoadingDots, {
3459
+ children: /* @__PURE__ */ jsx15(LoadingDots, {
2873
3460
  label: "Waiting for agenda to load..."
2874
3461
  })
2875
3462
  });
2876
3463
  }
2877
- return /* @__PURE__ */ jsx14("div", {
3464
+ return /* @__PURE__ */ jsx15("div", {
2878
3465
  className: "skippr:flex-1 skippr:overflow-y-auto skippr:px-4 skippr:py-4",
2879
- children: /* @__PURE__ */ jsx14("div", {
3466
+ children: /* @__PURE__ */ jsx15("div", {
2880
3467
  className: "skippr:space-y-1",
2881
3468
  children: phases.map((phase) => {
2882
3469
  const isActive = phase.status === "active";
2883
3470
  const isCompleted = phase.status === "completed";
2884
- return /* @__PURE__ */ jsxs13("div", {
3471
+ return /* @__PURE__ */ jsxs14("div", {
2885
3472
  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
3473
  children: [
2887
- /* @__PURE__ */ jsx14("div", {
3474
+ /* @__PURE__ */ jsx15("div", {
2888
3475
  className: "skippr:mt-0.5",
2889
- children: isCompleted ? /* @__PURE__ */ jsx14(CircleCheck, {
3476
+ children: isCompleted ? /* @__PURE__ */ jsx15(CircleCheck, {
2890
3477
  className: "skippr:size-4 skippr:text-chart-3"
2891
- }) : isActive ? /* @__PURE__ */ jsx14(Circle, {
3478
+ }) : isActive ? /* @__PURE__ */ jsx15(Circle, {
2892
3479
  className: "skippr:size-4 skippr:fill-primary/30 skippr:text-primary"
2893
- }) : /* @__PURE__ */ jsx14(Circle, {
3480
+ }) : /* @__PURE__ */ jsx15(Circle, {
2894
3481
  className: "skippr:size-4 skippr:text-muted-foreground/30"
2895
3482
  })
2896
3483
  }),
2897
- /* @__PURE__ */ jsxs13("div", {
3484
+ /* @__PURE__ */ jsxs14("div", {
2898
3485
  className: "skippr:min-w-0 skippr:flex-1",
2899
3486
  children: [
2900
- /* @__PURE__ */ jsx14("p", {
3487
+ /* @__PURE__ */ jsx15("p", {
2901
3488
  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
3489
  children: phase.name
2903
3490
  }),
2904
- phase.highlights.length > 0 && /* @__PURE__ */ jsx14("ul", {
3491
+ phase.highlights.length > 0 && /* @__PURE__ */ jsx15("ul", {
2905
3492
  className: "skippr:mt-1 skippr:space-y-0.5",
2906
- children: phase.highlights.map((text) => /* @__PURE__ */ jsxs13("li", {
3493
+ children: phase.highlights.map((text) => /* @__PURE__ */ jsxs14("li", {
2907
3494
  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
3495
  children: [
2909
- /* @__PURE__ */ jsx14("span", {
3496
+ /* @__PURE__ */ jsx15("span", {
2910
3497
  className: "skippr:size-1 skippr:shrink-0 skippr:rounded-full skippr:bg-current"
2911
3498
  }),
2912
3499
  text
@@ -2923,12 +3510,12 @@ function SessionAgenda({ phases, hasStarted }) {
2923
3510
  }
2924
3511
 
2925
3512
  // src/components/SessionWarningBanner.tsx
2926
- import { jsx as jsx15 } from "react/jsx-runtime";
3513
+ import { jsx as jsx16 } from "react/jsx-runtime";
2927
3514
  var SESSION_WARNING_THRESHOLD_SECS = 60;
2928
3515
  function SessionWarningBanner({ remaining }) {
2929
3516
  if (remaining === null || remaining <= 0 || remaining > SESSION_WARNING_THRESHOLD_SECS)
2930
3517
  return null;
2931
- return /* @__PURE__ */ jsx15("div", {
3518
+ return /* @__PURE__ */ jsx16("div", {
2932
3519
  "data-testid": "session-warning-banner",
2933
3520
  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
3521
  children: "Session ending soon"
@@ -2936,24 +3523,39 @@ function SessionWarningBanner({ remaining }) {
2936
3523
  }
2937
3524
 
2938
3525
  // src/components/StartSessionPrompt.tsx
2939
- import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
3526
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
3527
+ var PROMPT_BUTTON = "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";
2940
3528
  function StartSessionPrompt({
2941
3529
  onStartSession,
3530
+ agentId,
3531
+ agentControls,
2942
3532
  isStarting,
2943
3533
  error,
2944
- label = "Talk to Skippr"
3534
+ label = "Talk to Skippr",
3535
+ canResume = false,
3536
+ onResume
2945
3537
  }) {
2946
- return /* @__PURE__ */ jsxs14("div", {
3538
+ return /* @__PURE__ */ jsxs15("div", {
2947
3539
  className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:items-center skippr:justify-center skippr:gap-3 skippr:px-4",
2948
3540
  children: [
2949
- /* @__PURE__ */ jsx16("button", {
3541
+ canResume && onResume ? /* @__PURE__ */ jsx17("button", {
2950
3542
  type: "button",
2951
- onClick: onStartSession,
3543
+ onClick: onResume,
2952
3544
  disabled: isStarting,
2953
- 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",
3545
+ className: PROMPT_BUTTON,
3546
+ children: isStarting ? "Resuming..." : "Resume session"
3547
+ }) : /* @__PURE__ */ jsx17("button", {
3548
+ type: "button",
3549
+ onClick: () => {
3550
+ if (!agentId)
3551
+ return;
3552
+ onStartSession(agentControls ? { agentId, agentControls } : { agentId });
3553
+ },
3554
+ disabled: isStarting || !agentId,
3555
+ className: PROMPT_BUTTON,
2954
3556
  children: isStarting ? "Starting..." : label
2955
3557
  }),
2956
- error && /* @__PURE__ */ jsx16("p", {
3558
+ error && /* @__PURE__ */ jsx17("p", {
2957
3559
  className: "skippr:text-xs skippr:text-destructive",
2958
3560
  children: error
2959
3561
  })
@@ -2962,7 +3564,7 @@ function StartSessionPrompt({
2962
3564
  }
2963
3565
 
2964
3566
  // src/components/Sidebar.tsx
2965
- import { jsx as jsx17, jsxs as jsxs15, Fragment as Fragment3 } from "react/jsx-runtime";
3567
+ import { jsx as jsx18, jsxs as jsxs16, Fragment as Fragment4 } from "react/jsx-runtime";
2966
3568
  function Sidebar({
2967
3569
  hideControls = false,
2968
3570
  hideHeader = false,
@@ -2972,8 +3574,14 @@ function Sidebar({
2972
3574
  variant,
2973
3575
  isConnected,
2974
3576
  isStarting,
3577
+ isDisconnecting,
3578
+ isPausing,
3579
+ isPaused,
2975
3580
  error,
2976
3581
  startSession,
3582
+ pauseSession,
3583
+ resumeSession,
3584
+ resumableSession,
2977
3585
  disconnect,
2978
3586
  isPanelOpen,
2979
3587
  position,
@@ -2986,11 +3594,14 @@ function Sidebar({
2986
3594
  sidebarTab: activeTab,
2987
3595
  setSidebarTab: setActiveTab,
2988
3596
  autoFocusChat,
2989
- captureMode
3597
+ captureMode,
3598
+ hasModuleSelector,
3599
+ agentId,
3600
+ agentControls
2990
3601
  } = useLiveAgent();
2991
3602
  const isFloating = variant === "floating";
2992
3603
  const isSidebar = variant === "sidebar";
2993
- useEffect13(() => {
3604
+ useEffect16(() => {
2994
3605
  if (!isSidebar)
2995
3606
  return;
2996
3607
  const prop = position === "right" ? "marginRight" : "marginLeft";
@@ -3004,24 +3615,30 @@ function Sidebar({
3004
3615
  document.body.style.transition = "";
3005
3616
  };
3006
3617
  }, [isSidebar, isPanelOpen, position]);
3007
- return /* @__PURE__ */ jsxs15("div", {
3618
+ return /* @__PURE__ */ jsxs16("div", {
3008
3619
  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
3620
  style: { width: isPanelOpen ? SIDEBAR_WIDTH : undefined },
3010
3621
  children: [
3011
- !hideHeader && /* @__PURE__ */ jsx17(ChatHeader, {}),
3012
- !isAuthenticated && isValidating ? /* @__PURE__ */ jsx17("div", {
3622
+ !hideHeader && /* @__PURE__ */ jsx18(ChatHeader, {}),
3623
+ !isAuthenticated && isValidating ? /* @__PURE__ */ jsx18("div", {
3013
3624
  className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
3014
- children: /* @__PURE__ */ jsx17(LoadingDots, {
3625
+ children: /* @__PURE__ */ jsx18(LoadingDots, {
3015
3626
  label: "Loading..."
3016
3627
  })
3017
- }) : !isAuthenticated ? /* @__PURE__ */ jsx17(LoginFlow, {
3628
+ }) : !isAuthenticated ? /* @__PURE__ */ jsx18(LoginFlow, {
3018
3629
  requestOtp,
3019
3630
  verifyOtp,
3020
3631
  error: authError,
3021
3632
  isSubmitting: isAuthSubmitting
3022
- }) : /* @__PURE__ */ jsx17(AuthenticatedContent, {
3633
+ }) : /* @__PURE__ */ jsx18(AuthenticatedContent, {
3023
3634
  isConnected,
3635
+ isPaused,
3636
+ isPausing,
3637
+ isDisconnecting,
3638
+ canResume: resumableSession !== null,
3024
3639
  onStartSession: startSession,
3640
+ onPause: pauseSession,
3641
+ onResume: resumeSession,
3025
3642
  onDisconnect: disconnect,
3026
3643
  isStarting,
3027
3644
  error,
@@ -3030,14 +3647,23 @@ function Sidebar({
3030
3647
  hideControls,
3031
3648
  startSessionLabel,
3032
3649
  autoFocusChat,
3033
- showScreenShareToggle: captureMode === "screenshare"
3650
+ showScreenShareToggle: captureMode === "screenshare",
3651
+ hasModuleSelector,
3652
+ agentId,
3653
+ agentControls
3034
3654
  })
3035
3655
  ]
3036
3656
  });
3037
3657
  }
3038
3658
  function AuthenticatedContent({
3039
3659
  isConnected,
3660
+ isPaused,
3661
+ isPausing,
3662
+ isDisconnecting,
3663
+ canResume,
3040
3664
  onStartSession,
3665
+ onPause,
3666
+ onResume,
3041
3667
  onDisconnect,
3042
3668
  isStarting,
3043
3669
  error,
@@ -3046,61 +3672,96 @@ function AuthenticatedContent({
3046
3672
  hideControls,
3047
3673
  startSessionLabel,
3048
3674
  autoFocusChat,
3049
- showScreenShareToggle
3675
+ showScreenShareToggle,
3676
+ hasModuleSelector,
3677
+ agentId,
3678
+ agentControls
3050
3679
  }) {
3051
- return /* @__PURE__ */ jsxs15(Fragment3, {
3680
+ const inSession = isConnected || isStarting || isPaused || isPausing;
3681
+ const showSelectorAsPrompt = hasModuleSelector && !inSession && !isDisconnecting;
3682
+ const showTabBar = !showSelectorAsPrompt;
3683
+ let transitionLabel = null;
3684
+ if (isPausing) {
3685
+ transitionLabel = "Pausing...";
3686
+ } else if (isStarting && !isConnected) {
3687
+ transitionLabel = "Reconnecting...";
3688
+ } else if (isPaused) {
3689
+ transitionLabel = "Paused";
3690
+ }
3691
+ return /* @__PURE__ */ jsxs16(Fragment4, {
3052
3692
  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",
3693
+ isConnected && /* @__PURE__ */ jsx18(ConnectedBanner, {}),
3694
+ showTabBar && /* @__PURE__ */ jsxs16("div", {
3695
+ className: "skippr:flex skippr:items-center skippr:gap-2 skippr:border-b skippr:border-border skippr:px-3 skippr:py-2",
3056
3696
  children: [
3057
- /* @__PURE__ */ jsxs15("button", {
3697
+ /* @__PURE__ */ jsxs16("button", {
3058
3698
  type: "button",
3059
3699
  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
3700
  onClick: () => onTabChange("chat"),
3061
3701
  children: [
3062
- /* @__PURE__ */ jsx17(MessageCircle, {
3702
+ /* @__PURE__ */ jsx18(MessageCircle, {
3063
3703
  className: "skippr:size-3.5"
3064
3704
  }),
3065
3705
  "Chat",
3066
- activeTab === "chat" && /* @__PURE__ */ jsx17("span", {
3706
+ activeTab === "chat" && /* @__PURE__ */ jsx18("span", {
3067
3707
  className: "skippr:absolute skippr:-bottom-2 skippr:left-3 skippr:right-3 skippr:h-0.5 skippr:rounded-full skippr:bg-foreground"
3068
3708
  })
3069
3709
  ]
3070
3710
  }),
3071
- /* @__PURE__ */ jsxs15("button", {
3711
+ /* @__PURE__ */ jsxs16("button", {
3072
3712
  type: "button",
3073
3713
  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
3714
  onClick: () => onTabChange("agenda"),
3075
3715
  children: [
3076
- /* @__PURE__ */ jsx17(Calendar, {
3716
+ /* @__PURE__ */ jsx18(Calendar, {
3077
3717
  className: "skippr:size-3.5"
3078
3718
  }),
3079
3719
  "Agenda",
3080
- activeTab === "agenda" && /* @__PURE__ */ jsx17("span", {
3720
+ activeTab === "agenda" && /* @__PURE__ */ jsx18("span", {
3081
3721
  className: "skippr:absolute skippr:-bottom-2 skippr:left-3 skippr:right-3 skippr:h-0.5 skippr:rounded-full skippr:bg-foreground"
3082
3722
  })
3083
3723
  ]
3084
3724
  })
3085
3725
  ]
3086
3726
  }),
3087
- /* @__PURE__ */ jsx17("div", {
3727
+ /* @__PURE__ */ jsx18("div", {
3088
3728
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col",
3089
- children: isConnected || isStarting ? /* @__PURE__ */ jsx17(ConnectedBody, {
3090
- activeTab,
3091
- autoFocusChat
3092
- }) : /* @__PURE__ */ jsx17("div", {
3729
+ children: isDisconnecting ? /* @__PURE__ */ jsx18("div", {
3730
+ className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
3731
+ children: /* @__PURE__ */ jsx18(LoadingDots, {
3732
+ label: "Ending session..."
3733
+ })
3734
+ }) : showSelectorAsPrompt ? /* @__PURE__ */ jsx18(ModuleSelector, {}) : inSession ? /* @__PURE__ */ jsxs16(Fragment4, {
3735
+ children: [
3736
+ transitionLabel && /* @__PURE__ */ jsx18("div", {
3737
+ className: "skippr:shrink-0 skippr:border-b skippr:border-border skippr:bg-muted/50 skippr:px-3 skippr:py-1.5 skippr:text-center skippr:text-xs skippr:text-muted-foreground",
3738
+ children: transitionLabel
3739
+ }),
3740
+ /* @__PURE__ */ jsx18(ConnectedBody, {
3741
+ activeTab,
3742
+ autoFocusChat
3743
+ })
3744
+ ]
3745
+ }) : /* @__PURE__ */ jsx18("div", {
3093
3746
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col skippr:animate-skippr-tab-fade",
3094
- children: /* @__PURE__ */ jsx17(StartSessionPrompt, {
3747
+ children: /* @__PURE__ */ jsx18(StartSessionPrompt, {
3095
3748
  onStartSession,
3749
+ agentId,
3750
+ agentControls,
3096
3751
  isStarting,
3097
3752
  error,
3098
- label: startSessionLabel
3753
+ label: startSessionLabel,
3754
+ canResume,
3755
+ onResume
3099
3756
  })
3100
3757
  }, `${activeTab}-empty`)
3101
3758
  }),
3102
- isConnected && !hideControls && /* @__PURE__ */ jsx17(MeetingControls, {
3759
+ (isConnected || isPaused) && !isDisconnecting && !hideControls && /* @__PURE__ */ jsx18(MeetingControls, {
3103
3760
  onHangUp: onDisconnect,
3761
+ onPause,
3762
+ onResume,
3763
+ isPaused,
3764
+ isPausing,
3104
3765
  showScreenShareToggle
3105
3766
  })
3106
3767
  ]
@@ -3108,7 +3769,7 @@ function AuthenticatedContent({
3108
3769
  }
3109
3770
  function ConnectedBanner() {
3110
3771
  const remaining = useSessionRemaining();
3111
- return /* @__PURE__ */ jsx17(SessionWarningBanner, {
3772
+ return /* @__PURE__ */ jsx18(SessionWarningBanner, {
3112
3773
  remaining
3113
3774
  });
3114
3775
  }
@@ -3119,17 +3780,17 @@ function ConnectedBody({
3119
3780
  const { allMessages, agentState, sendChatMessage, isSendingChat } = useCombinedMessages();
3120
3781
  const { phases } = usePhaseUpdates();
3121
3782
  if (activeTab === "agenda") {
3122
- return /* @__PURE__ */ jsx17("div", {
3783
+ return /* @__PURE__ */ jsx18("div", {
3123
3784
  className: "skippr:min-h-0 skippr:flex-1 skippr:overflow-y-auto skippr:animate-skippr-tab-fade",
3124
- children: /* @__PURE__ */ jsx17(SessionAgenda, {
3785
+ children: /* @__PURE__ */ jsx18(SessionAgenda, {
3125
3786
  phases,
3126
3787
  hasStarted: allMessages.length > 0 || agentState === "speaking"
3127
3788
  })
3128
3789
  }, "agenda");
3129
3790
  }
3130
- return /* @__PURE__ */ jsx17("div", {
3791
+ return /* @__PURE__ */ jsx18("div", {
3131
3792
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col skippr:animate-skippr-tab-fade",
3132
- children: /* @__PURE__ */ jsx17(MessageList, {
3793
+ children: /* @__PURE__ */ jsx18(MessageList, {
3133
3794
  messages: allMessages,
3134
3795
  isStreaming: agentState === "speaking",
3135
3796
  sendChatMessage,
@@ -3140,31 +3801,31 @@ function ConnectedBody({
3140
3801
  }
3141
3802
 
3142
3803
  // src/components/SidebarTrigger.tsx
3143
- import { jsx as jsx18 } from "react/jsx-runtime";
3804
+ import { jsx as jsx19 } from "react/jsx-runtime";
3144
3805
  function SidebarTrigger() {
3145
3806
  const { isPanelOpen, togglePanel, minimizePanel, minimizable, position, isMinimized } = useLiveAgent();
3146
3807
  if (isMinimized)
3147
3808
  return null;
3148
3809
  const handleClick = isPanelOpen && minimizable ? minimizePanel : togglePanel;
3149
- return /* @__PURE__ */ jsx18("button", {
3810
+ return /* @__PURE__ */ jsx19("button", {
3150
3811
  type: "button",
3151
3812
  onClick: handleClick,
3152
3813
  title: isPanelOpen ? "Close chat" : "Open chat",
3153
3814
  "aria-label": isPanelOpen ? "Close chat" : "Open chat",
3154
3815
  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, {
3816
+ children: isPanelOpen ? /* @__PURE__ */ jsx19(ChevronDown, {
3156
3817
  className: "skippr:size-5"
3157
- }) : /* @__PURE__ */ jsx18(Logo, {
3818
+ }) : /* @__PURE__ */ jsx19(Logo, {
3158
3819
  className: "skippr:size-7"
3159
3820
  })
3160
3821
  });
3161
3822
  }
3162
3823
 
3163
3824
  // src/components/LiveAgent.tsx
3164
- import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
3825
+ import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
3165
3826
  function LiveAgent(props) {
3166
3827
  const {
3167
- agentId,
3828
+ agentId: hostAgentId,
3168
3829
  authToken: authTokenProp,
3169
3830
  appKey,
3170
3831
  userToken,
@@ -3180,39 +3841,106 @@ function LiveAgent(props) {
3180
3841
  children
3181
3842
  } = props;
3182
3843
  const captureMode = props.captureMode ?? "screenshare";
3183
- let agentControls;
3844
+ let hostAgentControls;
3184
3845
  if ("agentControls" in props && props.agentControls) {
3185
3846
  if (captureMode === "auto") {
3186
- agentControls = props.agentControls;
3847
+ hostAgentControls = props.agentControls;
3187
3848
  } else {
3188
3849
  console.warn('[Skippr] agentControls requires captureMode: "auto"');
3189
3850
  }
3190
3851
  }
3191
3852
  const auth = useAuth({ appKey });
3192
3853
  const effectiveAuthToken = authTokenProp || auth.authToken || undefined;
3854
+ const hasModuleSelector = !hostAgentId;
3855
+ const [activeModule, setActiveModule] = useState11(null);
3856
+ const agentId = hasModuleSelector ? activeModule?.id ?? null : hostAgentId;
3857
+ const agentControls = hasModuleSelector ? activeModule?.controls : hostAgentControls;
3858
+ const minimizeOnSessionStart = useCallback8(() => {
3859
+ if (minimizable) {
3860
+ setIsMinimized(true);
3861
+ setIsPanelOpen(false);
3862
+ }
3863
+ }, [minimizable]);
3864
+ const expandOnSessionStartError = useCallback8(() => {
3865
+ setIsMinimized(false);
3866
+ setIsPanelOpen(true);
3867
+ }, []);
3868
+ const minimizeOnSessionDisconnect = useCallback8(() => {
3869
+ if (hasModuleSelector) {
3870
+ setActiveModule(null);
3871
+ }
3872
+ if (minimizable && !hasModuleSelector) {
3873
+ setIsMinimized(true);
3874
+ setIsPanelOpen(false);
3875
+ }
3876
+ }, [minimizable, hasModuleSelector]);
3193
3877
  const {
3194
3878
  connection,
3195
3879
  shouldConnect,
3196
3880
  isStarting,
3881
+ isDisconnecting,
3882
+ isPausing,
3197
3883
  error,
3198
3884
  errorCode,
3199
3885
  startSession,
3886
+ pauseSession,
3887
+ resumeSession: resumeSessionById,
3200
3888
  disconnect,
3201
- pendingScreenStream
3889
+ isPaused,
3890
+ resumableSessions,
3891
+ historyMessages,
3892
+ pendingScreenStream,
3893
+ bearerToken
3202
3894
  } = useSession({
3203
- agentId,
3204
3895
  captureMode,
3205
- agentControls,
3206
3896
  authToken: effectiveAuthToken,
3207
3897
  appKey,
3208
- userToken
3898
+ userToken,
3899
+ onStart: minimizeOnSessionStart,
3900
+ onStartError: expandOnSessionStartError,
3901
+ onDisconnect: minimizeOnSessionDisconnect
3209
3902
  });
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(() => {
3903
+ const teardownInFlightRef = useRef10(false);
3904
+ teardownInFlightRef.current = isPaused || isPausing || isDisconnecting;
3905
+ const handleRoomDisconnected = useCallback8(() => {
3906
+ if (teardownInFlightRef.current)
3907
+ return;
3908
+ disconnect();
3909
+ }, [disconnect]);
3910
+ const resumableSession = resumableSessions.find((s) => s.agentId === agentId) ?? null;
3911
+ const resumeSession = useCallback8(async () => {
3912
+ if (!resumableSession)
3913
+ return;
3914
+ await resumeSessionById({ sessionId: resumableSession.id, agentControls });
3915
+ }, [resumableSession, resumeSessionById, agentControls]);
3916
+ const [isPanelOpen, setIsPanelOpen] = useState11(defaultOpen);
3917
+ const [isMinimized, setIsMinimized] = useState11(minimizable && !defaultOpen);
3918
+ const [sidebarTab, setSidebarTab] = useState11("agenda");
3919
+ const [phasesSnapshot, setPhasesSnapshot] = useState11([]);
3920
+ const {
3921
+ modules: availableModules,
3922
+ isLoading: isLoadingModules,
3923
+ error: modulesError,
3924
+ refetch: refetchModules
3925
+ } = useAvailableModules({
3926
+ enabled: hasModuleSelector && isPanelOpen,
3927
+ bearerToken
3928
+ });
3929
+ const selectModule = useCallback8((moduleId) => {
3930
+ const found = availableModules.find((m) => m.id === moduleId);
3931
+ if (!found)
3932
+ return;
3933
+ setActiveModule(found);
3934
+ const resumable = resumableSessions.find((s) => s.agentId === found.id);
3935
+ if (resumable) {
3936
+ resumeSessionById({ sessionId: resumable.id, agentControls: found.controls });
3937
+ } else {
3938
+ startSession({ agentId: found.id, agentControls: found.controls });
3939
+ }
3940
+ }, [availableModules, resumableSessions, startSession, resumeSessionById]);
3941
+ const [welcomeDismissed, setWelcomeDismissed] = useState11(false);
3942
+ const dismissWelcome = useCallback8(() => setWelcomeDismissed(true), []);
3943
+ const [currentPosition, setCurrentPosition] = useState11(() => {
3216
3944
  try {
3217
3945
  const saved = localStorage.getItem("skippr_widget_position");
3218
3946
  if (saved === "left" || saved === "right")
@@ -3220,20 +3948,20 @@ function LiveAgent(props) {
3220
3948
  } catch {}
3221
3949
  return position;
3222
3950
  });
3223
- const setPositionWithPersist = useCallback7((pos) => {
3951
+ const setPositionWithPersist = useCallback8((pos) => {
3224
3952
  setCurrentPosition(pos);
3225
3953
  try {
3226
3954
  localStorage.setItem("skippr_widget_position", pos);
3227
3955
  } catch {}
3228
3956
  }, []);
3229
- const openPanel = useCallback7(() => setIsPanelOpen(true), []);
3230
- const closePanel = useCallback7(() => setIsPanelOpen(false), []);
3231
- const togglePanel = useCallback7(() => setIsPanelOpen((prev) => !prev), []);
3232
- const expandPanel = useCallback7(() => {
3957
+ const openPanel = useCallback8(() => setIsPanelOpen(true), []);
3958
+ const closePanel = useCallback8(() => setIsPanelOpen(false), []);
3959
+ const togglePanel = useCallback8(() => setIsPanelOpen((prev) => !prev), []);
3960
+ const expandPanel = useCallback8(() => {
3233
3961
  setIsMinimized(false);
3234
3962
  setIsPanelOpen(true);
3235
3963
  }, []);
3236
- const minimizePanel = useCallback7(() => {
3964
+ const minimizePanel = useCallback8(() => {
3237
3965
  if (!minimizable)
3238
3966
  return;
3239
3967
  setIsMinimized(true);
@@ -3241,24 +3969,25 @@ function LiveAgent(props) {
3241
3969
  }, [minimizable]);
3242
3970
  const isConnected = connection !== null;
3243
3971
  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
- const ctx = useMemo5(() => ({
3972
+ const ctx = useMemo6(() => ({
3254
3973
  connection,
3255
3974
  shouldConnect,
3256
3975
  isConnected,
3257
3976
  isStarting,
3977
+ isDisconnecting,
3978
+ isPausing,
3258
3979
  error,
3259
3980
  errorCode,
3260
3981
  startSession,
3982
+ pauseSession,
3983
+ resumeSession,
3261
3984
  disconnect,
3985
+ isPaused,
3986
+ resumableSession,
3987
+ resumableSessions,
3988
+ historyMessages,
3989
+ phasesSnapshot,
3990
+ setPhasesSnapshot,
3262
3991
  isPanelOpen,
3263
3992
  openPanel,
3264
3993
  closePanel,
@@ -3281,16 +4010,33 @@ function LiveAgent(props) {
3281
4010
  setSidebarTab,
3282
4011
  autoFocusChat,
3283
4012
  captureMode,
3284
- agentControls
4013
+ agentId,
4014
+ agentControls,
4015
+ hasModuleSelector,
4016
+ availableModules,
4017
+ activeModule,
4018
+ isLoadingModules,
4019
+ modulesError,
4020
+ refetchModules,
4021
+ selectModule
3285
4022
  }), [
3286
4023
  connection,
3287
4024
  shouldConnect,
3288
4025
  isConnected,
3289
4026
  isStarting,
4027
+ isDisconnecting,
4028
+ isPausing,
3290
4029
  error,
3291
4030
  errorCode,
3292
4031
  startSession,
4032
+ pauseSession,
4033
+ resumeSession,
3293
4034
  disconnect,
4035
+ isPaused,
4036
+ resumableSession,
4037
+ resumableSessions,
4038
+ historyMessages,
4039
+ phasesSnapshot,
3294
4040
  isPanelOpen,
3295
4041
  openPanel,
3296
4042
  closePanel,
@@ -3312,33 +4058,41 @@ function LiveAgent(props) {
3312
4058
  sidebarTab,
3313
4059
  autoFocusChat,
3314
4060
  captureMode,
3315
- agentControls
4061
+ agentId,
4062
+ agentControls,
4063
+ hasModuleSelector,
4064
+ availableModules,
4065
+ activeModule,
4066
+ isLoadingModules,
4067
+ modulesError,
4068
+ refetchModules,
4069
+ selectModule
3316
4070
  ]);
3317
- return /* @__PURE__ */ jsx19(LiveAgentContext.Provider, {
4071
+ return /* @__PURE__ */ jsx20(LiveAgentContext.Provider, {
3318
4072
  value: ctx,
3319
- children: /* @__PURE__ */ jsxs16(LiveKitRoom, {
4073
+ children: /* @__PURE__ */ jsxs17(LiveKitRoom, {
3320
4074
  serverUrl: connection?.livekitUrl,
3321
4075
  token: connection?.token,
3322
4076
  connect: shouldConnect,
3323
4077
  audio: true,
3324
- onDisconnected: disconnect,
4078
+ onDisconnected: handleRoomDisconnected,
3325
4079
  children: [
3326
- connection && /* @__PURE__ */ jsx19(RoomAudioRenderer, {}),
3327
- connection && captureMode === "screenshare" && /* @__PURE__ */ jsx19(AutoStartMedia, {
4080
+ connection && /* @__PURE__ */ jsx20(RoomAudioRenderer, {}),
4081
+ connection && captureMode === "screenshare" && /* @__PURE__ */ jsx20(AutoStartMedia, {
3328
4082
  pendingScreenStream
3329
4083
  }),
3330
- connection && captureMode === "auto" && /* @__PURE__ */ jsx19(DomCapture, {}),
3331
- connection && captureMode === "auto" && agentControls?.highlight && /* @__PURE__ */ jsx19(HighlightOverlay, {}),
3332
- /* @__PURE__ */ jsxs16("div", {
4084
+ connection && captureMode === "auto" && /* @__PURE__ */ jsx20(DomCapture, {}),
4085
+ connection && captureMode === "auto" && agentControls?.highlight && /* @__PURE__ */ jsx20(HighlightOverlay, {}),
4086
+ /* @__PURE__ */ jsxs17("div", {
3333
4087
  id: WIDGET_ROOT_ID,
3334
4088
  children: [
3335
- isMinimized && /* @__PURE__ */ jsx19(MinimizedBubble, {
4089
+ isMinimized && /* @__PURE__ */ jsx20(MinimizedBubble, {
3336
4090
  welcomeMessage,
3337
4091
  welcomeDismissed,
3338
4092
  onDismissWelcome: dismissWelcome
3339
4093
  }),
3340
- /* @__PURE__ */ jsx19(SidebarTrigger, {}),
3341
- /* @__PURE__ */ jsx19(Sidebar, {
4094
+ /* @__PURE__ */ jsx20(SidebarTrigger, {}),
4095
+ /* @__PURE__ */ jsx20(Sidebar, {
3342
4096
  hideControls,
3343
4097
  hideHeader,
3344
4098
  startSessionLabel