@invergent/agent-chat-react 1.6.0 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -266,7 +266,9 @@ function Button({
266
266
  function BrowserControlBar({
267
267
  sessionId,
268
268
  hasControl,
269
- adapter
269
+ adapter,
270
+ onControlAcquired,
271
+ onControlReleased
270
272
  }) {
271
273
  const [pending, setPending] = react.useState(false);
272
274
  const [error, setError] = react.useState(null);
@@ -277,8 +279,10 @@ function BrowserControlBar({
277
279
  try {
278
280
  if (hasControl) {
279
281
  await adapter.releaseBrowserControl(sessionId);
282
+ onControlReleased?.();
280
283
  } else {
281
284
  await adapter.acquireBrowserControl(sessionId);
285
+ onControlAcquired?.();
282
286
  }
283
287
  } catch (nextError) {
284
288
  setError(nextError.message);
@@ -308,11 +312,14 @@ function BrowserControlBar({
308
312
  error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error })
309
313
  ] });
310
314
  }
311
- function BrowserLiveView({ src }) {
315
+ function BrowserLiveView({
316
+ src,
317
+ testId = "browser-iframe"
318
+ }) {
312
319
  return /* @__PURE__ */ jsxRuntime.jsx(
313
320
  "iframe",
314
321
  {
315
- "data-testid": "browser-iframe",
322
+ "data-testid": testId,
316
323
  title: "Browser live view",
317
324
  src,
318
325
  className: "h-full w-full border-0 bg-black",
@@ -340,38 +347,379 @@ function BrowserStatusDot({
340
347
  }
341
348
  );
342
349
  }
343
- function BrowserPane({ sessionId, state, adapter }) {
344
- const liveViewUrl = react.useMemo(
345
- () => adapter.browserLiveViewUrl(sessionId),
346
- [adapter, sessionId]
350
+
351
+ // src/components/ui/dialog.tsx
352
+ init_utils();
353
+ function Dialog({
354
+ ...props
355
+ }) {
356
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Root, { "data-slot": "dialog", ...props });
357
+ }
358
+ function DialogPortal({
359
+ ...props
360
+ }) {
361
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Portal, { "data-slot": "dialog-portal", ...props });
362
+ }
363
+ function DialogOverlay({
364
+ className,
365
+ ...props
366
+ }) {
367
+ return /* @__PURE__ */ jsxRuntime.jsx(
368
+ radixUi.Dialog.Overlay,
369
+ {
370
+ "data-slot": "dialog-overlay",
371
+ className: cn(
372
+ "fixed inset-0 isolate z-50 bg-black/20 duration-100 supports-backdrop-filter:backdrop-blur-sm data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
373
+ className
374
+ ),
375
+ ...props
376
+ }
347
377
  );
348
- const hasLiveView = state.status !== "provisioning" && state.status !== "closed";
378
+ }
379
+ function DialogContent({
380
+ className,
381
+ children,
382
+ showCloseButton = true,
383
+ ...props
384
+ }) {
385
+ return /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
386
+ /* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
387
+ /* @__PURE__ */ jsxRuntime.jsxs(
388
+ radixUi.Dialog.Content,
389
+ {
390
+ "data-slot": "dialog-content",
391
+ className: cn(
392
+ "fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 rounded-none bg-popover p-6 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-md data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
393
+ className
394
+ ),
395
+ ...props,
396
+ children: [
397
+ children,
398
+ showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
399
+ Button,
400
+ {
401
+ variant: "ghost",
402
+ className: "absolute top-5 right-5 bg-secondary",
403
+ size: "icon-sm",
404
+ children: [
405
+ /* @__PURE__ */ jsxRuntime.jsx(
406
+ lucideReact.XIcon,
407
+ {}
408
+ ),
409
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Close" })
410
+ ]
411
+ }
412
+ ) })
413
+ ]
414
+ }
415
+ )
416
+ ] });
417
+ }
418
+ function DialogHeader({ className, ...props }) {
419
+ return /* @__PURE__ */ jsxRuntime.jsx(
420
+ "div",
421
+ {
422
+ "data-slot": "dialog-header",
423
+ className: cn("flex flex-col gap-2", className),
424
+ ...props
425
+ }
426
+ );
427
+ }
428
+ function DialogFooter({
429
+ className,
430
+ showCloseButton = false,
431
+ children,
432
+ ...props
433
+ }) {
349
434
  return /* @__PURE__ */ jsxRuntime.jsxs(
350
435
  "div",
351
436
  {
352
- "data-testid": "browser-pane",
353
- className: "flex h-full min-h-0 flex-col bg-background",
437
+ "data-slot": "dialog-footer",
438
+ className: cn(
439
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
440
+ className
441
+ ),
442
+ ...props,
354
443
  children: [
355
- /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex min-h-10 items-center gap-2 border-b border-line bg-card px-3 text-xs text-foreground", children: [
356
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
357
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Browser" }),
358
- /* @__PURE__ */ jsxRuntime.jsx(BrowserStatusDot, { status: state.status }),
359
- state.controlOwner && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate text-amber-500", children: [
360
- state.controlOwner,
361
- " has control"
362
- ] })
363
- ] }),
364
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 bg-black", children: state.status === "provisioning" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Starting browser..." }) : state.status === "closed" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser closed." }) : /* @__PURE__ */ jsxRuntime.jsx(BrowserLiveView, { src: liveViewUrl }) }),
365
- hasLiveView && /* @__PURE__ */ jsxRuntime.jsx(
366
- BrowserControlBar,
367
- {
368
- sessionId,
369
- hasControl: state.status === "user-control",
370
- adapter
371
- }
372
- )
444
+ children,
445
+ showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", children: "Close" }) })
446
+ ]
447
+ }
448
+ );
449
+ }
450
+ function DialogTitle({
451
+ className,
452
+ ...props
453
+ }) {
454
+ return /* @__PURE__ */ jsxRuntime.jsx(
455
+ radixUi.Dialog.Title,
456
+ {
457
+ "data-slot": "dialog-title",
458
+ className: cn(
459
+ "font-heading text-lg leading-none font-semibold tracking-wider uppercase",
460
+ className
461
+ ),
462
+ ...props
463
+ }
464
+ );
465
+ }
466
+ function DialogDescription({
467
+ className,
468
+ ...props
469
+ }) {
470
+ return /* @__PURE__ */ jsxRuntime.jsx(
471
+ radixUi.Dialog.Description,
472
+ {
473
+ "data-slot": "dialog-description",
474
+ className: cn(
475
+ "mt-0.5 text-sm leading-relaxed text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
476
+ className
477
+ ),
478
+ ...props
479
+ }
480
+ );
481
+ }
482
+
483
+ // src/components/ui/tooltip.tsx
484
+ init_utils();
485
+ function TooltipProvider({
486
+ delayDuration = 0,
487
+ ...props
488
+ }) {
489
+ return /* @__PURE__ */ jsxRuntime.jsx(
490
+ radixUi.Tooltip.Provider,
491
+ {
492
+ "data-slot": "tooltip-provider",
493
+ delayDuration,
494
+ ...props
495
+ }
496
+ );
497
+ }
498
+ function Tooltip({
499
+ ...props
500
+ }) {
501
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Root, { "data-slot": "tooltip", ...props });
502
+ }
503
+ function TooltipTrigger({
504
+ ...props
505
+ }) {
506
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props });
507
+ }
508
+ function TooltipContent({
509
+ className,
510
+ sideOffset = 0,
511
+ children,
512
+ ...props
513
+ }) {
514
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
515
+ radixUi.Tooltip.Content,
516
+ {
517
+ "data-slot": "tooltip-content",
518
+ sideOffset,
519
+ className: cn(
520
+ "z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-none bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-none data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
521
+ className
522
+ ),
523
+ ...props,
524
+ children: [
525
+ children,
526
+ /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground" })
373
527
  ]
374
528
  }
529
+ ) });
530
+ }
531
+ function BrowserPane({ sessionId, state, adapter }) {
532
+ const [fullscreenOpen, setFullscreenOpen] = react.useState(false);
533
+ const [openFullscreenOnControl, setOpenFullscreenOnControl] = react.useState(false);
534
+ const [localControlActive, setLocalControlActive] = react.useState(false);
535
+ const [previewSnapshot, setPreviewSnapshot] = react.useState(null);
536
+ const [previewLoading, setPreviewLoading] = react.useState(false);
537
+ const [previewError, setPreviewError] = react.useState(null);
538
+ const hasLiveViewAdapter = typeof adapter.browserLiveViewUrl === "function";
539
+ const hasPreviewAdapter = typeof adapter.getBrowserPreviewSnapshot === "function";
540
+ const hasControlAdapter = typeof adapter.acquireBrowserControl === "function" && typeof adapter.releaseBrowserControl === "function";
541
+ const liveViewUrl = react.useMemo(() => {
542
+ if (!hasLiveViewAdapter) return "";
543
+ return adapter.browserLiveViewUrl(sessionId);
544
+ }, [adapter, hasLiveViewAdapter, sessionId]);
545
+ const hasLiveView = state.status !== "provisioning" && state.status !== "closed";
546
+ const hasUserControl = localControlActive;
547
+ const canUseLiveView = hasLiveView && hasUserControl && Boolean(liveViewUrl);
548
+ const canOpenPreview = hasLiveView && (hasPreviewAdapter || canUseLiveView);
549
+ const canOpenFullscreen = canOpenPreview;
550
+ const refreshPreview = react.useCallback(
551
+ async (signal) => {
552
+ if (!hasPreviewAdapter || canUseLiveView) return;
553
+ setPreviewLoading(true);
554
+ setPreviewError(null);
555
+ try {
556
+ const snapshot = await adapter.getBrowserPreviewSnapshot?.(sessionId);
557
+ if (signal?.aborted) return;
558
+ setPreviewSnapshot(snapshot ?? null);
559
+ if (!snapshot) {
560
+ setPreviewError("Browser preview is unavailable.");
561
+ }
562
+ } catch (error) {
563
+ if (signal?.aborted) return;
564
+ setPreviewSnapshot(null);
565
+ setPreviewError(
566
+ error instanceof Error ? error.message : "Browser preview failed."
567
+ );
568
+ } finally {
569
+ if (!signal?.aborted) setPreviewLoading(false);
570
+ }
571
+ },
572
+ [adapter, canUseLiveView, hasPreviewAdapter, sessionId]
573
+ );
574
+ react.useEffect(() => {
575
+ setFullscreenOpen(false);
576
+ setOpenFullscreenOnControl(false);
577
+ setLocalControlActive(false);
578
+ setPreviewSnapshot(null);
579
+ setPreviewLoading(false);
580
+ setPreviewError(null);
581
+ }, [sessionId]);
582
+ react.useEffect(() => {
583
+ if (!openFullscreenOnControl) return;
584
+ if (!canUseLiveView) return;
585
+ setFullscreenOpen(true);
586
+ setOpenFullscreenOnControl(false);
587
+ }, [canUseLiveView, openFullscreenOnControl]);
588
+ const handleFullscreenOpenChange = react.useCallback(
589
+ (open) => {
590
+ setFullscreenOpen(open);
591
+ if (open || !hasUserControl || !hasControlAdapter) return;
592
+ setLocalControlActive(false);
593
+ void adapter.releaseBrowserControl(sessionId).catch((error) => {
594
+ console.error("Failed to release browser control", error);
595
+ });
596
+ },
597
+ [adapter, hasControlAdapter, hasUserControl, sessionId]
598
+ );
599
+ react.useEffect(() => {
600
+ if (!hasLiveView || canUseLiveView) {
601
+ setPreviewLoading(false);
602
+ setPreviewError(null);
603
+ return;
604
+ }
605
+ if (!hasPreviewAdapter) return;
606
+ const abort = new AbortController();
607
+ void refreshPreview(abort.signal);
608
+ const timer = window.setInterval(() => {
609
+ void refreshPreview(abort.signal);
610
+ }, 5e3);
611
+ return () => {
612
+ abort.abort();
613
+ window.clearInterval(timer);
614
+ };
615
+ }, [
616
+ canUseLiveView,
617
+ fullscreenOpen,
618
+ hasLiveView,
619
+ hasPreviewAdapter,
620
+ refreshPreview
621
+ ]);
622
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
623
+ /* @__PURE__ */ jsxRuntime.jsxs(
624
+ "div",
625
+ {
626
+ "data-testid": "browser-pane",
627
+ className: "flex h-full min-h-0 flex-col bg-background",
628
+ children: [
629
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex min-h-10 items-center gap-2 border-b border-line bg-card px-3 text-xs text-foreground", children: [
630
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
631
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Browser" }),
632
+ /* @__PURE__ */ jsxRuntime.jsx(BrowserStatusDot, { status: state.status }),
633
+ state.controlOwner && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate text-amber-500", children: [
634
+ state.controlOwner,
635
+ " has control"
636
+ ] }),
637
+ canOpenFullscreen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
638
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
639
+ "button",
640
+ {
641
+ type: "button",
642
+ "aria-label": "Maximize browser",
643
+ className: "inline-flex size-7 items-center justify-center text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
644
+ onClick: () => setFullscreenOpen(true),
645
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2Icon, { className: "size-3.5", "aria-hidden": "true" })
646
+ }
647
+ ) }),
648
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: "Full screen" })
649
+ ] }) }) })
650
+ ] }),
651
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 bg-black", children: state.status === "provisioning" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Starting browser..." }) : state.status === "closed" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser closed." }) : canUseLiveView ? /* @__PURE__ */ jsxRuntime.jsx(BrowserLiveView, { src: liveViewUrl }) : previewSnapshot ? /* @__PURE__ */ jsxRuntime.jsx(
652
+ BrowserPreviewImage,
653
+ {
654
+ src: previewSnapshot.src,
655
+ fit: "contain",
656
+ testId: "browser-preview-image"
657
+ }
658
+ ) : previewLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Loading browser preview..." }) : previewError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: previewError }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser preview is unavailable." }) }),
659
+ hasLiveView && hasControlAdapter && /* @__PURE__ */ jsxRuntime.jsx(
660
+ BrowserControlBar,
661
+ {
662
+ sessionId,
663
+ hasControl: localControlActive,
664
+ adapter,
665
+ onControlAcquired: () => {
666
+ setLocalControlActive(true);
667
+ setOpenFullscreenOnControl(true);
668
+ },
669
+ onControlReleased: () => setLocalControlActive(false)
670
+ }
671
+ )
672
+ ]
673
+ }
674
+ ),
675
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open: fullscreenOpen, onOpenChange: handleFullscreenOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(
676
+ DialogContent,
677
+ {
678
+ "aria-describedby": void 0,
679
+ className: "flex h-screen w-screen max-w-none flex-col gap-0 overflow-hidden rounded-none border-0 bg-background p-0 shadow-none ring-0 sm:max-w-none",
680
+ children: [
681
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { className: "h-10 shrink-0 flex-row items-center gap-2 border-b border-line bg-card px-4 py-0", children: [
682
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
683
+ /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { className: "text-xs normal-case tracking-normal", children: "Browser" }),
684
+ /* @__PURE__ */ jsxRuntime.jsx(BrowserStatusDot, { status: state.status }),
685
+ state.controlOwner && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate text-xs text-amber-500", children: [
686
+ state.controlOwner,
687
+ " has control"
688
+ ] })
689
+ ] }),
690
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 bg-black", children: canUseLiveView ? /* @__PURE__ */ jsxRuntime.jsx(
691
+ BrowserLiveView,
692
+ {
693
+ src: liveViewUrl,
694
+ testId: "browser-fullscreen-iframe"
695
+ }
696
+ ) : previewSnapshot ? /* @__PURE__ */ jsxRuntime.jsx(
697
+ BrowserPreviewImage,
698
+ {
699
+ src: previewSnapshot.src,
700
+ fit: "contain",
701
+ testId: "browser-fullscreen-preview-image"
702
+ }
703
+ ) : previewLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Loading browser preview..." }) : previewError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: previewError }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser preview is unavailable." }) })
704
+ ]
705
+ }
706
+ ) })
707
+ ] });
708
+ }
709
+ function BrowserPreviewImage({
710
+ src,
711
+ fit,
712
+ testId
713
+ }) {
714
+ return /* @__PURE__ */ jsxRuntime.jsx(
715
+ "img",
716
+ {
717
+ "data-testid": testId,
718
+ src,
719
+ alt: "Browser preview",
720
+ className: `h-full w-full bg-black ${fit === "cover" ? "object-cover object-top" : "object-contain"}`,
721
+ draggable: false
722
+ }
375
723
  );
376
724
  }
