@invergent/agent-chat-react 1.5.10 → 1.6.1

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
@@ -6,9 +6,9 @@ var react = require('react');
6
6
  var auto = require('chart.js/auto');
7
7
  var nextThemes = require('next-themes');
8
8
  var jsxRuntime = require('react/jsx-runtime');
9
+ var lucideReact = require('lucide-react');
9
10
  var classVarianceAuthority = require('class-variance-authority');
10
11
  var radixUi = require('radix-ui');
11
- var lucideReact = require('lucide-react');
12
12
  var useStickToBottom = require('use-stick-to-bottom');
13
13
  var cjk = require('@streamdown/cjk');
14
14
  var code = require('@streamdown/code');
@@ -263,77 +263,218 @@ function Button({
263
263
  }
264
264
  );
265
265
  }
266
+ function BrowserControlBar({
267
+ sessionId,
268
+ hasControl,
269
+ adapter
270
+ }) {
271
+ const [pending, setPending] = react.useState(false);
272
+ const [error, setError] = react.useState(null);
273
+ async function toggleControl() {
274
+ if (!sessionId || pending) return;
275
+ setPending(true);
276
+ setError(null);
277
+ try {
278
+ if (hasControl) {
279
+ await adapter.releaseBrowserControl(sessionId);
280
+ } else {
281
+ await adapter.acquireBrowserControl(sessionId);
282
+ }
283
+ } catch (nextError) {
284
+ setError(nextError.message);
285
+ } finally {
286
+ setPending(false);
287
+ }
288
+ }
289
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-11 items-center gap-2 border-t border-line bg-card px-3 py-2", children: [
290
+ /* @__PURE__ */ jsxRuntime.jsxs(
291
+ Button,
292
+ {
293
+ type: "button",
294
+ size: "xs",
295
+ variant: hasControl ? "secondary" : "default",
296
+ disabled: !sessionId || pending,
297
+ onClick: () => void toggleControl(),
298
+ children: [
299
+ hasControl ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcwIcon, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MousePointer2Icon, { className: "size-3", "aria-hidden": "true" }),
300
+ hasControl ? "Return control" : "Take control"
301
+ ]
302
+ }
303
+ ),
304
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
305
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1.5 rounded-full bg-red-500", "aria-hidden": "true" }),
306
+ "Live"
307
+ ] }),
308
+ error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error })
309
+ ] });
310
+ }
311
+ function BrowserLiveView({
312
+ src,
313
+ testId = "browser-iframe"
314
+ }) {
315
+ return /* @__PURE__ */ jsxRuntime.jsx(
316
+ "iframe",
317
+ {
318
+ "data-testid": testId,
319
+ title: "Browser live view",
320
+ src,
321
+ className: "h-full w-full border-0 bg-black",
322
+ allow: "clipboard-read; clipboard-write"
323
+ }
324
+ );
325
+ }
266
326
 
267
- // src/components/ai-elements/conversation.tsx
327
+ // src/components/browser/browser-status-dot.tsx
268
328
  init_utils();
