@skippr/live-agent-sdk 0.35.0 → 0.36.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 useCallback8, useMemo as useMemo6, useRef as useRef10, useState as useState11 } from "react";
11
+ import { useCallback as useCallback8, useMemo as useMemo5, useRef as useRef10, useState as useState11 } from "react";
12
12
 
13
13
  // src/context/LiveAgentContext.tsx
14
14
  import { createContext } from "react";
@@ -273,7 +273,6 @@ function useSession({
273
273
  const [pendingScreenStream, setPendingScreenStream] = useState3(null);
274
274
  const [isPaused, setIsPaused] = useState3(false);
275
275
  const [isPausing, setIsPausing] = useState3(false);
276
- const [resumableSessions, setResumableSessions] = useState3([]);
277
276
  const [historyMessages, setHistoryMessages] = useState3([]);
278
277
  useEffect3(() => {
279
278
  let stale = false;
@@ -294,29 +293,6 @@ function useSession({
294
293
  stale = true;
295
294
  };
296
295
  }, [authToken, appKey, userToken]);
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
296
  const pauseOnUnloadRef = useRef(null);
321
297
  pauseOnUnloadRef.current = connection !== null && !isPaused && sessionId && bearerToken ? { sessionId, bearerToken } : null;
322
298
  useEffect3(() => {
@@ -334,12 +310,12 @@ function useSession({
334
310
  window.addEventListener("pagehide", onPageHide);
335
311
  return () => window.removeEventListener("pagehide", onPageHide);
336
312
  }, []);
337
- const startSession = useCallback3(async ({ agentId, agentControls }) => {
313
+ const startSession = useCallback3(async ({ agentId, agentControls, existingSessionId }) => {
338
314
  if (!bearerToken) {
339
315
  setError("No auth token available");
340
316
  return;
341
317
  }
342
- if (!agentId) {
318
+ if (!agentId && !existingSessionId) {
343
319
  setError("No agent selected");
344
320
  return;
345
321
  }
@@ -354,35 +330,41 @@ function useSession({
354
330
  const requestAgentControls = agentControls?.highlight === true ? { highlight: true } : undefined;
355
331
  const headers = { Authorization: `Bearer ${bearerToken}` };
356
332
  try {
357
- const createResp = await fetch(`${API_URL3}/v1/sessions`, {
358
- method: "POST",
359
- credentials: "omit",
360
- headers: { "Content-Type": "application/json", ...headers },
361
- body: JSON.stringify({
362
- agentId,
363
- captureMode,
364
- agentControls: requestAgentControls
365
- })
366
- });
367
- if (!createResp.ok) {
368
- const body = await createResp.json().catch(() => null);
369
- setErrorCode(createResp.status);
370
- throw new Error(body?.detail || `Failed to create session: ${createResp.status}`);
333
+ let resolvedSessionId = existingSessionId;
334
+ if (!resolvedSessionId) {
335
+ const createResp = await fetch(`${API_URL3}/v1/sessions`, {
336
+ method: "POST",
337
+ credentials: "omit",
338
+ headers: { "Content-Type": "application/json", ...headers },
339
+ body: JSON.stringify({
340
+ agentId,
341
+ captureMode,
342
+ agentControls: requestAgentControls
343
+ })
344
+ });
345
+ if (!createResp.ok) {
346
+ const body = await createResp.json().catch(() => null);
347
+ setErrorCode(createResp.status);
348
+ throw new Error(body?.detail || `Failed to create session: ${createResp.status}`);
349
+ }
350
+ const { session: session2 } = await createResp.json();
351
+ resolvedSessionId = session2.id;
371
352
  }
372
- const { session } = await createResp.json();
373
- const startResp = await fetch(`${API_URL3}/v1/sessions/${session.id}/start`, {
353
+ const startResp = await fetch(`${API_URL3}/v1/sessions/${resolvedSessionId}/start`, {
374
354
  method: "POST",
375
355
  credentials: "omit",
376
- headers
356
+ headers: { "Content-Type": "application/json", ...headers },
357
+ body: JSON.stringify({ captureMode, agentControls: requestAgentControls })
377
358
  });
378
359
  if (!startResp.ok) {
379
360
  const body = await startResp.json().catch(() => null);
380
361
  setErrorCode(startResp.status);
381
362
  throw new Error(body?.detail || `Failed to start session: ${startResp.status}`);
382
363
  }
383
- const { connection: conn } = await startResp.json();
364
+ const { session, connection: conn } = await startResp.json();
365
+ const history2 = existingSessionId ? await fetchSessionMessages(session.id, bearerToken) : [];
384
366
  setSessionId(session.id);
385
- setHistoryMessages([]);
367
+ setHistoryMessages(history2);
386
368
  setConnection({
387
369
  livekitUrl: conn.livekitUrl,
388
370
  token: conn.token
@@ -425,64 +407,16 @@ function useSession({
425
407
  setHistoryMessages(history2);
426
408
  setIsPaused(true);
427
409
  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]);
410
+ }, [sessionId, bearerToken, pendingScreenStream]);
476
411
  const disconnect = useCallback3(async () => {
477
412
  setIsDisconnecting(true);
478
413
  try {
479
414
  if (sessionId && bearerToken) {
480
415
  try {
481
- await fetch(`${API_URL3}/v1/sessions/${sessionId}/complete`, {
416
+ await fetch(`${API_URL3}/v1/sessions/${sessionId}/pause`, {
482
417
  method: "POST",
483
418
  credentials: "omit",
484
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
485
- body: JSON.stringify({})
419
+ headers: { Authorization: `Bearer ${bearerToken}` }
486
420
  });
487
421
  } catch {}
488
422
  }
@@ -495,11 +429,10 @@ function useSession({
495
429
  setHistoryMessages([]);
496
430
  setIsPaused(false);
497
431
  onDisconnect?.();
498
- await refetchResumable();
499
432
  } finally {
500
433
  setIsDisconnecting(false);
501
434
  }
502
- }, [sessionId, bearerToken, pendingScreenStream, onDisconnect, refetchResumable]);
435
+ }, [sessionId, bearerToken, pendingScreenStream, onDisconnect]);
503
436
  return {
504
437
  connection,
505
438
  shouldConnect,
@@ -509,13 +442,10 @@ function useSession({
509
442
  errorCode,
510
443
  startSession,
511
444
  pauseSession,
512
- resumeSession,
513
445
  disconnect,
514
446
  isPaused,
515
447
  isPausing,
516
- resumableSessions,
517
448
  historyMessages,
518
- refetchResumable,
519
449
  pendingScreenStream,
520
450
  bearerToken
521
451
  };
@@ -531,6 +461,27 @@ var DOM_EVENTS_TOPIC = "skippr.dom-events";
531
461
  var HIGHLIGHT_TOPIC = "skippr.highlight";
532
462
  var NAME_MAX_CHARS = 80;
533
463
 
464
+ // src/lib/session.ts
465
+ function getResumableSessionId(currentSession, mode) {
466
+ if (!currentSession)
467
+ return;
468
+ const { id, status } = currentSession;
469
+ if (status === "paused" || status === "active")
470
+ return id;
471
+ if (status === "expired" && mode === "always_on")
472
+ return id;
473
+ return;
474
+ }
475
+ function canResumeSession(currentSession, mode) {
476
+ return getResumableSessionId(currentSession, mode) !== undefined;
477
+ }
478
+ function getResumableSession(module) {
479
+ if (!module)
480
+ return null;
481
+ const id = getResumableSessionId(module.currentSession, module.mode);
482
+ return id ? { id, agentId: module.id } : null;
483
+ }
484
+
534
485
  // src/components/AutoStartMedia.tsx
535
486
  import { useConnectionState, useLocalParticipant } from "@livekit/components-react/hooks";
536
487
  import { ConnectionState, Track } from "livekit-client";
@@ -3279,7 +3230,7 @@ function MessageList({
3279
3230
  }
3280
3231
 
3281
3232
  // src/components/ModuleSelector.tsx
3282
- import { useEffect as useEffect15, useMemo as useMemo5, useRef as useRef9, useState as useState10 } from "react";
3233
+ import { useEffect as useEffect15, useRef as useRef9, useState as useState10 } from "react";
3283
3234
  import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
3284
3235
  var AGENT_TYPE_ICONS = {
3285
3236
  onboarding: UserPlus,
@@ -3299,11 +3250,9 @@ function ModuleSelector() {
3299
3250
  selectModule,
3300
3251
  isStarting,
3301
3252
  isDisconnecting,
3302
- error,
3303
- resumableSessions
3253
+ error
3304
3254
  } = useLiveAgent();
3305
3255
  const isBusy = isStarting || isDisconnecting;
3306
- const resumableAgentIds = useMemo5(() => new Set(resumableSessions.map((s) => s.agentId)), [resumableSessions]);
3307
3256
  const scrollRef = useRef9(null);
3308
3257
  const [showScrollHint, setShowScrollHint] = useState10(false);
3309
3258
  const [isScrolled, setIsScrolled] = useState10(false);
@@ -3391,7 +3340,7 @@ function ModuleSelector() {
3391
3340
  const isFeatured = index2 === 0;
3392
3341
  const isWide = index2 === availableModules.length - 1 && availableModules.length % 2 === 1;
3393
3342
  const Icon2 = getAgentIcon(module.type);
3394
- const canResume = resumableAgentIds.has(module.id);
3343
+ const canResume = canResumeSession(module.currentSession, module.mode);
3395
3344
  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
3345
  const layout = isWide ? "skippr:items-center skippr:p-3.5 skippr:pb-5" : "skippr:flex-col skippr:items-start skippr:p-3.5";
3397
3346
  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";
@@ -3597,8 +3546,10 @@ function Sidebar({
3597
3546
  captureMode,
3598
3547
  hasModuleSelector,
3599
3548
  agentId,
3549
+ agentMode,
3600
3550
  agentControls
3601
3551
  } = useLiveAgent();
3552
+ const showAgenda = agentMode !== "always_on";
3602
3553
  const isFloating = variant === "floating";
3603
3554
  const isSidebar = variant === "sidebar";
3604
3555
  useEffect16(() => {
@@ -3650,7 +3601,8 @@ function Sidebar({
3650
3601
  showScreenShareToggle: captureMode === "screenshare",
3651
3602
  hasModuleSelector,
3652
3603
  agentId,
3653
- agentControls
3604
+ agentControls,
3605
+ showAgenda
3654
3606
  })
3655
3607
  ]
3656
3608
  });
@@ -3675,11 +3627,13 @@ function AuthenticatedContent({
3675
3627
  showScreenShareToggle,
3676
3628
  hasModuleSelector,
3677
3629
  agentId,
3678
- agentControls
3630
+ agentControls,
3631
+ showAgenda
3679
3632
  }) {
3680
3633
  const inSession = isConnected || isStarting || isPaused || isPausing;
3681
3634
  const showSelectorAsPrompt = hasModuleSelector && !inSession && !isDisconnecting;
3682
3635
  const showTabBar = !showSelectorAsPrompt;
3636
+ const effectiveTab = showAgenda ? activeTab : "chat";
3683
3637
  let transitionLabel = null;
3684
3638
  if (isPausing) {
3685
3639
  transitionLabel = "Pausing...";
@@ -3708,7 +3662,7 @@ function AuthenticatedContent({
3708
3662
  })
3709
3663
  ]
3710
3664
  }),
3711
- /* @__PURE__ */ jsxs16("button", {
3665
+ showAgenda && /* @__PURE__ */ jsxs16("button", {
3712
3666
  type: "button",
3713
3667
  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"),
3714
3668
  onClick: () => onTabChange("agenda"),
@@ -3738,7 +3692,7 @@ function AuthenticatedContent({
3738
3692
  children: transitionLabel
3739
3693
  }),
3740
3694
  /* @__PURE__ */ jsx18(ConnectedBody, {
3741
- activeTab,
3695
+ activeTab: effectiveTab,
3742
3696
  autoFocusChat
3743
3697
  })
3744
3698
  ]
@@ -3884,10 +3838,8 @@ function LiveAgent(props) {
3884
3838
  errorCode,
3885
3839
  startSession,
3886
3840
  pauseSession,
3887
- resumeSession: resumeSessionById,
3888
3841
  disconnect,
3889
3842
  isPaused,
3890
- resumableSessions,
3891
3843
  historyMessages,
3892
3844
  pendingScreenStream,
3893
3845
  bearerToken
@@ -3907,12 +3859,6 @@ function LiveAgent(props) {
3907
3859
  return;
3908
3860
  disconnect();
3909
3861
  }, [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
3862
  const [isPanelOpen, setIsPanelOpen] = useState11(defaultOpen);
3917
3863
  const [isMinimized, setIsMinimized] = useState11(minimizable && !defaultOpen);
3918
3864
  const [sidebarTab, setSidebarTab] = useState11("agenda");
@@ -3926,18 +3872,35 @@ function LiveAgent(props) {
3926
3872
  enabled: hasModuleSelector && isPanelOpen,
3927
3873
  bearerToken
3928
3874
  });
3875
+ const activeModuleForAgent = useMemo5(() => {
3876
+ if (activeModule)
3877
+ return activeModule;
3878
+ if (!hostAgentId)
3879
+ return null;
3880
+ return availableModules.find((m) => m.id === hostAgentId) ?? null;
3881
+ }, [activeModule, hostAgentId, availableModules]);
3882
+ const agentMode = activeModuleForAgent?.mode ?? null;
3883
+ const resumableSession = useMemo5(() => getResumableSession(activeModuleForAgent), [activeModuleForAgent]);
3884
+ const resumeSession = useCallback8(async () => {
3885
+ if (!resumableSession)
3886
+ return;
3887
+ await startSession({
3888
+ agentId: resumableSession.agentId,
3889
+ agentControls,
3890
+ existingSessionId: resumableSession.id
3891
+ });
3892
+ }, [resumableSession, startSession, agentControls]);
3929
3893
  const selectModule = useCallback8((moduleId) => {
3930
3894
  const found = availableModules.find((m) => m.id === moduleId);
3931
3895
  if (!found)
3932
3896
  return;
3933
3897
  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]);
3898
+ startSession({
3899
+ agentId: found.id,
3900
+ agentControls: found.controls,
3901
+ existingSessionId: getResumableSessionId(found.currentSession, found.mode)
3902
+ });
3903
+ }, [availableModules, startSession]);
3941
3904
  const [welcomeDismissed, setWelcomeDismissed] = useState11(false);
3942
3905
  const dismissWelcome = useCallback8(() => setWelcomeDismissed(true), []);
3943
3906
  const [currentPosition, setCurrentPosition] = useState11(() => {
@@ -3969,7 +3932,7 @@ function LiveAgent(props) {
3969
3932
  }, [minimizable]);
3970
3933
  const isConnected = connection !== null;
3971
3934
  const isAuthenticated = !!userToken || !!authTokenProp || auth.isAuthenticated;
3972
- const ctx = useMemo6(() => ({
3935
+ const ctx = useMemo5(() => ({
3973
3936
  connection,
3974
3937
  shouldConnect,
3975
3938
  isConnected,
@@ -3984,7 +3947,6 @@ function LiveAgent(props) {
3984
3947
  disconnect,
3985
3948
  isPaused,
3986
3949
  resumableSession,
3987
- resumableSessions,
3988
3950
  historyMessages,
3989
3951
  phasesSnapshot,
3990
3952
  setPhasesSnapshot,
@@ -4011,6 +3973,7 @@ function LiveAgent(props) {
4011
3973
  autoFocusChat,
4012
3974
  captureMode,
4013
3975
  agentId,
3976
+ agentMode,
4014
3977
  agentControls,
4015
3978
  hasModuleSelector,
4016
3979
  availableModules,
@@ -4034,7 +3997,6 @@ function LiveAgent(props) {
4034
3997
  disconnect,
4035
3998
  isPaused,
4036
3999
  resumableSession,
4037
- resumableSessions,
4038
4000
  historyMessages,
4039
4001
  phasesSnapshot,
4040
4002
  isPanelOpen,
@@ -4059,6 +4021,7 @@ function LiveAgent(props) {
4059
4021
  autoFocusChat,
4060
4022
  captureMode,
4061
4023
  agentId,
4024
+ agentMode,
4062
4025
  agentControls,
4063
4026
  hasModuleSelector,
4064
4027
  availableModules,