377
725
 
@@ -446,56 +794,7 @@ var ConversationScrollButton = ({
446
794
  );
447
795
  };
448
796
 
449
- // src/components/ui/tooltip.tsx
450
- init_utils();
451
- function TooltipProvider({
452
- delayDuration = 0,
453
- ...props
454
- }) {
455
- return /* @__PURE__ */ jsxRuntime.jsx(
456
- radixUi.Tooltip.Provider,
457
- {
458
- "data-slot": "tooltip-provider",
459
- delayDuration,
460
- ...props
461
- }
462
- );
463
- }
464
- function Tooltip({
465
- ...props
466
- }) {
467
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Root, { "data-slot": "tooltip", ...props });
468
- }
469
- function TooltipTrigger({
470
- ...props
471
- }) {
472
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props });
473
- }
474
- function TooltipContent({
475
- className,
476
- sideOffset = 0,
477
- children,
478
- ...props
479
- }) {
480
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
481
- radixUi.Tooltip.Content,
482
- {
483
- "data-slot": "tooltip-content",
484
- sideOffset,
485
- className: cn(
486
- "z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-none bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-none data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
487
- className
488
- ),
489
- ...props,
490
- children: [
491
- children,
492
- /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground" })
493
- ]
494
- }
495
- ) });
496
- }
497
-
498
- // src/components/ai-elements/message.tsx
797
+ // src/components/ai-elements/message.tsx
499
798
  init_utils();