269
- var Conversation = ({ className, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(
270
- useStickToBottom.StickToBottom,
271
- {
272
- className: cn("relative flex-1", className),
273
- initial: "smooth",
274
- resize: "smooth",
275
- role: "log",
276
- ...props
277
- }
278
- );
279
- var ConversationContent = ({
329
+ function BrowserStatusDot({
330
+ status
331
+ }) {
332
+ return /* @__PURE__ */ jsxRuntime.jsx(
333
+ "span",
334
+ {
335
+ "aria-hidden": "true",
336
+ className: cn(
337
+ "inline-block size-2 shrink-0 rounded-full",
338
+ status === "live" && "bg-emerald-500",
339
+ status === "user-control" && "bg-amber-500",
340
+ status === "provisioning" && "animate-pulse bg-sky-500",
341
+ status === "closed" && "bg-muted-foreground"
342
+ )
343
+ }
344
+ );
345
+ }
346
+
347
+ // src/components/ui/dialog.tsx
348
+ init_utils();
349
+ function Dialog({
350
+ ...props
351
+ }) {
352
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Root, { "data-slot": "dialog", ...props });
353
+ }
354
+ function DialogPortal({
355
+ ...props
356
+ }) {
357
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Portal, { "data-slot": "dialog-portal", ...props });
358
+ }
359
+ function DialogOverlay({
280
360
  className,
281
361
  ...props
282
- }) => /* @__PURE__ */ jsxRuntime.jsx(
283
- useStickToBottom.StickToBottom.Content,
284
- {
285
- className: cn("flex flex-col gap-6 py-6 px-6", className),
286
- ...props
287
- }
288
- );
289
- var ConversationEmptyState = ({
362
+ }) {
363
+ return /* @__PURE__ */ jsxRuntime.jsx(
364
+ radixUi.Dialog.Overlay,
365
+ {
366
+ "data-slot": "dialog-overlay",
367
+ className: cn(
368
+ "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",
369
+ className
370
+ ),
371
+ ...props
372
+ }
373
+ );
374
+ }
375
+ function DialogContent({
290
376
  className,
291
- title = "No messages yet",
292
- description = "Start a conversation to see messages here",
293
- icon,
294
377
  children,
378
+ showCloseButton = true,
295
379
  ...props
296
- }) => /* @__PURE__ */ jsxRuntime.jsx(
297
- "div",
298
- {
299
- className: cn(
300
- "flex size-full flex-col items-center justify-center gap-4 p-8 text-center",
301
- className
302
- ),
303
- ...props,
304
- children: children ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
305
- icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center size-14 rounded-2xl bg-muted/60 text-muted-foreground/60", children: icon }),
306
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
307
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-base text-foreground/80", children: title }),
308
- description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground/60 text-sm max-w-sm", children: description })
309
- ] })
310
- ] })
311
- }
312
- );
313
- var ConversationScrollButton = ({
380
+ }) {
381
+ return /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
382
+ /* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
383
+ /* @__PURE__ */ jsxRuntime.jsxs(
384
+ radixUi.Dialog.Content,
385
+ {
386
+ "data-slot": "dialog-content",
387
+ className: cn(
388
+ "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",
389
+ className
390
+ ),
391
+ ...props,
392
+ children: [
393
+ children,
394
+ showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
395
+ Button,
396
+ {
397
+ variant: "ghost",
398
+ className: "absolute top-5 right-5 bg-secondary",
399
+ size: "icon-sm",
400
+ children: [
401
+ /* @__PURE__ */ jsxRuntime.jsx(
402
+ lucideReact.XIcon,
403
+ {}
404
+ ),
405
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Close" })
406
+ ]
407
+ }
408
+ ) })
409
+ ]
410
+ }
411
+ )
412
+ ] });
413
+ }
414
+ function DialogHeader({ className, ...props }) {
415
+ return /* @__PURE__ */ jsxRuntime.jsx(
416
+ "div",
417
+ {
418
+ "data-slot": "dialog-header",
419
+ className: cn("flex flex-col gap-2", className),
420
+ ...props
421
+ }
422
+ );
423
+ }
424
+ function DialogFooter({
314
425
  className,
426
+ showCloseButton = false,
427
+ children,
315
428
  ...props
316
- }) => {
317
- const { isAtBottom, scrollToBottom } = useStickToBottom.useStickToBottomContext();
318
- const handleScrollToBottom = react.useCallback(() => {
319
- scrollToBottom();
320
- }, [scrollToBottom]);
321
- return !isAtBottom && /* @__PURE__ */ jsxRuntime.jsx(
322
- Button,
429
+ }) {
430
+ return /* @__PURE__ */ jsxRuntime.jsxs(
431
+ "div",
323
432
  {
433
+ "data-slot": "dialog-footer",
324
434
  className: cn(
325
- "absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full shadow-lg border-border/50 dark:bg-background dark:hover:bg-muted",
435
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
326
436
  className
327
437
  ),
328
- onClick: handleScrollToBottom,
329
- size: "icon",
330
- type: "button",
331
- variant: "outline",
332
438
  ...props,
333
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowDownIcon, { className: "size-4" })
439
+ children: [
440
+ children,
441
+ showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", children: "Close" }) })
442
+ ]
334
443
  }
335
444
  );
336
- };
445
+ }
446
+ function DialogTitle({
447
+ className,
448
+ ...props
449
+ }) {
450
+ return /* @__PURE__ */ jsxRuntime.jsx(
451
+ radixUi.Dialog.Title,
452
+ {
453
+ "data-slot": "dialog-title",
454
+ className: cn(
455
+ "font-heading text-lg leading-none font-semibold tracking-wider uppercase",
456
+ className
457
+ ),
458
+ ...props
459
+ }
460
+ );
461
+ }
462
+ function DialogDescription({
463
+ className,
464
+ ...props
465
+ }) {
466
+ return /* @__PURE__ */ jsxRuntime.jsx(
467
+ radixUi.Dialog.Description,
468
+ {
469
+ "data-slot": "dialog-description",
470
+ className: cn(
471
+ "mt-0.5 text-sm leading-relaxed text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
472
+ className
473
+ ),
474
+ ...props
475
+ }
476
+ );
477
+ }
337
478
 
338
479
  // src/components/ui/tooltip.tsx
339
480
  init_utils();
@@ -365,24 +506,190 @@ function TooltipContent({
365
506
  sideOffset = 0,
366
507
  children,
367
508
  ...props
368
- }) {
369
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
370
- radixUi.Tooltip.Content,
509
+ }) {
510
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
511
+ radixUi.Tooltip.Content,
512
+ {
513
+ "data-slot": "tooltip-content",
514
+ sideOffset,
515
+ className: cn(
516
+ "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",
517
+ className
518
+ ),
519
+ ...props,
520
+ children: [
521
+ children,
522
+ /* @__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" })
523
+ ]
524
+ }
525
+ ) });
526
+ }
527
+ function BrowserPane({ sessionId, state, adapter }) {
528
+ const [fullscreenOpen, setFullscreenOpen] = react.useState(false);
529
+ const [inlinePreviewOpen, setInlinePreviewOpen] = react.useState(false);
530
+ const hasLiveViewAdapter = typeof adapter.browserLiveViewUrl === "function";
531
+ const hasControlAdapter = typeof adapter.acquireBrowserControl === "function" && typeof adapter.releaseBrowserControl === "function";
532
+ const liveViewUrl = react.useMemo(() => {
533
+ if (!hasLiveViewAdapter) return "";
534
+ return adapter.browserLiveViewUrl(sessionId);
535
+ }, [adapter, hasLiveViewAdapter, sessionId]);
536
+ const hasLiveView = state.status !== "provisioning" && state.status !== "closed";
537
+ const canOpenFullscreen = hasLiveView && Boolean(liveViewUrl);
538
+ react.useEffect(() => {
539
+ setFullscreenOpen(false);
540
+ setInlinePreviewOpen(false);
541
+ }, [sessionId]);
542
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
543
+ /* @__PURE__ */ jsxRuntime.jsxs(
544
+ "div",
545
+ {
546
+ "data-testid": "browser-pane",
547
+ className: "flex h-full min-h-0 flex-col bg-background",
548
+ children: [
549
+ /* @__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: [
550
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
551
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Browser" }),
552
+ /* @__PURE__ */ jsxRuntime.jsx(BrowserStatusDot, { status: state.status }),
553
+ state.controlOwner && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate text-amber-500", children: [
554
+ state.controlOwner,
555
+ " has control"
556
+ ] }),
557
+ canOpenFullscreen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
558
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
559
+ "button",
560
+ {
561
+ type: "button",
562
+ "aria-label": "Maximize browser",
563
+ 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",
564
+ onClick: () => setFullscreenOpen(true),
565
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2Icon, { className: "size-3.5", "aria-hidden": "true" })
566
+ }
567
+ ) }),
568
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: "Full screen" })
569
+ ] }) }) })
570
+ ] }),
571
+ /* @__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." }) : liveViewUrl && inlinePreviewOpen ? /* @__PURE__ */ jsxRuntime.jsx(BrowserLiveView, { src: liveViewUrl }) : liveViewUrl ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsxs(
572
+ "button",
573
+ {
574
+ type: "button",
575
+ "aria-label": "Open browser live preview",
576
+ className: "inline-flex items-center gap-2 border border-line bg-card px-3 py-2 text-xs font-medium text-foreground transition-colors hover:bg-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
577
+ onClick: () => setInlinePreviewOpen(true),
578
+ children: [
579
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlayIcon, { className: "size-3.5", "aria-hidden": "true" }),
580
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Open live preview" })
581
+ ]
582
+ }
583
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser live view is unavailable." }) }),
584
+ hasLiveView && hasControlAdapter && /* @__PURE__ */ jsxRuntime.jsx(
585
+ BrowserControlBar,
586
+ {
587
+ sessionId,
588
+ hasControl: state.status === "user-control",
589
+ adapter
590
+ }
591
+ )
592
+ ]
593
+ }
594
+ ),
595
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open: fullscreenOpen, onOpenChange: setFullscreenOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(
596
+ DialogContent,
597
+ {
598
+ "aria-describedby": void 0,
599
+ 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",
600
+ children: [
601
+ /* @__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: [
602
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
603
+ /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { className: "text-xs normal-case tracking-normal", children: "Browser" }),
604
+ /* @__PURE__ */ jsxRuntime.jsx(BrowserStatusDot, { status: state.status }),
605
+ state.controlOwner && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate text-xs text-amber-500", children: [
606
+ state.controlOwner,
607
+ " has control"
608
+ ] })
609
+ ] }),
610
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 bg-black", children: liveViewUrl ? /* @__PURE__ */ jsxRuntime.jsx(
611
+ BrowserLiveView,
612
+ {
613
+ src: liveViewUrl,
614
+ testId: "browser-fullscreen-iframe"
615
+ }
616
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser live view is unavailable." }) })
617
+ ]
618
+ }
619
+ ) })
620
+ ] });
621
+ }
622
+
623
+ // src/components/ai-elements/conversation.tsx
624
+ init_utils();
625
+ var Conversation = ({ className, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(
626
+ useStickToBottom.StickToBottom,
627
+ {
628
+ className: cn("relative flex-1", className),
629
+ initial: "smooth",
630
+ resize: "smooth",
631
+ role: "log",
632
+ ...props
633
+ }
634
+ );
635
+ var ConversationContent = ({
636
+ className,
637
+ ...props
638
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
639
+ useStickToBottom.StickToBottom.Content,
640
+ {
641
+ className: cn("flex flex-col gap-6 py-6 px-6", className),
642
+ ...props
643
+ }
644
+ );
645
+ var ConversationEmptyState = ({
646
+ className,
647
+ title = "No messages yet",
648
+ description = "Start a conversation to see messages here",
649
+ icon,
650
+ children,
651
+ ...props
652
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
653
+ "div",
654
+ {
655
+ className: cn(
656
+ "flex size-full flex-col items-center justify-center gap-4 p-8 text-center",
657
+ className
658
+ ),
659
+ ...props,
660
+ children: children ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
661
+ icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center size-14 rounded-2xl bg-muted/60 text-muted-foreground/60", children: icon }),
662
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
663
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-base text-foreground/80", children: title }),
664
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground/60 text-sm max-w-sm", children: description })
665
+ ] })
666
+ ] })
667
+ }
668
+ );
669
+ var ConversationScrollButton = ({
670
+ className,
671
+ ...props
672
+ }) => {
673
+ const { isAtBottom, scrollToBottom } = useStickToBottom.useStickToBottomContext();
674
+ const handleScrollToBottom = react.useCallback(() => {
675
+ scrollToBottom();
676
+ }, [scrollToBottom]);
677
+ return !isAtBottom && /* @__PURE__ */ jsxRuntime.jsx(
678
+ Button,
371
679
  {
372
- "data-slot": "tooltip-content",
373
- sideOffset,
374
680
  className: cn(
375
- "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",
681
+ "absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full shadow-lg border-border/50 dark:bg-background dark:hover:bg-muted",
376
682
  className
377
683
  ),
684
+ onClick: handleScrollToBottom,
685
+ size: "icon",
686
+ type: "button",
687
+ variant: "outline",
378
688
  ...props,
379
- children: [
380
- children,
381
- /* @__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" })
382
- ]
689
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowDownIcon, { className: "size-4" })
383
690
  }
384
- ) });
385
- }
691
+ );
692
+ };
386
693
 
387
694
  // src/components/ai-elements/message.tsx
388
695
  init_utils();
@@ -767,6 +1074,80 @@ function TimelineSeparator({
767
1074
  }
768
1075
  );
769
1076
  }
1077
+ function BrowserActivityGroup({ calls }) {
1078
+ const [open, setOpen] = react.useState(false);
1079
+ const latest = calls[calls.length - 1];
1080
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-none border border-line bg-card text-xs", children: [
1081
+ /* @__PURE__ */ jsxRuntime.jsxs(
1082
+ "button",
1083
+ {
1084
+ type: "button",
1085
+ onClick: () => setOpen((current) => !current),
1086
+ className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-muted/40",
1087
+ children: [
1088
+ 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" }),
1089
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZapIcon, { className: "size-3 shrink-0 text-amber-500", "aria-hidden": "true" }),
1090
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: "browser" }),
1091
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 truncate text-muted-foreground", children: [
1092
+ "(",
1093
+ calls.length,
1094
+ " ",
1095
+ calls.length === 1 ? "action" : "actions",
1096
+ " - latest:",
1097
+ " ",
1098
+ latest ? summarise(latest) : "none",
1099
+ ")"
1100
+ ] })
1101
+ ]
1102
+ }
1103
+ ),
1104
+ open && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "border-t border-line py-1", children: calls.map((call) => {
1105
+ const error = stringValue(parseJson(call.result).error);
1106
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1107
+ "li",
1108
+ {
1109
+ className: "flex items-center gap-2 px-6 py-1 font-mono text-[11px]",
1110
+ children: [
1111
+ error && /* @__PURE__ */ jsxRuntime.jsx(
1112
+ lucideReact.AlertCircleIcon,
1113
+ {
1114
+ "data-testid": `activity-error-${call.id}`,
1115
+ className: "size-3 shrink-0 text-destructive",
1116
+ "aria-hidden": "true"
1117
+ }
1118
+ ),
1119
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: summarise(call) }),
1120
+ error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto max-w-40 truncate text-destructive", children: error })
1121
+ ]
1122
+ },
1123
+ call.id
1124
+ );
1125
+ }) })
1126
+ ] });
1127
+ }
1128
+ function summarise(call) {
1129
+ const verb = call.toolName.replace(/^browser_/, "");
1130
+ const args = parseJson(call.args);
1131
+ const ref = stringValue(args.ref);
1132
+ const url = stringValue(args.url);
1133
+ const text = stringValue(args.text);
1134
+ if (url) return `${verb} ${url}`;
1135
+ if (ref) return `${verb} ${ref}`;
1136
+ if (text) return `${verb} "${text.slice(0, 24)}"`;
1137
+ return verb;
1138
+ }
1139
+ function parseJson(raw) {
1140
+ if (!raw) return {};
1141
+ try {
1142
+ const parsed = JSON.parse(raw);
1143
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
1144
+ } catch {
1145
+ return {};
1146
+ }
1147
+ }
1148
+ function stringValue(value) {
1149
+ return typeof value === "string" ? value : "";
1150
+ }
770
1151
  init_utils();