500
799
  var Message = ({ className, from, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(
501
800
  "div",
@@ -879,66 +1178,82 @@ function TimelineSeparator({
879
1178
  );
880
1179
  }
881
1180
  function BrowserActivityGroup({ calls }) {
882
- const [open, setOpen] = react.useState(false);
883
- const latest = calls[calls.length - 1];
884
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-none border border-line bg-card text-xs", children: [
885
- /* @__PURE__ */ jsxRuntime.jsxs(
886
- "button",
887
- {
888
- type: "button",
889
- onClick: () => setOpen((current) => !current),
890
- className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-muted/40",
891
- children: [
892
- open ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDownIcon, { className: "size-3 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "size-3 shrink-0", "aria-hidden": "true" }),
893
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZapIcon, { className: "size-3 shrink-0 text-amber-500", "aria-hidden": "true" }),
894
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: "browser" }),
895
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate text-muted-foreground", children: [
896
- "(",
897
- calls.length,
898
- " ",
899
- calls.length === 1 ? "action" : "actions",
900
- " - latest:",
901
- " ",
902
- latest ? summarise(latest) : "none",
903
- ")"
904
- ] })
905
- ]
906
- }
907
- ),
908
- open && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "border-t border-line py-1", children: calls.map((call) => {
1181
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border border-line bg-card px-3 py-2 text-xs", children: [
1182
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZapIcon, { className: "size-3 shrink-0 text-amber-500", "aria-hidden": "true" }),
1183
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 font-semibold text-foreground", children: "Browser:" }),
1184
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate text-muted-foreground", children: calls.map((call, index) => {
909
1185
  const error = stringValue(parseJson(call.result).error);
910
- return /* @__PURE__ */ jsxRuntime.jsxs(
911
- "li",
912
- {
913
- className: "flex items-center gap-2 px-6 py-1 font-mono text-[11px]",
914
- children: [
915
- error && /* @__PURE__ */ jsxRuntime.jsx(
916
- lucideReact.AlertCircleIcon,
917
- {
918
- "data-testid": `activity-error-${call.id}`,
919
- className: "size-3 shrink-0 text-destructive",
920
- "aria-hidden": "true"
921
- }
922
- ),
923
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: summarise(call) }),
924
- error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto max-w-40 truncate text-destructive", children: error })
925
- ]
926
- },
927
- call.id
928
- );
1186
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1187
+ index > 0 && ", ",
1188
+ /* @__PURE__ */ jsxRuntime.jsx(
1189
+ "span",
1190
+ {
1191
+ className: error ? "text-destructive" : void 0,
1192
+ "data-testid": error ? `activity-error-${call.id}` : void 0,
1193
+ title: error || void 0,
1194
+ children: summarize(call)
1195
+ }
1196
+ )
1197
+ ] }, call.id);
929
1198
  }) })
930
1199
  ] });
931
1200
  }
932
- function summarise(call) {
933
- const verb = call.toolName.replace(/^browser_/, "");
1201
+ function summarize(call) {
934
1202
  const args = parseJson(call.args);
935
- const ref = stringValue(args.ref);
936
- const url = stringValue(args.url);
937
- const text = stringValue(args.text);
938
- if (url) return `${verb} ${url}`;
939
- if (ref) return `${verb} ${ref}`;
940
- if (text) return `${verb} "${text.slice(0, 24)}"`;
941
- return verb;
1203
+ const handler = ACTION_SUMMARIES[call.toolName];
1204
+ return handler ? handler(args) : call.toolName.replace(/^browser_/, "");
1205
+ }
1206
+ var ACTION_SUMMARIES = {
1207
+ browser_navigate: (args) => `navigate to ${stringValue(args.url) || "?"}`,
1208
+ browser_get_state: () => "get state",
1209
+ browser_close: () => "close",
1210
+ browser_click: (args) => {
1211
+ const ref = stringValue(args.ref);
1212
+ if (ref) return `click ${ref}`;
1213
+ const x = asNumber(args.x);
1214
+ const y = asNumber(args.y);
1215
+ if (x !== null && y !== null) return `click (${x}, ${y})`;
1216
+ return "click";
1217
+ },
1218
+ browser_type: (args) => {
1219
+ const text = stringValue(args.text);
1220
+ const ref = stringValue(args.ref);
1221
+ const quoted = text ? `"${truncateText(text)}"` : "";
1222
+ if (!quoted) return ref ? `type into ${ref}` : "type";
1223
+ return ref ? `type ${quoted} into ${ref}` : `type ${quoted}`;
1224
+ },
1225
+ browser_press_key: (args) => {
1226
+ const keys = Array.isArray(args.keys) ? args.keys.map(String) : [];
1227
+ return keys.length ? `press ${keys.join("+")}` : "press key";
1228
+ },
1229
+ browser_scroll: (args) => {
1230
+ const x = asNumber(args.x);
1231
+ const y = asNumber(args.y);
1232
+ const dx = asNumber(args.delta_x) ?? 0;
1233
+ const dy = asNumber(args.delta_y) ?? 0;
1234
+ const delta = dx || dy ? `\u0394(${dx}, ${dy})` : "";
1235
+ const origin = x !== null && y !== null ? `at (${x}, ${y})` : "";
1236
+ return ["scroll", delta, origin].filter(Boolean).join(" ");
1237
+ },
1238
+ browser_drag: (args) => {
1239
+ const path = Array.isArray(args.path) ? args.path : [];
1240
+ if (path.length < 2) return "drag";
1241
+ const first = path[0];
1242
+ const last = path[path.length - 1];
1243
+ const point = (p) => `(${asNumber(p[0]) ?? "?"}, ${asNumber(p[1]) ?? "?"})`;
1244
+ return `drag ${point(first)} \u2192 ${point(last)}`;
1245
+ },
1246
+ browser_wait: (args) => {
1247
+ const ms = asNumber(args.ms);
1248
+ return ms === null ? "wait" : `wait ${ms}ms`;
1249
+ },
1250
+ browser_screenshot: (args) => args.annotate === true ? "screenshot (annotated)" : "screenshot"
1251
+ };
1252
+ function truncateText(text, max = 32) {
1253
+ return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
1254
+ }
1255
+ function asNumber(value) {
1256
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
942
1257
  }
943
1258
  function parseJson(raw) {
944
1259
  if (!raw) return {};
@@ -1997,140 +2312,20 @@ function WebToolBlock({ tc }) {
1997
2312
  displayText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: displayText })
1998
2313
  ] });
1999
2314
  }
2000
-
2001
- // src/components/ui/dialog.tsx
2002
- init_utils();
2003
- function Dialog({
2004
- ...props
2005
- }) {
2006
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Root, { "data-slot": "dialog", ...props });
2007
- }
2008
- function DialogPortal({
2009
- ...props
2010
- }) {
2011
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Portal, { "data-slot": "dialog-portal", ...props });
2012
- }
2013
- function DialogOverlay({
2014
- className,
2015
- ...props
2016
- }) {
2017
- return /* @__PURE__ */ jsxRuntime.jsx(
2018
- radixUi.Dialog.Overlay,
2019
- {
2020
- "data-slot": "dialog-overlay",
2021
- className: cn(
2022
- "fixed inset-0 isolate z-50 bg-black/20 duration-100 supports-backdrop-filter:backdrop-blur-sm data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
2023
- className
2024
- ),
2025
- ...props
2026
- }
2027
- );
2028
- }
2029
- function DialogContent({
2030
- className,
2031
- children,
2032
- showCloseButton = true,
2033
- ...props
2034
- }) {
2035
- return /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
2036
- /* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
2037
- /* @__PURE__ */ jsxRuntime.jsxs(
2038
- radixUi.Dialog.Content,
2039
- {
2040
- "data-slot": "dialog-content",
2041
- className: cn(
2042
- "fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 rounded-none bg-popover p-6 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-md data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
2043
- className
2044
- ),
2045
- ...props,
2046
- children: [
2047
- children,
2048
- showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
2049
- Button,
2050
- {
2051
- variant: "ghost",
2052
- className: "absolute top-5 right-5 bg-secondary",
2053
- size: "icon-sm",
2054
- children: [
2055
- /* @__PURE__ */ jsxRuntime.jsx(
2056
- lucideReact.XIcon,
2057
- {}
2058
- ),
2059
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Close" })
2060
- ]
2061
- }
2062
- ) })
2063
- ]
2064
- }
2065
- )
2315
+ function VisionAnalyzeBlock({ tc }) {
2316
+ let image = "";
2317
+ try {
2318
+ const args = JSON.parse(tc.args);
2319
+ const ref = String(args.image ?? args.image_url ?? args.image_path ?? "");
2320
+ image = ref.split("/").pop() ?? ref;
2321
+ } catch {
2322
+ }
2323
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm py-0.5", children: [
2324
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
2325
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: "Vision Analyze" }),
2326
+ image && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: image })
2066
2327
  ] });