771
1152
 
772
1153
  // src/components/chat/tools/shared.ts
@@ -1812,140 +2193,6 @@ function WebToolBlock({ tc }) {
1812
2193
  displayText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: displayText })
1813
2194
  ] });
1814
2195
  }
1815
-
1816
- // src/components/ui/dialog.tsx
1817
- init_utils();
1818
- function Dialog({
1819
- ...props
1820
- }) {
1821
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Root, { "data-slot": "dialog", ...props });
1822
- }
1823
- function DialogPortal({
1824
- ...props
1825
- }) {
1826
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Portal, { "data-slot": "dialog-portal", ...props });
1827
- }
1828
- function DialogOverlay({
1829
- className,
1830
- ...props
1831
- }) {
1832
- return /* @__PURE__ */ jsxRuntime.jsx(
1833
- radixUi.Dialog.Overlay,
1834
- {
1835
- "data-slot": "dialog-overlay",
1836
- className: cn(
1837
- "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",
1838
- className
1839
- ),
1840
- ...props
1841
- }
1842
- );
1843
- }
1844
- function DialogContent({
1845
- className,
1846
- children,
1847
- showCloseButton = true,
1848
- ...props
1849
- }) {
1850
- return /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
1851
- /* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
1852
- /* @__PURE__ */ jsxRuntime.jsxs(
1853
- radixUi.Dialog.Content,
1854
- {
1855
- "data-slot": "dialog-content",
1856
- className: cn(
1857
- "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",
1858
- className
1859
- ),
1860
- ...props,
1861
- children: [
1862
- children,
1863
- showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
1864
- Button,
1865
- {
1866
- variant: "ghost",
1867
- className: "absolute top-5 right-5 bg-secondary",
1868
- size: "icon-sm",
1869
- children: [
1870
- /* @__PURE__ */ jsxRuntime.jsx(
1871
- lucideReact.XIcon,
1872
- {}
1873
- ),
1874
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Close" })
1875
- ]
1876
- }
1877
- ) })
1878
- ]
1879
- }
1880
- )
1881
- ] });
1882
- }
1883
- function DialogHeader({ className, ...props }) {
1884
- return /* @__PURE__ */ jsxRuntime.jsx(
1885
- "div",
1886
- {
1887
- "data-slot": "dialog-header",
1888
- className: cn("flex flex-col gap-2", className),
1889
- ...props
1890
- }
1891
- );
1892
- }
1893
- function DialogFooter({
1894
- className,
1895
- showCloseButton = false,
1896
- children,
1897
- ...props
1898
- }) {
1899
- return /* @__PURE__ */ jsxRuntime.jsxs(
1900
- "div",
1901
- {
1902
- "data-slot": "dialog-footer",
1903
- className: cn(
1904
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1905
- className
1906
- ),
1907
- ...props,
1908
- children: [
1909
- children,
1910
- showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", children: "Close" }) })
1911
- ]
1912
- }
1913
- );
1914
- }
1915
- function DialogTitle({
1916
- className,
1917
- ...props
1918
- }) {
1919
- return /* @__PURE__ */ jsxRuntime.jsx(
1920
- radixUi.Dialog.Title,
1921
- {
1922
- "data-slot": "dialog-title",
1923
- className: cn(
1924
- "font-heading text-lg leading-none font-semibold tracking-wider uppercase",
1925
- className
1926
- ),
1927
- ...props
1928
- }
1929
- );
1930
- }
1931
- function DialogDescription({
1932
- className,
1933
- ...props
1934
- }) {
1935
- return /* @__PURE__ */ jsxRuntime.jsx(
1936
- radixUi.Dialog.Description,
1937
- {
1938
- "data-slot": "dialog-description",
1939
- className: cn(
1940
- "mt-0.5 text-sm leading-relaxed text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
1941
- className
1942
- ),
1943
- ...props
1944
- }
1945
- );
1946
- }
1947
-
1948
- // src/components/chat/tools/file-tools.tsx
1949
2196
  init_utils();