2067
2328
  }
2068
- function DialogHeader({ className, ...props }) {
2069
- return /* @__PURE__ */ jsxRuntime.jsx(
2070
- "div",
2071
- {
2072
- "data-slot": "dialog-header",
2073
- className: cn("flex flex-col gap-2", className),
2074
- ...props
2075
- }
2076
- );
2077
- }
2078
- function DialogFooter({
2079
- className,
2080
- showCloseButton = false,
2081
- children,
2082
- ...props
2083
- }) {
2084
- return /* @__PURE__ */ jsxRuntime.jsxs(
2085
- "div",
2086
- {
2087
- "data-slot": "dialog-footer",
2088
- className: cn(
2089
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
2090
- className
2091
- ),
2092
- ...props,
2093
- children: [
2094
- children,
2095
- showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", children: "Close" }) })
2096
- ]
2097
- }
2098
- );
2099
- }
2100
- function DialogTitle({
2101
- className,
2102
- ...props
2103
- }) {
2104
- return /* @__PURE__ */ jsxRuntime.jsx(
2105
- radixUi.Dialog.Title,
2106
- {
2107
- "data-slot": "dialog-title",
2108
- className: cn(
2109
- "font-heading text-lg leading-none font-semibold tracking-wider uppercase",
2110
- className
2111
- ),
2112
- ...props
2113
- }
2114
- );
2115
- }
2116
- function DialogDescription({
2117
- className,
2118
- ...props
2119
- }) {
2120
- return /* @__PURE__ */ jsxRuntime.jsx(
2121
- radixUi.Dialog.Description,
2122
- {
2123
- "data-slot": "dialog-description",
2124
- className: cn(
2125
- "mt-0.5 text-sm leading-relaxed text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
2126
- className
2127
- ),
2128
- ...props
2129
- }
2130
- );
2131
- }
2132
-
2133
- // src/components/chat/tools/file-tools.tsx
2134
2329
  init_utils();
2135
2330
 
2136
2331
  // src/components/chat/diff-viewer.tsx