1950
2197
 
1951
2198
  // src/components/chat/diff-viewer.tsx
@@ -6178,6 +6425,32 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
6178
6425
  }
6179
6426
  return entries;
6180
6427
  }
6428
+ function groupBrowserActivityEntries(entries) {
6429
+ const grouped = [];
6430
+ let buffer = [];
6431
+ const flush = () => {
6432
+ if (buffer.length === 0) return;
6433
+ grouped.push({
6434
+ kind: "browser_activity",
6435
+ key: `browser-activity-${buffer[0]?.id}-${buffer[buffer.length - 1]?.id}`,
6436
+ calls: buffer
6437
+ });
6438
+ buffer = [];
6439
+ };
6440
+ for (const entry of entries) {
6441
+ if (entry.kind === "tool" && isBrowserToolCall(entry.tc)) {
6442
+ buffer.push(entry.tc);
6443
+ continue;
6444
+ }
6445
+ flush();
6446
+ grouped.push(entry);
6447
+ }
6448
+ flush();
6449
+ return grouped;
6450
+ }
6451
+ function isBrowserToolCall(call) {
6452
+ return call.toolName.startsWith("browser_");
6453
+ }
6181
6454
  function groupMessages(messages) {
6182
6455
  const groups = [];
6183
6456
  let pendingSystem = [];
@@ -6334,6 +6607,15 @@ function TimelineEntryItem({
6334
6607
  ] }) })
6335
6608
  ] });
6336
6609
  }