@@ -4097,6 +4292,8 @@ function ToolCallBlock({
4097
4292
  case "web_search":
4098
4293
  case "web_crawl":
4099
4294
  return /* @__PURE__ */ jsxRuntime.jsx(WebToolBlock, { tc });
4295
+ case "vision_analyze":
4296
+ return /* @__PURE__ */ jsxRuntime.jsx(VisionAnalyzeBlock, { tc });
4100
4297
  case "read_file":
4101
4298
  return /* @__PURE__ */ jsxRuntime.jsx(ReadFileBlock, { tc, onFileSelect });
4102
4299
  case "write_file":
@@ -5617,18 +5814,21 @@ function ChatComposerInner({
5617
5814
  [menuOpen, filteredCommands, selectedIndex, handleCommandSelect, textInput]
5618
5815
  );
5619
5816
  const handleSubmit = react.useCallback(
5620
- (message) => {
5817
+ async (message) => {
5621
5818
  const text = message.text.trim();
5622
- if (!text || isRunning || disabled) return;
5819
+ if (!text || disabled) return;
5623
5820
  const images = [];
5624
5821
  for (const file of message.files) {
5625
5822
  if (file.mediaType?.startsWith("image/") && file.url) {
5626
5823
  images.push({ data: file.url, mimeType: file.mediaType });
5627
5824
  }
5628
5825
  }
5629
- onSend(text, images.length > 0 ? images : void 0);
5826
+ if (isRunning) {
5827
+ await onStop();
5828
+ }
5829
+ await onSend(text, images.length > 0 ? images : void 0);
5630
5830
  },
5631
- [onSend, isRunning, disabled]
5831
+ [onSend, onStop, isRunning, disabled]
5632
5832
  );
5633
5833
  return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: menuOpen, onOpenChange: (open) => {
5634
5834
  if (!open) {
@@ -7489,10 +7689,9 @@ function WorkspacePanel({
7489
7689
  sessionId,
7490
7690
  selectedPath,
7491
7691
  onSelectedPathChange,
7492
- collapsed = false,
7493
- onCollapsedChange,
7494
7692
  refreshSignal = 0,
7495
- disabled = false
7693
+ disabled = false,
7694
+ fillParent = false
7496
7695
  }) {
7497
7696
  const fileInputRef = react.useRef(null);
7498
7697
  const [entries, setEntries] = react.useState([]);
@@ -7693,34 +7892,17 @@ function WorkspacePanel({
7693
7892
  window.removeEventListener("mouseup", onMouseUp);
7694
7893
  };
7695
7894
  }, []);
7696
- if (collapsed) {
7697
- return /* @__PURE__ */ jsxRuntime.jsx(
7698
- "aside",
7699
- {
7700
- "data-testid": "workspace-panel",
7701
- role: "button",
7702
- tabIndex: 0,
7703
- className: "relative z-10 flex min-h-0 w-10 shrink-0 cursor-pointer items-center justify-center border-l border-muted-foreground/20 bg-card text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30",
7704
- "aria-label": "Expand workspace",
7705
- onClick: () => onCollapsedChange?.(false),
7706
- onKeyDown: (event) => {
7707
- if (event.key === "Enter" || event.key === " ") {
7708
- event.preventDefault();
7709
- onCollapsedChange?.(false);
7710
- }
7711
- },
7712
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpenIcon, { className: "size-4 text-amber-500" })
7713
- }
7714
- );
7715
- }
7716
7895
  return /* @__PURE__ */ jsxRuntime.jsxs(
7717
7896
  "aside",
7718
7897
  {
7719
7898
  "data-testid": "workspace-panel",
7720
- className: "relative z-10 flex min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
7721
- style: { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
7899
+ className: cn(
7900
+ "relative z-10 flex h-full min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
7901
+ fillParent && "w-full"
7902
+ ),
7903
+ style: fillParent ? void 0 : { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
7722
7904
  children: [
7723
- /* @__PURE__ */ jsxRuntime.jsx(
7905
+ !fillParent && /* @__PURE__ */ jsxRuntime.jsx(
7724
7906
  "div",
7725
7907
  {
7726
7908
  className: "absolute inset-y-0 left-0 z-20 w-1.5 cursor-col-resize transition-colors hover:bg-primary/20 active:bg-primary/30",
@@ -7746,19 +7928,6 @@ function WorkspacePanel({
7746
7928
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-sm font-medium text-foreground", children: rootName }),
7747
7929
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs text-faint", children: disabled ? "Workspace - Read-only" : "Workspace" })
7748
7930
  ] }),
7749
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
7750
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
7751
- Button,
7752
- {
7753
- variant: "ghost",
7754
- size: "icon-sm",
7755
- onClick: () => onCollapsedChange?.(true),
7756
- "aria-label": "Collapse workspace",
7757
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "size-4" })
7758
- }
7759
- ) }),
7760
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: "Collapse" })
7761
- ] }),
7762
7931
  /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
7763
7932
  /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
7764
7933
  Button,
@@ -7931,7 +8100,8 @@ var WORKSPACE_MUTATING_TOOLS = /* @__PURE__ */ new Set([
7931
8100
  "terminal",
7932
8101
  "write_file",
7933
8102
  "patch",
7934
- "execute_code"
8103
+ "execute_code",
8104
+ "browser_screenshot"
7935
8105
  ]);
7936
8106
  var AGENT_CHAT_LISTENED_EVENTS = [
7937
8107
  "user.message",
@@ -8857,7 +9027,6 @@ function AgentChat({
8857
9027
  disabled
8858
9028
  }) {
8859
9029
  const [workspacePath, setWorkspacePath] = react.useState(null);
8860
- const [workspaceCollapsed, setWorkspaceCollapsed] = react.useState(false);
8861
9030
  const runtime = useAgentChatRuntime({
8862
9031
  adapter,
8863
9032
  agentId,
@@ -8868,6 +9037,7 @@ function AgentChat({
8868
9037
  const effectiveDisabled = disabled || isReadOnlySession;
8869
9038
  const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
8870
9039
  const browserState = runtime.state.browser;
9040
+ const hasBrowserPanel = browserState !== null && sessionId;
8871
9041
  react.useEffect(() => {
8872
9042
  onMessagesChange?.(runtime.messages);
8873
9043
  }, [onMessagesChange, runtime.messages]);
@@ -8886,53 +9056,769 @@ function AgentChat({
8886
9056
  sessionId,
8887
9057
  onFileSelect: handleFileSelect
8888
9058
  },
8889
- children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "flex flex-1 min-h-0 overflow-hidden bg-background text-sm text-foreground", children: [
8890
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
8891
- ChatThread,
8892
- {
8893
- sessionId,
8894
- messages: runtime.messages,
8895
- isRunning: runtime.isRunning,
8896
- isLoadingHistory: runtime.isLoadingHistory,
8897
- onSend: (content, images) => void runtime.send(content, images),
8898
- onStop: () => void runtime.stop(),
8899
- onRetry: runtime.retry,
8900
- onFileSelect: handleFileSelect,
8901
- disabled: effectiveDisabled,
8902
- disabledReason,
8903
- tokenUsage: runtime.tokenUsage,
8904
- retryIndicator: runtime.retryIndicator
8905
- }
8906
- ) }),
8907
- /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": "right-stack", className: "flex min-h-0 shrink-0 flex-col", children: [
8908
- browserState !== null && sessionId && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 border-b border-line", children: /* @__PURE__ */ jsxRuntime.jsx(
8909
- BrowserPane,
8910
- {
8911
- sessionId,
8912
- state: browserState,
8913
- adapter
8914
- }
8915
- ) }),
8916
- /* @__PURE__ */ jsxRuntime.jsx(
8917
- WorkspacePanel,
8918
- {
8919
- adapter,
8920
- sessionId,
8921
- selectedPath: workspacePath,
8922
- onSelectedPathChange: setWorkspacePath,
8923
- collapsed: workspaceCollapsed,
8924
- onCollapsedChange: setWorkspaceCollapsed,
8925
- refreshSignal: runtime.workspaceRefreshKey,
8926
- disabled: effectiveDisabled
8927
- }
8928
- )
8929
- ] })
8930
- ] }) })
8931
- }
8932
- );
8933
- }
8934
- init_utils();
8935
- var DEFAULT_LIMIT = 50;
9059
+ children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(
9060
+ "section",
9061
+ {
9062
+ "data-testid": "agent-chat-layout",
9063
+ className: hasBrowserPanel ? "relative min-h-0 flex-1 overflow-hidden bg-background text-sm text-foreground" : "flex min-h-0 flex-1 overflow-hidden bg-background text-sm text-foreground",
9064
+ style: { direction: "ltr" },
9065
+ children: [
9066
+ /* @__PURE__ */ jsxRuntime.jsx(
9067
+ "div",
9068
+ {
9069
+ "data-testid": "chat-panel",
9070
+ className: hasBrowserPanel ? "absolute inset-y-0 left-0 flex min-h-0 min-w-0 flex-col overflow-hidden" : "flex min-w-0 flex-1 flex-col overflow-hidden",
9071
+ style: hasBrowserPanel ? { right: 440 } : void 0,
9072
+ children: /* @__PURE__ */ jsxRuntime.jsx(
9073
+ ChatThread,
9074
+ {
9075
+ sessionId,
9076
+ messages: runtime.messages,
9077
+ isRunning: runtime.isRunning,
9078
+ isLoadingHistory: runtime.isLoadingHistory,
9079
+ onSend: (content, images) => runtime.send(content, images),
9080
+ onStop: () => runtime.stop(),
9081
+ onRetry: runtime.retry,
9082
+ onFileSelect: handleFileSelect,
9083
+ disabled: effectiveDisabled,
9084
+ disabledReason,
9085
+ tokenUsage: runtime.tokenUsage,
9086
+ retryIndicator: runtime.retryIndicator
9087
+ }
9088
+ )
9089
+ }
9090
+ ),
9091
+ /* @__PURE__ */ jsxRuntime.jsxs(
9092
+ "div",
9093
+ {
9094
+ "data-testid": "right-stack",
9095
+ className: hasBrowserPanel ? "absolute inset-y-0 flex min-h-0 flex-col overflow-hidden" : "flex min-h-0 shrink-0 flex-col",
9096
+ style: hasBrowserPanel ? { right: 0, width: 440 } : void 0,
9097
+ children: [
9098
+ hasBrowserPanel && /* @__PURE__ */ jsxRuntime.jsx(
9099
+ "div",
9100
+ {
9101
+ "data-testid": "browser-panel",
9102
+ className: "h-1/2 min-h-0 w-full overflow-hidden border-b border-line",
9103
+ children: /* @__PURE__ */ jsxRuntime.jsx(
9104
+ BrowserPane,
9105
+ {
9106
+ sessionId,
9107
+ state: browserState,
9108
+ adapter
9109
+ }
9110
+ )
9111
+ }
9112
+ ),
9113
+ /* @__PURE__ */ jsxRuntime.jsx(
9114
+ "div",
9115
+ {
9116
+ "data-testid": "workspace-panel-frame",
9117
+ className: hasBrowserPanel ? "h-1/2 min-h-0 w-full overflow-hidden" : "min-h-0",
9118
+ children: /* @__PURE__ */ jsxRuntime.jsx(
9119
+ WorkspacePanel,
9120
+ {
9121
+ adapter,
9122
+ sessionId,
9123
+ selectedPath: workspacePath,
9124
+ onSelectedPathChange: setWorkspacePath,
9125
+ refreshSignal: runtime.workspaceRefreshKey,
9126
+ disabled: effectiveDisabled,
9127
+ fillParent: Boolean(hasBrowserPanel)
9128
+ }
9129
+ )
9130
+ }
9131
+ )
9132
+ ]
9133
+ }
9134
+ )
9135
+ ]
9136
+ }
9137
+ ) })
9138
+ }
9139
+ );
9140
+ }
9141
+ init_utils();
9142
+ var DEFAULT_LIMIT = 50;
9143
+ function requireInboxAdapter(adapter) {
9144
+ if (!adapter.listInbox || !adapter.getInboxItem || !adapter.markInboxItemRead || !adapter.acknowledgeInboxItem || !adapter.respondGovernanceInboxItem || !adapter.openInboxStream) {
9145
+ throw new Error("Inbox is not supported by this adapter.");
9146
+ }
9147
+ return adapter;
9148
+ }
9149
+ var KIND_LABEL2 = {
9150
+ input_required: "Input needed",
9151
+ action_required: "Action needed",
9152
+ task_complete: "Task complete",
9153
+ governance_gate: "Approval",
9154
+ progress_checkin: "Progress"
9155
+ };
9156
+ function kindLabel(kind) {
9157
+ return KIND_LABEL2[kind] ?? kind.replace(/[_-]+/g, " ");
9158
+ }
9159
+ function statusLabel(status) {
9160
+ return status.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
9161
+ }
9162
+ function formatRelative(value) {
9163
+ const date = new Date(value);
9164
+ if (Number.isNaN(date.getTime())) return "";
9165
+ return dateFns.formatDistanceToNow(date, { addSuffix: true });
9166
+ }
9167
+ function kindIcon(kind) {
9168
+ if (kind === "input_required") return lucideReact.MessageSquareIcon;
9169
+ if (kind === "action_required") return lucideReact.ExternalLinkIcon;
9170
+ if (kind === "task_complete") return lucideReact.ClipboardCheckIcon;
9171
+ if (kind === "governance_gate") return lucideReact.ShieldAlertIcon;
9172
+ if (kind === "progress_checkin") return lucideReact.TimerIcon;
9173
+ return lucideReact.CircleDotIcon;
9174
+ }
9175
+ function sortItems(items) {
9176
+ return [...items].sort((a, b) => {
9177
+ if (a.createdAt !== b.createdAt) return a.createdAt > b.createdAt ? -1 : 1;
9178
+ return b.id - a.id;
9179
+ });
9180
+ }
9181
+ function QuestionInput({
9182
+ prompt,
9183
+ choices,
9184
+ disabled,
9185
+ value,
9186
+ onChange
9187
+ }) {
9188
+ if (choices && choices.length > 0) {
9189
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9190
+ "select",
9191
+ {
9192
+ "aria-label": prompt,
9193
+ className: "h-9 w-full border border-line bg-background px-2 text-sm text-foreground outline-none focus:border-primary disabled:opacity-50",
9194
+ disabled,
9195
+ value,
9196
+ onChange: (event) => onChange(event.target.value),
9197
+ children: [
9198
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select" }),
9199
+ choices.map((choice) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: choice.label, children: choice.label }, choice.label))
9200
+ ]
9201
+ }
9202
+ );
9203
+ }
9204
+ return /* @__PURE__ */ jsxRuntime.jsx(
9205
+ "input",
9206
+ {
9207
+ "aria-label": prompt,
9208
+ className: "h-9 w-full border border-line bg-background px-2 text-sm text-foreground outline-none focus:border-primary disabled:opacity-50",
9209
+ disabled,
9210
+ value,
9211
+ onChange: (event) => onChange(event.target.value)
9212
+ }
9213
+ );
9214
+ }
9215
+ function InboxDetailActions({
9216
+ item,
9217
+ adapter,
9218
+ onDeleted,
9219
+ onSessionSelect,
9220
+ children
9221
+ }) {
9222
+ const [deleting, setDeleting] = react.useState(false);
9223
+ async function deleteItem() {
9224
+ if (!adapter.deleteInboxItem) return;
9225
+ setDeleting(true);
9226
+ try {
9227
+ await onDeleted(item.id);
9228
+ } finally {
9229
+ setDeleting(false);
9230
+ }
9231
+ }
9232
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
9233
+ /* @__PURE__ */ jsxRuntime.jsxs(
9234
+ Button,
9235
+ {
9236
+ type: "button",
9237
+ size: "sm",
9238
+ onClick: () => onSessionSelect?.(item.sessionId),
9239
+ "aria-label": "Open session",
9240
+ children: [
9241
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLinkIcon, { className: "size-3.5" }),
9242
+ "Open session"
9243
+ ]
9244
+ }
9245
+ ),
9246
+ children,
9247
+ adapter.deleteInboxItem && /* @__PURE__ */ jsxRuntime.jsxs(
9248
+ Button,
9249
+ {
9250
+ type: "button",
9251
+ size: "sm",
9252
+ variant: "ghost",
9253
+ onClick: () => void deleteItem(),
9254
+ disabled: deleting,
9255
+ "aria-label": "Delete inbox item",
9256
+ title: "Delete inbox item",
9257
+ children: [
9258
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2Icon, { className: "size-3.5" }),
9259
+ deleting ? "Deleting" : "Delete"
9260
+ ]
9261
+ }
9262
+ )
9263
+ ] });
9264
+ }
9265
+ function InputRequiredDetail({
9266
+ item,
9267
+ adapter,
9268
+ onUpdated,
9269
+ onDeleted,
9270
+ onSessionSelect
9271
+ }) {
9272
+ const questions = Array.isArray(item.payload.questions) ? item.payload.questions.map((question) => ({
9273
+ prompt: typeof question.prompt === "string" ? question.prompt : "",
9274
+ choices: Array.isArray(question.choices) ? question.choices.map((choice) => ({
9275
+ label: typeof choice.label === "string" ? choice.label : "",
9276
+ description: typeof choice.description === "string" ? choice.description : void 0
9277
+ })).filter((choice) => choice.label) : void 0
9278
+ })).filter((question) => question.prompt) : [];
9279
+ const [answers, setAnswers] = react.useState({});
9280
+ const [submitting, setSubmitting] = react.useState(false);
9281
+ const disabled = item.status !== "pending" || submitting;
9282
+ const canSubmit = questions.every((question) => answers[question.prompt]?.trim());
9283
+ async function submit() {
9284
+ const toolCallId = typeof item.payload.tool_call_id === "string" ? item.payload.tool_call_id : "";
9285
+ if (!toolCallId || !canSubmit) return;
9286
+ setSubmitting(true);
9287
+ try {
9288
+ const responses = questions.map((question) => ({
9289
+ question: question.prompt,
9290
+ answer: answers[question.prompt]?.trim() ?? "",
9291
+ is_other: false
9292
+ }));
9293
+ await adapter.submitClarifyResponse({
9294
+ sessionId: item.sessionId,
9295
+ toolCallId,
9296
+ responses
9297
+ });
9298
+ onUpdated(await adapter.getInboxItem({ itemId: item.id }));
9299
+ } finally {
9300
+ setSubmitting(false);
9301
+ }
9302
+ }
9303
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
9304
+ item.body && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap text-sm text-muted-foreground", children: item.body }),
9305
+ questions.map((question) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1.5", children: [
9306
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-foreground", children: question.prompt }),
9307
+ /* @__PURE__ */ jsxRuntime.jsx(
9308
+ QuestionInput,
9309
+ {
9310
+ prompt: question.prompt,
9311
+ choices: question.choices,
9312
+ disabled,
9313
+ value: answers[question.prompt] ?? "",
9314
+ onChange: (value) => setAnswers((current) => ({
9315
+ ...current,
9316
+ [question.prompt]: value
9317
+ }))
9318
+ }
9319
+ )
9320
+ ] }, question.prompt)),
9321
+ /* @__PURE__ */ jsxRuntime.jsx(
9322
+ InboxDetailActions,
9323
+ {
9324
+ item,
9325
+ adapter,
9326
+ onDeleted,
9327
+ onSessionSelect,
9328
+ children: /* @__PURE__ */ jsxRuntime.jsx(
9329
+ Button,
9330
+ {
9331
+ type: "button",
9332
+ size: "sm",
9333
+ onClick: () => void submit(),
9334
+ disabled: disabled || !canSubmit,
9335
+ "aria-label": "Submit inbox response",
9336
+ children: submitting ? "Submitting" : "Submit"
9337
+ }
9338
+ )
9339
+ }
9340
+ )
9341
+ ] });
9342
+ }
9343
+ function AckDetail({
9344
+ item,
9345
+ adapter,
9346
+ onUpdated,
9347
+ onDeleted,
9348
+ onSessionSelect
9349
+ }) {
9350
+ async function acknowledge() {
9351
+ onUpdated(await adapter.acknowledgeInboxItem({ itemId: item.id }));
9352
+ }
9353
+ const outcome = typeof item.payload.outcome === "string" ? item.payload.outcome : "";
9354
+ const duration = typeof item.payload.duration_seconds === "number" ? item.payload.duration_seconds : null;
9355
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
9356
+ outcome && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "secondary", children: outcome }),
9357
+ item.body && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap text-sm", children: item.body }),
9358
+ typeof item.payload.error === "string" && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "overflow-x-auto bg-destructive/10 p-3 text-xs text-destructive", children: item.payload.error }),
9359
+ duration !== null && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
9360
+ "Duration: ",
9361
+ Math.round(duration / 60),
9362
+ " min (",
9363
+ duration,
9364
+ " s)"
9365
+ ] }),
9366
+ /* @__PURE__ */ jsxRuntime.jsx(
9367
+ InboxDetailActions,
9368
+ {
9369
+ item,
9370
+ adapter,
9371
+ onDeleted,
9372
+ onSessionSelect,
9373
+ children: item.status === "pending" && /* @__PURE__ */ jsxRuntime.jsxs(
9374
+ Button,
9375
+ {
9376
+ type: "button",
9377
+ size: "sm",
9378
+ onClick: () => void acknowledge(),
9379
+ "aria-label": "Acknowledge inbox item",
9380
+ children: [
9381
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckIcon, { className: "size-3.5" }),
9382
+ "Acknowledge"
9383
+ ]
9384
+ }
9385
+ )
9386
+ }
9387
+ )
9388
+ ] });
9389
+ }
9390
+ function GovernanceDetail({
9391
+ item,
9392
+ adapter,
9393
+ onUpdated,
9394
+ onDeleted,
9395
+ onSessionSelect
9396
+ }) {
9397
+ async function decide(decision) {
9398
+ onUpdated(
9399
+ await adapter.respondGovernanceInboxItem({ itemId: item.id, decision })
9400
+ );
9401
+ }
9402
+ const toolName = typeof item.payload.tool_name === "string" ? item.payload.tool_name : "tool";
9403
+ const args = typeof item.payload.arguments_excerpt === "string" ? item.payload.arguments_excerpt : "";
9404
+ const reason = typeof item.payload.deny_reason === "string" ? item.payload.deny_reason : "";
9405
+ const disabled = item.status !== "pending";
9406
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
9407
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
9408
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-base font-semibold text-foreground", children: [
9409
+ "Approve ",
9410
+ toolName,
9411
+ "?"
9412
+ ] }),
9413
+ reason && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: reason })
9414
+ ] }),
9415
+ args && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "overflow-x-auto border border-line bg-muted p-3 text-xs", children: args }),
9416
+ /* @__PURE__ */ jsxRuntime.jsxs(
9417
+ InboxDetailActions,
9418
+ {
9419
+ item,
9420
+ adapter,
9421
+ onDeleted,
9422
+ onSessionSelect,
9423
+ children: [
9424
+ /* @__PURE__ */ jsxRuntime.jsx(
9425
+ Button,
9426
+ {
9427
+ type: "button",
9428
+ size: "sm",
9429
+ disabled,
9430
+ onClick: () => void decide("approve"),
9431
+ children: "Approve"
9432
+ }
9433
+ ),
9434
+ /* @__PURE__ */ jsxRuntime.jsx(
9435
+ Button,
9436
+ {
9437
+ type: "button",
9438
+ size: "sm",
9439
+ variant: "destructive",
9440
+ disabled,
9441
+ onClick: () => void decide("reject"),
9442
+ children: "Reject"
9443
+ }
9444
+ )
9445
+ ]
9446
+ }
9447
+ )
9448
+ ] });
9449
+ }
9450
+ function ActionRequiredDetail({
9451
+ item,
9452
+ adapter,
9453
+ onUpdated,
9454
+ onDeleted,
9455
+ onSessionSelect
9456
+ }) {
9457
+ const [submitting, setSubmitting] = react.useState(false);
9458
+ const actionType = typeof item.payload.action_type === "string" ? item.payload.action_type : "";
9459
+ const context = typeof item.payload.context === "string" ? item.payload.context : "";
9460
+ const disabled = item.status !== "pending" || submitting || !adapter.respondActionRequiredInboxItem;
9461
+ async function complete() {
9462
+ if (!adapter.respondActionRequiredInboxItem) return;
9463
+ setSubmitting(true);
9464
+ try {
9465
+ onUpdated(await adapter.respondActionRequiredInboxItem({ itemId: item.id }));
9466
+ } finally {
9467
+ setSubmitting(false);
9468
+ }
9469
+ }
9470
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
9471
+ item.body && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap text-sm", children: item.body }),
9472
+ context && context !== item.body && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap text-sm text-muted-foreground", children: context }),
9473
+ actionType && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "secondary", children: actionType }),
9474
+ /* @__PURE__ */ jsxRuntime.jsx(
9475
+ InboxDetailActions,
9476
+ {
9477
+ item,
9478
+ adapter,
9479
+ onDeleted,
9480
+ onSessionSelect,
9481
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
9482
+ Button,
9483
+ {
9484
+ type: "button",
9485
+ size: "sm",
9486
+ onClick: () => void complete(),
9487
+ disabled,
9488
+ "aria-label": "Mark action complete",
9489
+ children: [
9490
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckIcon, { className: "size-3.5" }),
9491
+ submitting ? "Marking" : "I completed this"
9492
+ ]
9493
+ }
9494
+ )
9495
+ }
9496
+ )
9497
+ ] });
9498
+ }
9499
+ function ProgressDetail({
9500
+ item,
9501
+ adapter,
9502
+ onUpdated,
9503
+ onDeleted,
9504
+ onSessionSelect
9505
+ }) {
9506
+ async function acknowledge() {
9507
+ onUpdated(await adapter.acknowledgeInboxItem({ itemId: item.id }));
9508
+ }
9509
+ const rows = [];
9510
+ for (const row of [
9511
+ ["Iterations", item.payload.iterations],
9512
+ ["Last tool", item.payload.last_tool],
9513
+ ["Elapsed", item.payload.elapsed_seconds]
9514
+ ]) {
9515
+ if (row[1] !== void 0 && row[1] !== null && row[1] !== "") {
9516
+ rows.push(row);
9517
+ }
9518
+ }
9519
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
9520
+ item.body && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap text-sm", children: item.body }),
9521
+ rows.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "space-y-1 text-xs text-muted-foreground", children: rows.map(([label, value]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
9522
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "w-20 shrink-0", children: String(label) }),
9523
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "min-w-0", children: String(value) })
9524
+ ] }, String(label))) }),
9525
+ /* @__PURE__ */ jsxRuntime.jsx(
9526
+ InboxDetailActions,
9527
+ {
9528
+ item,
9529
+ adapter,
9530
+ onDeleted,
9531
+ onSessionSelect,
9532
+ children: item.status === "pending" && /* @__PURE__ */ jsxRuntime.jsxs(
9533
+ Button,
9534
+ {
9535
+ type: "button",
9536
+ size: "sm",
9537
+ onClick: () => void acknowledge(),
9538
+ "aria-label": "Acknowledge inbox item",
9539
+ children: [
9540
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckIcon, { className: "size-3.5" }),
9541
+ "Acknowledge"
9542
+ ]
9543
+ }
9544
+ )
9545
+ }
9546
+ )
9547
+ ] });
9548
+ }
9549
+ function InboxDetail({
9550
+ item,
9551
+ adapter,
9552
+ onUpdated,
9553
+ onDeleted,
9554
+ onSessionSelect
9555
+ }) {
9556
+ const Icon = kindIcon(item.kind);
9557
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "min-w-0 flex-1 overflow-y-auto p-6", children: [
9558
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-5 flex min-w-0 items-start gap-3", children: [
9559
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-0.5 flex size-8 shrink-0 items-center justify-center border border-line text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4" }) }),
9560
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
9561
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
9562
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "secondary", children: kindLabel(item.kind) }),
9563
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: item.status === "pending" ? "default" : "secondary", children: statusLabel(item.status) })
9564
+ ] }),
9565
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "mt-2 text-lg font-semibold leading-snug text-foreground", children: item.title })
9566
+ ] })
9567
+ ] }),
9568
+ item.kind === "input_required" ? /* @__PURE__ */ jsxRuntime.jsx(
9569
+ InputRequiredDetail,
9570
+ {
9571
+ item,
9572
+ adapter,
9573
+ onUpdated,
9574
+ onDeleted,
9575
+ onSessionSelect
9576
+ }
9577
+ ) : item.kind === "action_required" ? /* @__PURE__ */ jsxRuntime.jsx(
9578
+ ActionRequiredDetail,
9579
+ {
9580
+ item,
9581
+ adapter,
9582
+ onUpdated,
9583
+ onDeleted,
9584
+ onSessionSelect
9585
+ }
9586
+ ) : item.kind === "governance_gate" ? /* @__PURE__ */ jsxRuntime.jsx(
9587
+ GovernanceDetail,
9588
+ {
9589
+ item,
9590
+ adapter,
9591
+ onUpdated,
9592
+ onDeleted,
9593
+ onSessionSelect
9594
+ }
9595
+ ) : item.kind === "progress_checkin" ? /* @__PURE__ */ jsxRuntime.jsx(
9596
+ ProgressDetail,
9597
+ {
9598
+ item,
9599
+ adapter,
9600
+ onUpdated,
9601
+ onDeleted,
9602
+ onSessionSelect
9603
+ }
9604
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
9605
+ AckDetail,
9606
+ {
9607
+ item,
9608
+ adapter,
9609
+ onUpdated,
9610
+ onDeleted,
9611
+ onSessionSelect
9612
+ }
9613
+ )
9614
+ ] });
9615
+ }
9616
+ function InboxPanel({
9617
+ adapter,
9618
+ title = "Inbox",
9619
+ selectedId,
9620
+ onSelectedIdChange,
9621
+ onSessionSelect,
9622
+ hideHeader = false,
9623
+ limit = DEFAULT_LIMIT
9624
+ }) {
9625
+ const inboxAdapter = react.useMemo(() => requireInboxAdapter(adapter), [adapter]);
9626
+ const [items, setItems] = react.useState([]);
9627
+ const [nextCursor, setNextCursor] = react.useState(null);
9628
+ const [internalSelectedId, setInternalSelectedId] = react.useState(null);
9629
+ const [loading, setLoading] = react.useState(false);
9630
+ const [error, setError] = react.useState(null);
9631
+ const requestId = react.useRef(0);
9632
+ const selectedItemId = selectedId ?? internalSelectedId;
9633
+ const selectedItem = react.useMemo(
9634
+ () => items.find((item) => item.id === selectedItemId) ?? null,
9635
+ [items, selectedItemId]
9636
+ );
9637
+ const applyItem = react.useCallback((nextItem) => {
9638
+ setItems(
9639
+ (current) => sortItems(
9640
+ current.map((item) => item.id === nextItem.id ? nextItem : item)
9641
+ )
9642
+ );
9643
+ }, []);
9644
+ const selectItem = react.useCallback(
9645
+ async (itemId) => {
9646
+ if (selectedId === void 0) setInternalSelectedId(itemId);
9647
+ onSelectedIdChange?.(itemId);
9648
+ const item = await inboxAdapter.getInboxItem({ itemId });
9649
+ applyItem(item);
9650
+ if (!item.readAt) {
9651
+ applyItem(await inboxAdapter.markInboxItemRead({ itemId }));
9652
+ }
9653
+ },
9654
+ [applyItem, inboxAdapter, onSelectedIdChange, selectedId]
9655
+ );
9656
+ const load = react.useCallback(
9657
+ async (cursor) => {
9658
+ const id = ++requestId.current;
9659
+ setLoading(true);
9660
+ setError(null);
9661
+ try {
9662
+ const response = await inboxAdapter.listInbox({
9663
+ cursor: cursor ?? void 0,
9664
+ limit
9665
+ });
9666
+ if (id !== requestId.current) return;
9667
+ setItems(
9668
+ (current) => sortItems(cursor ? [...current, ...response.items] : response.items)
9669
+ );
9670
+ setNextCursor(response.nextCursor);
9671
+ } catch (err) {
9672
+ if (id === requestId.current) {
9673
+ setError(err instanceof Error ? err.message : "Failed to load inbox");
9674
+ }
9675
+ } finally {
9676
+ if (id === requestId.current) setLoading(false);
9677
+ }
9678
+ },
9679
+ [inboxAdapter, limit]
9680
+ );
9681
+ react.useEffect(() => {
9682
+ void load(null);
9683
+ }, [load]);
9684
+ react.useEffect(() => {
9685
+ const stream = inboxAdapter.openInboxStream();
9686
+ stream.addEventListener("item", (event) => {
9687
+ const payload = JSON.parse(event.data);
9688
+ if (typeof payload.item_id !== "number") return;
9689
+ void inboxAdapter.getInboxItem({ itemId: payload.item_id }).then(applyItem);
9690
+ });
9691
+ return () => stream.close();
9692
+ }, [applyItem, inboxAdapter]);
9693
+ function updateSelectedItem(item) {
9694
+ applyItem(item);
9695
+ }
9696
+ const deleteItem = react.useCallback(
9697
+ async (itemId) => {
9698
+ if (!inboxAdapter.deleteInboxItem) return;
9699
+ await inboxAdapter.deleteInboxItem({ itemId });
9700
+ setItems((current) => current.filter((item) => item.id !== itemId));
9701
+ if (selectedItemId === itemId) {
9702
+ if (selectedId === void 0) setInternalSelectedId(null);
9703
+ onSelectedIdChange?.(null);
9704
+ }
9705
+ },
9706
+ [inboxAdapter, onSelectedIdChange, selectedId, selectedItemId]
9707
+ );
9708
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-0 min-w-0 bg-background text-foreground", children: [
9709
+ /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "flex w-80 min-w-72 max-w-sm shrink-0 flex-col border-r border-line", children: [
9710
+ !hideHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-14 items-center gap-2 border-b border-line px-4", children: [
9711
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.InboxIcon, { className: "size-4 text-muted-foreground" }),
9712
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "font-semibold", children: title })
9713
+ ] }),
9714
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto", children: [
9715
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 text-sm text-destructive", children: error }),
9716
+ !error && items.length === 0 && !loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-sm text-muted-foreground", children: "No inbox items" }),
9717
+ items.map((item) => {
9718
+ const Icon = kindIcon(item.kind);
9719
+ const selected = item.id === selectedItemId;
9720
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9721
+ "button",
9722
+ {
9723
+ type: "button",
9724
+ "aria-label": `Open inbox item ${item.title}`,
9725
+ className: cn(
9726
+ "flex w-full min-w-0 gap-3 border-l-2 px-3 py-3 text-left transition-colors hover:bg-input",
9727
+ selected ? "border-l-primary bg-line text-foreground" : "border-l-transparent text-subtle",
9728
+ !item.readAt && "font-medium text-foreground"
9729
+ ),
9730
+ onClick: () => void selectItem(item.id),
9731
+ children: [
9732
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }),
9733
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
9734
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center justify-between gap-2", children: [
9735
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs uppercase tracking-wide text-muted-foreground", children: kindLabel(item.kind) }),
9736
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatRelative(item.createdAt) })
9737
+ ] }),
9738
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block truncate text-sm", children: item.title }),
9739
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-xs text-muted-foreground", children: statusLabel(item.status) })
9740
+ ] })
9741
+ ]
9742
+ },
9743
+ item.id
9744
+ );
9745
+ }),
9746
+ nextCursor && /* @__PURE__ */ jsxRuntime.jsx(
9747
+ "button",
9748
+ {
9749
+ type: "button",
9750
+ className: "w-full px-3 py-3 text-sm text-muted-foreground hover:bg-input",
9751
+ onClick: () => void load(nextCursor),
9752
+ disabled: loading,
9753
+ children: loading ? "Loading" : "Load more"
9754
+ }
9755
+ )
9756
+ ] })
9757
+ ] }),
9758
+ selectedItem ? /* @__PURE__ */ jsxRuntime.jsx(
9759
+ InboxDetail,
9760
+ {
9761
+ item: selectedItem,
9762
+ adapter: inboxAdapter,
9763
+ onUpdated: updateSelectedItem,
9764
+ onDeleted: deleteItem,
9765
+ onSessionSelect
9766
+ }
9767
+ ) : /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex min-w-0 flex-1 items-center justify-center px-4 text-sm text-muted-foreground", children: "Select an item" })
9768
+ ] });
9769
+ }
9770
+ function useInboxUnreadCount(adapter) {
9771
+ const [unreadCount, setUnreadCount] = react.useState(0);
9772
+ const [hasLoaded, setHasLoaded] = react.useState(false);
9773
+ const [error, setError] = react.useState(null);
9774
+ react.useEffect(() => {
9775
+ let cancelled = false;
9776
+ let stream = null;
9777
+ if (!adapter.listInbox || !adapter.openInboxStream) {
9778
+ setError("Inbox is not supported by this adapter.");
9779
+ setHasLoaded(true);
9780
+ return () => {
9781
+ cancelled = true;
9782
+ };
9783
+ }
9784
+ adapter.listInbox({ status: "pending", limit: 200 }).then((response) => {
9785
+ if (cancelled) return;
9786
+ setUnreadCount(response.items.filter((item) => !item.readAt).length);
9787
+ setHasLoaded(true);
9788
+ }).catch((err) => {
9789
+ if (cancelled) return;
9790
+ setError(err instanceof Error ? err.message : "Failed to load inbox");
9791
+ setHasLoaded(true);
9792
+ });
9793
+ try {
9794
+ stream = adapter.openInboxStream();
9795
+ stream.addEventListener("snapshot", (event) => {
9796
+ if (cancelled) return;
9797
+ const payload = JSON.parse(event.data);
9798
+ const ids = Array.isArray(payload.unread_ids) ? payload.unread_ids : [];
9799
+ setUnreadCount(ids.length);
9800
+ setHasLoaded(true);
9801
+ });
9802
+ stream.addEventListener("item", () => {
9803
+ if (cancelled) return;
9804
+ setUnreadCount((count) => count + 1);
9805
+ setHasLoaded(true);
9806
+ });
9807
+ stream.onerror = () => {
9808
+ if (!cancelled) setError("Inbox stream disconnected.");
9809
+ };
9810
+ } catch (err) {
9811
+ setError(err instanceof Error ? err.message : "Failed to open inbox stream");
9812
+ }
9813
+ return () => {
9814
+ cancelled = true;
9815
+ stream?.close();
9816
+ };
9817
+ }, [adapter]);
9818
+ return { unreadCount, hasLoaded, error };
9819
+ }
9820
+ init_utils();
9821
+ var DEFAULT_LIMIT2 = 50;
8936
9822
  var DEFAULT_POLL_INTERVAL_MS = 3e4;
8937
9823
  function fingerprint(items) {
8938
9824
  return items.map(
@@ -8947,7 +9833,7 @@ function formatKind(value) {
8947
9833
  if (!value) return "Scheduled";
8948
9834
  return value.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
8949
9835
  }
8950
- function formatRelative(value, label) {
9836
+ function formatRelative2(value, label) {
8951
9837
  if (!value) return null;
8952
9838
  const date = new Date(value);
8953
9839
  if (Number.isNaN(date.getTime())) return null;
@@ -8997,10 +9883,10 @@ function ScheduledWorkRow({
8997
9883
  const lastSessionId = item.lastSessionId ?? null;
8998
9884
  const meta = [
8999
9885
  scheduleDisplay(item),
9000
- formatRelative(item.nextRunAt, "Next"),
9001
- formatRelative(item.lastRunAt, "Last"),
9886
+ formatRelative2(item.nextRunAt, "Next"),
9887
+ formatRelative2(item.lastRunAt, "Last"),
9002
9888
  formatRuns(item.runCount),
9003
- item.expiresAt ? formatRelative(item.expiresAt, "Expires") : null
9889
+ item.expiresAt ? formatRelative2(item.expiresAt, "Expires") : null
9004
9890
  ].filter(Boolean);
9005
9891
  const openLastRun = () => {
9006
9892
  if (lastSessionId) onOpenLastRun(lastSessionId);
@@ -9080,7 +9966,7 @@ function ScheduledWorkPanel({
9080
9966
  adapter,
9081
9967
  agentId,
9082
9968
  title = "Scheduled Work",
9083
- limit = DEFAULT_LIMIT,
9969
+ limit = DEFAULT_LIMIT2,
9084
9970
  status = "active",
9085
9971
  hideHeader = false,
9086
9972
  pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
@@ -9603,10 +10489,12 @@ function SessionTreePanel({
9603
10489
 
9604
10490
  exports.AgentChat = AgentChat;
9605
10491
  exports.AgentChatAdapterProvider = AgentChatAdapterProvider;
10492
+ exports.InboxPanel = InboxPanel;
9606
10493
  exports.MessageResponse = MessageResponse;
9607
10494
  exports.ScheduledWorkPanel = ScheduledWorkPanel;
9608
10495
  exports.SessionTreePanel = SessionTreePanel;
9609
10496
  exports.useAgentChatAdapterContext = useAgentChatAdapterContext;
9610
10497
  exports.useAgentChatRuntime = useAgentChatRuntime;
10498
+ exports.useInboxUnreadCount = useInboxUnreadCount;
9611
10499
  //# sourceMappingURL=index.cjs.map
9612
10500
  //# sourceMappingURL=index.cjs.map