6610
+ if (entry.kind === "browser_activity") {
6611
+ return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
6612
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
6613
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
6614
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-amber-500" })
6615
+ ] }),
6616
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx(BrowserActivityGroup, { calls: entry.calls }) })
6617
+ ] });
6618
+ }
6337
6619
  if (entry.kind === "text") {
6338
6620
  return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
6339
6621
  /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
@@ -6395,11 +6677,12 @@ function AssistantGroup({
6395
6677
  onFileSelect,
6396
6678
  onRetry
6397
6679
  }) {
6398
- const entries = [];
6680
+ let entries = [];
6399
6681
  for (let i = 0; i < messages.length; i++) {
6400
6682
  const isLast = i === messages.length - 1 && lastGlobalIndex === totalMessages - 1;
6401
6683
  entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
6402
6684
  }
6685
+ entries = groupBrowserActivityEntries(entries);
6403
6686
  const isTailGroup = lastGlobalIndex === totalMessages - 1;
6404
6687
  const lastEntry = entries[entries.length - 1];
6405
6688
  const hasRunningTool = entries.some(
@@ -7476,6 +7759,7 @@ function WorkspacePanel({
7476
7759
  return /* @__PURE__ */ jsxRuntime.jsx(
7477
7760
  "aside",
7478
7761
  {
7762
+ "data-testid": "workspace-panel",
7479
7763
  role: "button",
7480
7764
  tabIndex: 0,
7481
7765
  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",
@@ -7494,6 +7778,7 @@ function WorkspacePanel({
7494
7778
  return /* @__PURE__ */ jsxRuntime.jsxs(
7495
7779
  "aside",
7496
7780
  {
7781
+ "data-testid": "workspace-panel",
7497
7782
  className: "relative z-10 flex min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
7498
7783
  style: { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
7499
7784
  children: [
@@ -7735,6 +8020,10 @@ var AGENT_CHAT_LISTENED_EVENTS = [
7735
8020
  "expert.override",
7736
8021
  "artifact.created",
7737
8022
  "artifact.updated",
8023
+ "browser.provisioned",
8024
+ "browser.destroyed",
8025
+ "browser.control_granted",
8026
+ "browser.control_returned",
7738
8027
  "clarify.response"
7739
8028
  ];
7740
8029
 
@@ -7759,7 +8048,8 @@ function createInitialAgentChatState(options = {}) {
7759
8048
  sessionDone: false,
7760
8049
  hadDeltas: false,
7761
8050
  terminal: false,
7762
- workspaceRefreshKey: 0
8051
+ workspaceRefreshKey: 0,
8052
+ browser: null
7763
8053
  };
7764
8054
  }
7765
8055
  function applyAgentChatEvent(state, event) {
@@ -7783,7 +8073,7 @@ function applyAgentChatEvent(state, event) {
7783
8073
  {
7784
8074
  id: `evt-${event.eventId}`,
7785
8075
  role: "system",
7786
- content: stringValue(event.data.skill),
8076
+ content: stringValue2(event.data.skill),
7787
8077
  createdAt: /* @__PURE__ */ new Date(),
7788
8078
  status: "complete",
7789
8079
  systemKind: "skill_invoked",
@@ -7800,7 +8090,7 @@ function applyAgentChatEvent(state, event) {
7800
8090
  {
7801
8091
  id: `evt-${event.eventId}`,
7802
8092
  role: "system",
7803
- content: stringValue(event.data.name),
8093
+ content: stringValue2(event.data.name),
7804
8094
  createdAt: /* @__PURE__ */ new Date(),
7805
8095
  status: "complete",
7806
8096
  systemKind: "artifact",
@@ -7813,6 +8103,23 @@ function applyAgentChatEvent(state, event) {
7813
8103
  }
7814
8104
  }
7815
8105
  ]);
8106
+ case "browser.provisioned":
8107
+ return applyBrowserEvent(nextState, event, {
8108
+ status: "live",
8109
+ controlOwner: null
8110
+ });
8111
+ case "browser.control_granted":
8112
+ return applyBrowserEvent(nextState, event, {
8113
+ status: "user-control",
8114
+ controlOwner: stringValue2(event.data.owner_user_id) || null
8115
+ });
8116
+ case "browser.control_returned":
8117
+ return applyBrowserEvent(nextState, event, {
8118
+ status: "live",
8119
+ controlOwner: null
8120
+ });
8121
+ case "browser.destroyed":
8122
+ return applyBrowserEvent(nextState, event, null);
7816
8123
  case "llm.delta":
7817
8124
  return applyLlmDelta(nextState, event);
7818
8125
  case "llm.response":
@@ -7822,7 +8129,7 @@ function applyAgentChatEvent(state, event) {
7822
8129
  case "tool.call":
7823
8130
  return applyToolCall(nextState, event);
7824
8131
  case "tool.result": {
7825
- const toolCallId = stringValue(event.data.tool_call_id);
8132
+ const toolCallId = stringValue2(event.data.tool_call_id);
7826
8133
  const toolName = findToolNameById(nextState.messages, toolCallId);
7827
8134
  const messages = applyToolResult(nextState.messages, event.data);
7828
8135
  const mutatesWorkspace = toolName !== null && WORKSPACE_MUTATING_TOOLS.has(toolName);
@@ -7892,8 +8199,8 @@ function applyRetryIndicator(state, event) {
7892
8199
  case "session.done":
7893
8200
  return { ...state, retryIndicator: null };
7894
8201
  case "harness.crash": {
7895
- const title = stringValue(event.data.error_title) || "A transient error occurred, retrying...";
7896
- const detail = stringValue(event.data.error_detail) || stringValue(event.data.error);
8202
+ const title = stringValue2(event.data.error_title) || "A transient error occurred, retrying...";
8203
+ const detail = stringValue2(event.data.error_detail) || stringValue2(event.data.error);
7897
8204
  return {
7898
8205
  ...state,
7899
8206
  retryIndicator: {
@@ -7908,7 +8215,7 @@ function applyRetryIndicator(state, event) {
7908
8215
  }
7909
8216
  }
7910
8217
  function applyUserMessage(messages, eventId, data) {
7911
- const content = stringValue(data.content);
8218
+ const content = stringValue2(data.content);
7912
8219
  const next = [...messages];
7913
8220
  const localIdx = next.findIndex(
7914
8221
  (m) => m.role === "user" && m.id.startsWith("local-") && m.content === content
@@ -7926,9 +8233,47 @@ function applyUserMessage(messages, eventId, data) {
7926
8233
  });
7927
8234
  return next;
7928
8235
  }
8236
+ function applyBrowserEvent(state, event, browser) {
8237
+ return withMessages(
8238
+ { ...state, browser },
8239
+ [...state.messages, browserMarker(event)]
8240
+ );
8241
+ }
8242
+ function browserMarker(event) {
8243
+ const labels = {
8244
+ "browser.provisioned": {
8245
+ content: "Browser ready.",
8246
+ warning: false
8247
+ },
8248
+ "browser.control_granted": {
8249
+ content: "A user took control of the browser.",
8250
+ warning: true
8251
+ },
8252
+ "browser.control_returned": {
8253
+ content: "Browser control returned to the agent.",
8254
+ warning: false
8255
+ },
8256
+ "browser.destroyed": {
8257
+ content: "Browser closed.",
8258
+ warning: false
8259
+ }
8260
+ };
8261
+ const label = labels[event.type] ?? {
8262
+ content: "Browser updated.",
8263
+ warning: false
8264
+ };
8265
+ return {
8266
+ id: `browser-marker-${event.eventId}`,
8267
+ role: "system",
8268
+ content: label.content,
8269
+ createdAt: /* @__PURE__ */ new Date(),
8270
+ status: "complete",
8271
+ systemKind: label.warning ? "browser_marker_warning" : "browser_marker"
8272
+ };
8273
+ }
7929
8274
  function applyLlmDelta(state, event) {
7930
- const deltaContent = stringValue(event.data.content);
7931
- const deltaReasoning = stringValue(event.data.reasoning);
8275
+ const deltaContent = stringValue2(event.data.content);
8276
+ const deltaReasoning = stringValue2(event.data.reasoning);
7932
8277
  const messages = [...state.messages];
7933
8278
  const lastIdx = findLastAssistantIndex(messages);
7934
8279
  const lastMsg = lastIdx >= 0 ? messages[lastIdx] : null;
@@ -7965,7 +8310,7 @@ function applyLlmDelta(state, event) {
7965
8310
  function applyLlmResponse(state, event) {
7966
8311
  const messages = [...state.messages];
7967
8312
  const message = objectValue(event.data.message);
7968
- const responseContent = stringValue(message?.content) || stringValue(event.data.content);
8313
+ const responseContent = stringValue2(message?.content) || stringValue2(event.data.content);
7969
8314
  const toolCalls = Array.isArray(message?.tool_calls) ? message?.tool_calls : [];
7970
8315
  const hasToolCalls = toolCalls.length > 0;
7971
8316
  const idx = findLastAssistantIndex(messages);
@@ -8023,12 +8368,12 @@ function applyLlmResponse(state, event) {
8023
8368
  cachedInputTokens: numberValue(event.data.cache_read_tokens),
8024
8369
  totalTokens: inputTokens + outputTokens,
8025
8370
  contextWindow: numberValue(event.data.context_window),
8026
- model: stringValue(event.data.model)
8371
+ model: stringValue2(event.data.model)
8027
8372
  }
8028
8373
  };
8029
8374
  }
8030
8375
  function applyLlmThinking(state, event) {
8031
- const reasoningText = stringValue(event.data.reasoning) || stringValue(event.data.content);
8376
+ const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
8032
8377
  const messages = [...state.messages];
8033
8378
  const idx = findLastAssistantIndex(messages);
8034
8379
  const prev = idx >= 0 ? messages[idx] : null;
@@ -8076,11 +8421,11 @@ function applyToolCall(state, event) {
8076
8421
  messages[assistantIdx] = assistant;
8077
8422
  }
8078
8423
  const existingCalls = assistant.toolCalls ?? [];
8079
- const toolCallId = stringValue(event.data.tool_call_id) || `tc-${event.eventId}`;
8424
+ const toolCallId = stringValue2(event.data.tool_call_id) || `tc-${event.eventId}`;
8080
8425
  if (!existingCalls.some((tc) => tc.id === toolCallId)) {
8081
8426
  const entry = {
8082
8427
  id: toolCallId,
8083
- toolName: stringValue(event.data.name) || stringValue(event.data.tool_name) || "unknown",
8428
+ toolName: stringValue2(event.data.name) || stringValue2(event.data.tool_name) || "unknown",
8084
8429
  args: typeof event.data.arguments === "string" ? event.data.arguments : JSON.stringify(event.data.arguments ?? {}),
8085
8430
  status: "running"
8086
8431
  };
@@ -8096,7 +8441,7 @@ function applyToolCall(state, event) {
8096
8441
  };
8097
8442
  }
8098
8443
  function applyToolResult(messages, data) {
8099
- const targetId = stringValue(data.tool_call_id);
8444
+ const targetId = stringValue2(data.tool_call_id);
8100
8445
  let matchIdx = -1;
8101
8446
  for (let i = messages.length - 1; i >= 0; i--) {
8102
8447
  if (messages[i]?.role === "assistant" && messages[i]?.toolCalls?.some((tc) => tc.id === targetId)) {
@@ -8165,8 +8510,8 @@ function buildErrorInfo(event) {
8165
8510
  }
8166
8511
  return {
8167
8512
  category: event.data.error_category,
8168
- title: stringValue(event.data.error_title) || "The session failed due to an internal error.",
8169
- detail: stringValue(event.data.error_detail) || stringValue(event.data.error),
8513
+ title: stringValue2(event.data.error_title) || "The session failed due to an internal error.",
8514
+ detail: stringValue2(event.data.error_detail) || stringValue2(event.data.error),
8170
8515
  retryable: Boolean(event.data.retryable)
8171
8516
  };
8172
8517
  }
@@ -8210,7 +8555,7 @@ function applyExpertFeedback(messages, type, data) {
8210
8555
  return messages;
8211
8556
  }
8212
8557
  function applyClarifyResponse(messages, data) {
8213
- const targetToolId = stringValue(data.tool_call_id);
8558
+ const targetToolId = stringValue2(data.tool_call_id);
8214
8559
  const responses = Array.isArray(data.responses) ? data.responses : void 0;
8215
8560
  if (!targetToolId || !responses) return messages;
8216
8561
  const next = [...messages];
@@ -8225,8 +8570,8 @@ function applyClarifyResponse(messages, data) {
8225
8570
  clarifyAnswers: responses.map((response) => {
8226
8571
  const row = objectValue(response) ?? {};
8227
8572
  return {
8228
- question: stringValue(row.question),
8229
- answer: stringValue(row.answer),
8573
+ question: stringValue2(row.question),
8574
+ answer: stringValue2(row.answer),
8230
8575
  is_other: Boolean(row.is_other)
8231
8576
  };
8232
8577
  })
@@ -8246,7 +8591,7 @@ function applyPolicyDenied(state, data) {
8246
8591
  ...policyMsg,
8247
8592
  content: `${policyMsg.content}
8248
8593
 
8249
- **Policy denied**: ${stringValue(data.reason) || "Action blocked by governance policy."}`,
8594
+ **Policy denied**: ${stringValue2(data.reason) || "Action blocked by governance policy."}`,
8250
8595
  status: "error"
8251
8596
  };
8252
8597
  }
@@ -8298,7 +8643,7 @@ function objectValue(value) {
8298
8643
  }
8299
8644
  return void 0;
8300
8645
  }
8301
- function stringValue(value) {
8646
+ function stringValue2(value) {
8302
8647
  return typeof value === "string" ? value : "";
8303
8648
  }
8304
8649
  function numberValue(value) {
@@ -8532,6 +8877,7 @@ function useAgentChatRuntime({
8532
8877
  }
8533
8878
  }, [adapter, sessionId]);
8534
8879
  return {
8880
+ state,
8535
8881
  session,
8536
8882
  messages: state.messages,
8537
8883
  isRunning: state.isRunning,
@@ -8583,6 +8929,7 @@ function AgentChat({
8583
8929
  const isReadOnlySession = isScheduledRunSession(runtime.session);
8584
8930
  const effectiveDisabled = disabled || isReadOnlySession;
8585
8931
  const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
8932
+ const browserState = runtime.state.browser;
8586
8933
  react.useEffect(() => {
8587
8934
  onMessagesChange?.(runtime.messages);
8588
8935
  }, [onMessagesChange, runtime.messages]);
@@ -8619,19 +8966,29 @@ function AgentChat({
8619
8966
  retryIndicator: runtime.retryIndicator
8620
8967
  }
8621
8968
  ) }),
8622
- /* @__PURE__ */ jsxRuntime.jsx(
8623
- WorkspacePanel,
8624
- {
8625
- adapter,
8626
- sessionId,
8627
- selectedPath: workspacePath,
8628
- onSelectedPathChange: setWorkspacePath,
8629
- collapsed: workspaceCollapsed,
8630
- onCollapsedChange: setWorkspaceCollapsed,
8631
- refreshSignal: runtime.workspaceRefreshKey,
8632
- disabled: effectiveDisabled
8633
- }
8634
- )
8969
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": "right-stack", className: "flex min-h-0 shrink-0 flex-col", children: [
8970
+ browserState !== null && sessionId && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 border-b border-line", children: /* @__PURE__ */ jsxRuntime.jsx(
8971
+ BrowserPane,
8972
+ {
8973
+ sessionId,
8974
+ state: browserState,
8975
+ adapter
8976
+ }
8977
+ ) }),
8978
+ /* @__PURE__ */ jsxRuntime.jsx(
8979
+ WorkspacePanel,
8980
+ {
8981
+ adapter,
8982
+ sessionId,
8983
+ selectedPath: workspacePath,
8984
+ onSelectedPathChange: setWorkspacePath,
8985
+ collapsed: workspaceCollapsed,
8986
+ onCollapsedChange: setWorkspaceCollapsed,
8987
+ refreshSignal: runtime.workspaceRefreshKey,
8988
+ disabled: effectiveDisabled
8989
+ }
8990
+ )
8991
+ ] })
8635
8992
  ] }) })
8636
8993
  }
8637
8994
  );