@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.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { cn } from './chunk-QSC4UIVT.js';
2
2
  import { createContext, memo, useRef, useState, useEffect, useCallback, useMemo, lazy, useContext, Suspense, Children } from 'react';
3
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, PlayIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
3
4
  import { cva } from 'class-variance-authority';
4
5
  import { Collapsible as Collapsible$1, Slot, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Tooltip as Tooltip$1, Tabs as Tabs$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1, HoverCard as HoverCard$1, Progress as Progress$1 } from 'radix-ui';
5
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
7
7
  import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
8
8
  import { cjk } from '@streamdown/cjk';
9
9
  import { code } from '@streamdown/code';
@@ -79,6 +79,353 @@ function Button({
79
79
  }
80
80
  );
81
81
  }
82
+ function BrowserControlBar({
83
+ sessionId,
84
+ hasControl,
85
+ adapter
86
+ }) {
87
+ const [pending, setPending] = useState(false);
88
+ const [error, setError] = useState(null);
89
+ async function toggleControl() {
90
+ if (!sessionId || pending) return;
91
+ setPending(true);
92
+ setError(null);
93
+ try {
94
+ if (hasControl) {
95
+ await adapter.releaseBrowserControl(sessionId);
96
+ } else {
97
+ await adapter.acquireBrowserControl(sessionId);
98
+ }
99
+ } catch (nextError) {
100
+ setError(nextError.message);
101
+ } finally {
102
+ setPending(false);
103
+ }
104
+ }
105
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-11 items-center gap-2 border-t border-line bg-card px-3 py-2", children: [
106
+ /* @__PURE__ */ jsxs(
107
+ Button,
108
+ {
109
+ type: "button",
110
+ size: "xs",
111
+ variant: hasControl ? "secondary" : "default",
112
+ disabled: !sessionId || pending,
113
+ onClick: () => void toggleControl(),
114
+ children: [
115
+ hasControl ? /* @__PURE__ */ jsx(RotateCcwIcon, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(MousePointer2Icon, { className: "size-3", "aria-hidden": "true" }),
116
+ hasControl ? "Return control" : "Take control"
117
+ ]
118
+ }
119
+ ),
120
+ /* @__PURE__ */ jsxs("span", { className: "ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
121
+ /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-red-500", "aria-hidden": "true" }),
122
+ "Live"
123
+ ] }),
124
+ error && /* @__PURE__ */ jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error })
125
+ ] });
126
+ }
127
+ function BrowserLiveView({
128
+ src,
129
+ testId = "browser-iframe"
130
+ }) {
131
+ return /* @__PURE__ */ jsx(
132
+ "iframe",
133
+ {
134
+ "data-testid": testId,
135
+ title: "Browser live view",
136
+ src,
137
+ className: "h-full w-full border-0 bg-black",
138
+ allow: "clipboard-read; clipboard-write"
139
+ }
140
+ );
141
+ }
142
+ function BrowserStatusDot({
143
+ status
144
+ }) {
145
+ return /* @__PURE__ */ jsx(
146
+ "span",
147
+ {
148
+ "aria-hidden": "true",
149
+ className: cn(
150
+ "inline-block size-2 shrink-0 rounded-full",
151
+ status === "live" && "bg-emerald-500",
152
+ status === "user-control" && "bg-amber-500",
153
+ status === "provisioning" && "animate-pulse bg-sky-500",
154
+ status === "closed" && "bg-muted-foreground"
155
+ )
156
+ }
157
+ );
158
+ }
159
+ function Dialog({
160
+ ...props
161
+ }) {
162
+ return /* @__PURE__ */ jsx(Dialog$1.Root, { "data-slot": "dialog", ...props });
163
+ }
164
+ function DialogPortal({
165
+ ...props
166
+ }) {
167
+ return /* @__PURE__ */ jsx(Dialog$1.Portal, { "data-slot": "dialog-portal", ...props });
168
+ }
169
+ function DialogOverlay({
170
+ className,
171
+ ...props
172
+ }) {
173
+ return /* @__PURE__ */ jsx(
174
+ Dialog$1.Overlay,
175
+ {
176
+ "data-slot": "dialog-overlay",
177
+ className: cn(
178
+ "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",
179
+ className
180
+ ),
181
+ ...props
182
+ }
183
+ );
184
+ }
185
+ function DialogContent({
186
+ className,
187
+ children,
188
+ showCloseButton = true,
189
+ ...props
190
+ }) {
191
+ return /* @__PURE__ */ jsxs(DialogPortal, { children: [
192
+ /* @__PURE__ */ jsx(DialogOverlay, {}),
193
+ /* @__PURE__ */ jsxs(
194
+ Dialog$1.Content,
195
+ {
196
+ "data-slot": "dialog-content",
197
+ className: cn(
198
+ "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",
199
+ className
200
+ ),
201
+ ...props,
202
+ children: [
203
+ children,
204
+ showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ jsxs(
205
+ Button,
206
+ {
207
+ variant: "ghost",
208
+ className: "absolute top-5 right-5 bg-secondary",
209
+ size: "icon-sm",
210
+ children: [
211
+ /* @__PURE__ */ jsx(
212
+ XIcon,
213
+ {}
214
+ ),
215
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
216
+ ]
217
+ }
218
+ ) })
219
+ ]
220
+ }
221
+ )
222
+ ] });
223
+ }
224
+ function DialogHeader({ className, ...props }) {
225
+ return /* @__PURE__ */ jsx(
226
+ "div",
227
+ {
228
+ "data-slot": "dialog-header",
229
+ className: cn("flex flex-col gap-2", className),
230
+ ...props
231
+ }
232
+ );
233
+ }
234
+ function DialogFooter({
235
+ className,
236
+ showCloseButton = false,
237
+ children,
238
+ ...props
239
+ }) {
240
+ return /* @__PURE__ */ jsxs(
241
+ "div",
242
+ {
243
+ "data-slot": "dialog-footer",
244
+ className: cn(
245
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
246
+ className
247
+ ),
248
+ ...props,
249
+ children: [
250
+ children,
251
+ showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", children: "Close" }) })
252
+ ]
253
+ }
254
+ );
255
+ }
256
+ function DialogTitle({
257
+ className,
258
+ ...props
259
+ }) {
260
+ return /* @__PURE__ */ jsx(
261
+ Dialog$1.Title,
262
+ {
263
+ "data-slot": "dialog-title",
264
+ className: cn(
265
+ "font-heading text-lg leading-none font-semibold tracking-wider uppercase",
266
+ className
267
+ ),
268
+ ...props
269
+ }
270
+ );
271
+ }
272
+ function DialogDescription({
273
+ className,
274
+ ...props
275
+ }) {
276
+ return /* @__PURE__ */ jsx(
277
+ Dialog$1.Description,
278
+ {
279
+ "data-slot": "dialog-description",
280
+ className: cn(
281
+ "mt-0.5 text-sm leading-relaxed text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
282
+ className
283
+ ),
284
+ ...props
285
+ }
286
+ );
287
+ }
288
+ function TooltipProvider({
289
+ delayDuration = 0,
290
+ ...props
291
+ }) {
292
+ return /* @__PURE__ */ jsx(
293
+ Tooltip$1.Provider,
294
+ {
295
+ "data-slot": "tooltip-provider",
296
+ delayDuration,
297
+ ...props
298
+ }
299
+ );
300
+ }
301
+ function Tooltip({
302
+ ...props
303
+ }) {
304
+ return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
305
+ }
306
+ function TooltipTrigger({
307
+ ...props
308
+ }) {
309
+ return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
310
+ }
311
+ function TooltipContent({
312
+ className,
313
+ sideOffset = 0,
314
+ children,
315
+ ...props
316
+ }) {
317
+ return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
318
+ Tooltip$1.Content,
319
+ {
320
+ "data-slot": "tooltip-content",
321
+ sideOffset,
322
+ className: cn(
323
+ "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",
324
+ className
325
+ ),
326
+ ...props,
327
+ children: [
328
+ children,
329
+ /* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground" })
330
+ ]
331
+ }
332
+ ) });
333
+ }
334
+ function BrowserPane({ sessionId, state, adapter }) {
335
+ const [fullscreenOpen, setFullscreenOpen] = useState(false);
336
+ const [inlinePreviewOpen, setInlinePreviewOpen] = useState(false);
337
+ const hasLiveViewAdapter = typeof adapter.browserLiveViewUrl === "function";
338
+ const hasControlAdapter = typeof adapter.acquireBrowserControl === "function" && typeof adapter.releaseBrowserControl === "function";
339
+ const liveViewUrl = useMemo(() => {
340
+ if (!hasLiveViewAdapter) return "";
341
+ return adapter.browserLiveViewUrl(sessionId);
342
+ }, [adapter, hasLiveViewAdapter, sessionId]);
343
+ const hasLiveView = state.status !== "provisioning" && state.status !== "closed";
344
+ const canOpenFullscreen = hasLiveView && Boolean(liveViewUrl);
345
+ useEffect(() => {
346
+ setFullscreenOpen(false);
347
+ setInlinePreviewOpen(false);
348
+ }, [sessionId]);
349
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
350
+ /* @__PURE__ */ jsxs(
351
+ "div",
352
+ {
353
+ "data-testid": "browser-pane",
354
+ className: "flex h-full min-h-0 flex-col bg-background",
355
+ children: [
356
+ /* @__PURE__ */ jsxs("header", { className: "flex min-h-10 items-center gap-2 border-b border-line bg-card px-3 text-xs text-foreground", children: [
357
+ /* @__PURE__ */ jsx(ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
358
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Browser" }),
359
+ /* @__PURE__ */ jsx(BrowserStatusDot, { status: state.status }),
360
+ state.controlOwner && /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate text-amber-500", children: [
361
+ state.controlOwner,
362
+ " has control"
363
+ ] }),
364
+ canOpenFullscreen && /* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
365
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
366
+ "button",
367
+ {
368
+ type: "button",
369
+ "aria-label": "Maximize browser",
370
+ 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",
371
+ onClick: () => setFullscreenOpen(true),
372
+ children: /* @__PURE__ */ jsx(Maximize2Icon, { className: "size-3.5", "aria-hidden": "true" })
373
+ }
374
+ ) }),
375
+ /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: "Full screen" })
376
+ ] }) }) })
377
+ ] }),
378
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-black", children: state.status === "provisioning" ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Starting browser..." }) : state.status === "closed" ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser closed." }) : liveViewUrl && inlinePreviewOpen ? /* @__PURE__ */ jsx(BrowserLiveView, { src: liveViewUrl }) : liveViewUrl ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: /* @__PURE__ */ jsxs(
379
+ "button",
380
+ {
381
+ type: "button",
382
+ "aria-label": "Open browser live preview",
383
+ 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",
384
+ onClick: () => setInlinePreviewOpen(true),
385
+ children: [
386
+ /* @__PURE__ */ jsx(PlayIcon, { className: "size-3.5", "aria-hidden": "true" }),
387
+ /* @__PURE__ */ jsx("span", { children: "Open live preview" })
388
+ ]
389
+ }
390
+ ) }) : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser live view is unavailable." }) }),
391
+ hasLiveView && hasControlAdapter && /* @__PURE__ */ jsx(
392
+ BrowserControlBar,
393
+ {
394
+ sessionId,
395
+ hasControl: state.status === "user-control",
396
+ adapter
397
+ }
398
+ )
399
+ ]
400
+ }
401
+ ),
402
+ /* @__PURE__ */ jsx(Dialog, { open: fullscreenOpen, onOpenChange: setFullscreenOpen, children: /* @__PURE__ */ jsxs(
403
+ DialogContent,
404
+ {
405
+ "aria-describedby": void 0,
406
+ 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",
407
+ children: [
408
+ /* @__PURE__ */ jsxs(DialogHeader, { className: "h-10 shrink-0 flex-row items-center gap-2 border-b border-line bg-card px-4 py-0", children: [
409
+ /* @__PURE__ */ jsx(ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
410
+ /* @__PURE__ */ jsx(DialogTitle, { className: "text-xs normal-case tracking-normal", children: "Browser" }),
411
+ /* @__PURE__ */ jsx(BrowserStatusDot, { status: state.status }),
412
+ state.controlOwner && /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate text-xs text-amber-500", children: [
413
+ state.controlOwner,
414
+ " has control"
415
+ ] })
416
+ ] }),
417
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-black", children: liveViewUrl ? /* @__PURE__ */ jsx(
418
+ BrowserLiveView,
419
+ {
420
+ src: liveViewUrl,
421
+ testId: "browser-fullscreen-iframe"
422
+ }
423
+ ) : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser live view is unavailable." }) })
424
+ ]
425
+ }
426
+ ) })
427
+ ] });
428
+ }
82
429
  var Conversation = ({ className, ...props }) => /* @__PURE__ */ jsx(
83
430
  StickToBottom,
84
431
  {
@@ -147,52 +494,6 @@ var ConversationScrollButton = ({
147
494
  }
148
495
  );
149
496
  };
150
- function TooltipProvider({
151
- delayDuration = 0,
152
- ...props
153
- }) {
154
- return /* @__PURE__ */ jsx(
155
- Tooltip$1.Provider,
156
- {
157
- "data-slot": "tooltip-provider",
158
- delayDuration,
159
- ...props
160
- }
161
- );
162
- }
163
- function Tooltip({
164
- ...props
165
- }) {
166
- return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
167
- }
168
- function TooltipTrigger({
169
- ...props
170
- }) {
171
- return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
172
- }
173
- function TooltipContent({
174
- className,
175
- sideOffset = 0,
176
- children,
177
- ...props
178
- }) {
179
- return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
180
- Tooltip$1.Content,
181
- {
182
- "data-slot": "tooltip-content",
183
- sideOffset,
184
- className: cn(
185
- "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",
186
- className
187
- ),
188
- ...props,
189
- children: [
190
- children,
191
- /* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground" })
192
- ]
193
- }
194
- ) });
195
- }
196
497
  var Message = ({ className, from, ...props }) => /* @__PURE__ */ jsx(
197
498
  "div",
198
499
  {
@@ -565,6 +866,80 @@ function TimelineSeparator({
565
866
  }
566
867
  );
567
868
  }
869
+ function BrowserActivityGroup({ calls }) {
870
+ const [open, setOpen] = useState(false);
871
+ const latest = calls[calls.length - 1];
872
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-none border border-line bg-card text-xs", children: [
873
+ /* @__PURE__ */ jsxs(
874
+ "button",
875
+ {
876
+ type: "button",
877
+ onClick: () => setOpen((current) => !current),
878
+ className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-muted/40",
879
+ children: [
880
+ open ? /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3 shrink-0", "aria-hidden": "true" }),
881
+ /* @__PURE__ */ jsx(ZapIcon, { className: "size-3 shrink-0 text-amber-500", "aria-hidden": "true" }),
882
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "browser" }),
883
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate text-muted-foreground", children: [
884
+ "(",
885
+ calls.length,
886
+ " ",
887
+ calls.length === 1 ? "action" : "actions",
888
+ " - latest:",
889
+ " ",
890
+ latest ? summarise(latest) : "none",
891
+ ")"
892
+ ] })
893
+ ]
894
+ }
895
+ ),
896
+ open && /* @__PURE__ */ jsx("ul", { className: "border-t border-line py-1", children: calls.map((call) => {
897
+ const error = stringValue(parseJson(call.result).error);
898
+ return /* @__PURE__ */ jsxs(
899
+ "li",
900
+ {
901
+ className: "flex items-center gap-2 px-6 py-1 font-mono text-[11px]",
902
+ children: [
903
+ error && /* @__PURE__ */ jsx(
904
+ AlertCircleIcon,
905
+ {
906
+ "data-testid": `activity-error-${call.id}`,
907
+ className: "size-3 shrink-0 text-destructive",
908
+ "aria-hidden": "true"
909
+ }
910
+ ),
911
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: summarise(call) }),
912
+ error && /* @__PURE__ */ jsx("span", { className: "ml-auto max-w-40 truncate text-destructive", children: error })
913
+ ]
914
+ },
915
+ call.id
916
+ );
917
+ }) })
918
+ ] });
919
+ }
920
+ function summarise(call) {
921
+ const verb = call.toolName.replace(/^browser_/, "");
922
+ const args = parseJson(call.args);
923
+ const ref = stringValue(args.ref);
924
+ const url = stringValue(args.url);
925
+ const text = stringValue(args.text);
926
+ if (url) return `${verb} ${url}`;
927
+ if (ref) return `${verb} ${ref}`;
928
+ if (text) return `${verb} "${text.slice(0, 24)}"`;
929
+ return verb;
930
+ }
931
+ function parseJson(raw) {
932
+ if (!raw) return {};
933
+ try {
934
+ const parsed = JSON.parse(raw);
935
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
936
+ } catch {
937
+ return {};
938
+ }
939
+ }
940
+ function stringValue(value) {
941
+ return typeof value === "string" ? value : "";
942
+ }
568
943
 
569
944
  // src/components/chat/tools/shared.ts
570
945
  function statusColorClass(status) {
@@ -1591,135 +1966,6 @@ function WebToolBlock({ tc }) {
1591
1966
  displayText && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: displayText })
1592
1967
  ] });
1593
1968
  }
1594
- function Dialog({
1595
- ...props
1596
- }) {
1597
- return /* @__PURE__ */ jsx(Dialog$1.Root, { "data-slot": "dialog", ...props });
1598
- }
1599
- function DialogPortal({
1600
- ...props
1601
- }) {
1602
- return /* @__PURE__ */ jsx(Dialog$1.Portal, { "data-slot": "dialog-portal", ...props });
1603
- }
1604
- function DialogOverlay({
1605
- className,
1606
- ...props
1607
- }) {
1608
- return /* @__PURE__ */ jsx(
1609
- Dialog$1.Overlay,
1610
- {
1611
- "data-slot": "dialog-overlay",
1612
- className: cn(
1613
- "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",
1614
- className
1615
- ),
1616
- ...props
1617
- }
1618
- );
1619
- }
1620
- function DialogContent({
1621
- className,
1622
- children,
1623
- showCloseButton = true,
1624
- ...props
1625
- }) {
1626
- return /* @__PURE__ */ jsxs(DialogPortal, { children: [
1627
- /* @__PURE__ */ jsx(DialogOverlay, {}),
1628
- /* @__PURE__ */ jsxs(
1629
- Dialog$1.Content,
1630
- {
1631
- "data-slot": "dialog-content",
1632
- className: cn(
1633
- "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",
1634
- className
1635
- ),
1636
- ...props,
1637
- children: [
1638
- children,
1639
- showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ jsxs(
1640
- Button,
1641
- {
1642
- variant: "ghost",
1643
- className: "absolute top-5 right-5 bg-secondary",
1644
- size: "icon-sm",
1645
- children: [
1646
- /* @__PURE__ */ jsx(
1647
- XIcon,
1648
- {}
1649
- ),
1650
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
1651
- ]
1652
- }
1653
- ) })
1654
- ]
1655
- }
1656
- )
1657
- ] });
1658
- }
1659
- function DialogHeader({ className, ...props }) {
1660
- return /* @__PURE__ */ jsx(
1661
- "div",
1662
- {
1663
- "data-slot": "dialog-header",
1664
- className: cn("flex flex-col gap-2", className),
1665
- ...props
1666
- }
1667
- );
1668
- }
1669
- function DialogFooter({
1670
- className,
1671
- showCloseButton = false,
1672
- children,
1673
- ...props
1674
- }) {
1675
- return /* @__PURE__ */ jsxs(
1676
- "div",
1677
- {
1678
- "data-slot": "dialog-footer",
1679
- className: cn(
1680
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1681
- className
1682
- ),
1683
- ...props,
1684
- children: [
1685
- children,
1686
- showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", children: "Close" }) })
1687
- ]
1688
- }
1689
- );
1690
- }
1691
- function DialogTitle({
1692
- className,
1693
- ...props
1694
- }) {
1695
- return /* @__PURE__ */ jsx(
1696
- Dialog$1.Title,
1697
- {
1698
- "data-slot": "dialog-title",
1699
- className: cn(
1700
- "font-heading text-lg leading-none font-semibold tracking-wider uppercase",
1701
- className
1702
- ),
1703
- ...props
1704
- }
1705
- );
1706
- }
1707
- function DialogDescription({
1708
- className,
1709
- ...props
1710
- }) {
1711
- return /* @__PURE__ */ jsx(
1712
- Dialog$1.Description,
1713
- {
1714
- "data-slot": "dialog-description",
1715
- className: cn(
1716
- "mt-0.5 text-sm leading-relaxed text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
1717
- className
1718
- ),
1719
- ...props
1720
- }
1721
- );
1722
- }
1723
1969
  function splitChangeLines(change) {
1724
1970
  const v = change.value;
1725
1971
  if (v === "") return [];
@@ -5886,6 +6132,32 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
5886
6132
  }
5887
6133
  return entries;
5888
6134
  }
6135
+ function groupBrowserActivityEntries(entries) {
6136
+ const grouped = [];
6137
+ let buffer = [];
6138
+ const flush = () => {
6139
+ if (buffer.length === 0) return;
6140
+ grouped.push({
6141
+ kind: "browser_activity",
6142
+ key: `browser-activity-${buffer[0]?.id}-${buffer[buffer.length - 1]?.id}`,
6143
+ calls: buffer
6144
+ });
6145
+ buffer = [];
6146
+ };
6147
+ for (const entry of entries) {
6148
+ if (entry.kind === "tool" && isBrowserToolCall(entry.tc)) {
6149
+ buffer.push(entry.tc);
6150
+ continue;
6151
+ }
6152
+ flush();
6153
+ grouped.push(entry);
6154
+ }
6155
+ flush();
6156
+ return grouped;
6157
+ }
6158
+ function isBrowserToolCall(call) {
6159
+ return call.toolName.startsWith("browser_");
6160
+ }
5889
6161
  function groupMessages(messages) {
5890
6162
  const groups = [];
5891
6163
  let pendingSystem = [];
@@ -6042,6 +6314,15 @@ function TimelineEntryItem({
6042
6314
  ] }) })
6043
6315
  ] });
6044
6316
  }
6317
+ if (entry.kind === "browser_activity") {
6318
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
6319
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
6320
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
6321
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-amber-500" })
6322
+ ] }),
6323
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(BrowserActivityGroup, { calls: entry.calls }) })
6324
+ ] });
6325
+ }
6045
6326
  if (entry.kind === "text") {
6046
6327
  return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
6047
6328
  /* @__PURE__ */ jsxs(TimelineHeader, { children: [
@@ -6103,11 +6384,12 @@ function AssistantGroup({
6103
6384
  onFileSelect,
6104
6385
  onRetry
6105
6386
  }) {
6106
- const entries = [];
6387
+ let entries = [];
6107
6388
  for (let i = 0; i < messages.length; i++) {
6108
6389
  const isLast = i === messages.length - 1 && lastGlobalIndex === totalMessages - 1;
6109
6390
  entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
6110
6391
  }
6392
+ entries = groupBrowserActivityEntries(entries);
6111
6393
  const isTailGroup = lastGlobalIndex === totalMessages - 1;
6112
6394
  const lastEntry = entries[entries.length - 1];
6113
6395
  const hasRunningTool = entries.some(
@@ -7175,6 +7457,7 @@ function WorkspacePanel({
7175
7457
  return /* @__PURE__ */ jsx(
7176
7458
  "aside",
7177
7459
  {
7460
+ "data-testid": "workspace-panel",
7178
7461
  role: "button",
7179
7462
  tabIndex: 0,
7180
7463
  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",
@@ -7193,6 +7476,7 @@ function WorkspacePanel({
7193
7476
  return /* @__PURE__ */ jsxs(
7194
7477
  "aside",
7195
7478
  {
7479
+ "data-testid": "workspace-panel",
7196
7480
  className: "relative z-10 flex min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
7197
7481
  style: { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
7198
7482
  children: [
@@ -7434,6 +7718,10 @@ var AGENT_CHAT_LISTENED_EVENTS = [
7434
7718
  "expert.override",
7435
7719
  "artifact.created",
7436
7720
  "artifact.updated",
7721
+ "browser.provisioned",
7722
+ "browser.destroyed",
7723
+ "browser.control_granted",
7724
+ "browser.control_returned",
7437
7725
  "clarify.response"
7438
7726
  ];
7439
7727
 
@@ -7458,7 +7746,8 @@ function createInitialAgentChatState(options = {}) {
7458
7746
  sessionDone: false,
7459
7747
  hadDeltas: false,
7460
7748
  terminal: false,
7461
- workspaceRefreshKey: 0
7749
+ workspaceRefreshKey: 0,
7750
+ browser: null
7462
7751
  };
7463
7752
  }
7464
7753
  function applyAgentChatEvent(state, event) {
@@ -7482,7 +7771,7 @@ function applyAgentChatEvent(state, event) {
7482
7771
  {
7483
7772
  id: `evt-${event.eventId}`,
7484
7773
  role: "system",
7485
- content: stringValue(event.data.skill),
7774
+ content: stringValue2(event.data.skill),
7486
7775
  createdAt: /* @__PURE__ */ new Date(),
7487
7776
  status: "complete",
7488
7777
  systemKind: "skill_invoked",
@@ -7499,7 +7788,7 @@ function applyAgentChatEvent(state, event) {
7499
7788
  {
7500
7789
  id: `evt-${event.eventId}`,
7501
7790
  role: "system",
7502
- content: stringValue(event.data.name),
7791
+ content: stringValue2(event.data.name),
7503
7792
  createdAt: /* @__PURE__ */ new Date(),
7504
7793
  status: "complete",
7505
7794
  systemKind: "artifact",
@@ -7512,6 +7801,23 @@ function applyAgentChatEvent(state, event) {
7512
7801
  }
7513
7802
  }
7514
7803
  ]);
7804
+ case "browser.provisioned":
7805
+ return applyBrowserEvent(nextState, event, {
7806
+ status: "live",
7807
+ controlOwner: null
7808
+ });
7809
+ case "browser.control_granted":
7810
+ return applyBrowserEvent(nextState, event, {
7811
+ status: "user-control",
7812
+ controlOwner: stringValue2(event.data.owner_user_id) || null
7813
+ });
7814
+ case "browser.control_returned":
7815
+ return applyBrowserEvent(nextState, event, {
7816
+ status: "live",
7817
+ controlOwner: null
7818
+ });
7819
+ case "browser.destroyed":
7820
+ return applyBrowserEvent(nextState, event, null);
7515
7821
  case "llm.delta":
7516
7822
  return applyLlmDelta(nextState, event);
7517
7823
  case "llm.response":
@@ -7521,7 +7827,7 @@ function applyAgentChatEvent(state, event) {
7521
7827
  case "tool.call":
7522
7828
  return applyToolCall(nextState, event);
7523
7829
  case "tool.result": {
7524
- const toolCallId = stringValue(event.data.tool_call_id);
7830
+ const toolCallId = stringValue2(event.data.tool_call_id);
7525
7831
  const toolName = findToolNameById(nextState.messages, toolCallId);
7526
7832
  const messages = applyToolResult(nextState.messages, event.data);
7527
7833
  const mutatesWorkspace = toolName !== null && WORKSPACE_MUTATING_TOOLS.has(toolName);
@@ -7591,8 +7897,8 @@ function applyRetryIndicator(state, event) {
7591
7897
  case "session.done":
7592
7898
  return { ...state, retryIndicator: null };
7593
7899
  case "harness.crash": {
7594
- const title = stringValue(event.data.error_title) || "A transient error occurred, retrying...";
7595
- const detail = stringValue(event.data.error_detail) || stringValue(event.data.error);
7900
+ const title = stringValue2(event.data.error_title) || "A transient error occurred, retrying...";
7901
+ const detail = stringValue2(event.data.error_detail) || stringValue2(event.data.error);
7596
7902
  return {
7597
7903
  ...state,
7598
7904
  retryIndicator: {
@@ -7607,7 +7913,7 @@ function applyRetryIndicator(state, event) {
7607
7913
  }
7608
7914
  }
7609
7915
  function applyUserMessage(messages, eventId, data) {
7610
- const content = stringValue(data.content);
7916
+ const content = stringValue2(data.content);
7611
7917
  const next = [...messages];
7612
7918
  const localIdx = next.findIndex(
7613
7919
  (m) => m.role === "user" && m.id.startsWith("local-") && m.content === content
@@ -7625,9 +7931,47 @@ function applyUserMessage(messages, eventId, data) {
7625
7931
  });
7626
7932
  return next;
7627
7933
  }
7934
+ function applyBrowserEvent(state, event, browser) {
7935
+ return withMessages(
7936
+ { ...state, browser },
7937
+ [...state.messages, browserMarker(event)]
7938
+ );
7939
+ }
7940
+ function browserMarker(event) {
7941
+ const labels = {
7942
+ "browser.provisioned": {
7943
+ content: "Browser ready.",
7944
+ warning: false
7945
+ },
7946
+ "browser.control_granted": {
7947
+ content: "A user took control of the browser.",
7948
+ warning: true
7949
+ },
7950
+ "browser.control_returned": {
7951
+ content: "Browser control returned to the agent.",
7952
+ warning: false
7953
+ },
7954
+ "browser.destroyed": {
7955
+ content: "Browser closed.",
7956
+ warning: false
7957
+ }
7958
+ };
7959
+ const label = labels[event.type] ?? {
7960
+ content: "Browser updated.",
7961
+ warning: false
7962
+ };
7963
+ return {
7964
+ id: `browser-marker-${event.eventId}`,
7965
+ role: "system",
7966
+ content: label.content,
7967
+ createdAt: /* @__PURE__ */ new Date(),
7968
+ status: "complete",
7969
+ systemKind: label.warning ? "browser_marker_warning" : "browser_marker"
7970
+ };
7971
+ }
7628
7972
  function applyLlmDelta(state, event) {
7629
- const deltaContent = stringValue(event.data.content);
7630
- const deltaReasoning = stringValue(event.data.reasoning);
7973
+ const deltaContent = stringValue2(event.data.content);
7974
+ const deltaReasoning = stringValue2(event.data.reasoning);
7631
7975
  const messages = [...state.messages];
7632
7976
  const lastIdx = findLastAssistantIndex(messages);
7633
7977
  const lastMsg = lastIdx >= 0 ? messages[lastIdx] : null;
@@ -7664,7 +8008,7 @@ function applyLlmDelta(state, event) {
7664
8008
  function applyLlmResponse(state, event) {
7665
8009
  const messages = [...state.messages];
7666
8010
  const message = objectValue(event.data.message);
7667
- const responseContent = stringValue(message?.content) || stringValue(event.data.content);
8011
+ const responseContent = stringValue2(message?.content) || stringValue2(event.data.content);
7668
8012
  const toolCalls = Array.isArray(message?.tool_calls) ? message?.tool_calls : [];
7669
8013
  const hasToolCalls = toolCalls.length > 0;
7670
8014
  const idx = findLastAssistantIndex(messages);
@@ -7722,12 +8066,12 @@ function applyLlmResponse(state, event) {
7722
8066
  cachedInputTokens: numberValue(event.data.cache_read_tokens),
7723
8067
  totalTokens: inputTokens + outputTokens,
7724
8068
  contextWindow: numberValue(event.data.context_window),
7725
- model: stringValue(event.data.model)
8069
+ model: stringValue2(event.data.model)
7726
8070
  }
7727
8071
  };
7728
8072
  }
7729
8073
  function applyLlmThinking(state, event) {
7730
- const reasoningText = stringValue(event.data.reasoning) || stringValue(event.data.content);
8074
+ const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
7731
8075
  const messages = [...state.messages];
7732
8076
  const idx = findLastAssistantIndex(messages);
7733
8077
  const prev = idx >= 0 ? messages[idx] : null;
@@ -7775,11 +8119,11 @@ function applyToolCall(state, event) {
7775
8119
  messages[assistantIdx] = assistant;
7776
8120
  }
7777
8121
  const existingCalls = assistant.toolCalls ?? [];
7778
- const toolCallId = stringValue(event.data.tool_call_id) || `tc-${event.eventId}`;
8122
+ const toolCallId = stringValue2(event.data.tool_call_id) || `tc-${event.eventId}`;
7779
8123
  if (!existingCalls.some((tc) => tc.id === toolCallId)) {
7780
8124
  const entry = {
7781
8125
  id: toolCallId,
7782
- toolName: stringValue(event.data.name) || stringValue(event.data.tool_name) || "unknown",
8126
+ toolName: stringValue2(event.data.name) || stringValue2(event.data.tool_name) || "unknown",
7783
8127
  args: typeof event.data.arguments === "string" ? event.data.arguments : JSON.stringify(event.data.arguments ?? {}),
7784
8128
  status: "running"
7785
8129
  };
@@ -7795,7 +8139,7 @@ function applyToolCall(state, event) {
7795
8139
  };
7796
8140
  }
7797
8141
  function applyToolResult(messages, data) {
7798
- const targetId = stringValue(data.tool_call_id);
8142
+ const targetId = stringValue2(data.tool_call_id);
7799
8143
  let matchIdx = -1;
7800
8144
  for (let i = messages.length - 1; i >= 0; i--) {
7801
8145
  if (messages[i]?.role === "assistant" && messages[i]?.toolCalls?.some((tc) => tc.id === targetId)) {
@@ -7864,8 +8208,8 @@ function buildErrorInfo(event) {
7864
8208
  }
7865
8209
  return {
7866
8210
  category: event.data.error_category,
7867
- title: stringValue(event.data.error_title) || "The session failed due to an internal error.",
7868
- detail: stringValue(event.data.error_detail) || stringValue(event.data.error),
8211
+ title: stringValue2(event.data.error_title) || "The session failed due to an internal error.",
8212
+ detail: stringValue2(event.data.error_detail) || stringValue2(event.data.error),
7869
8213
  retryable: Boolean(event.data.retryable)
7870
8214
  };
7871
8215
  }
@@ -7909,7 +8253,7 @@ function applyExpertFeedback(messages, type, data) {
7909
8253
  return messages;
7910
8254
  }
7911
8255
  function applyClarifyResponse(messages, data) {
7912
- const targetToolId = stringValue(data.tool_call_id);
8256
+ const targetToolId = stringValue2(data.tool_call_id);
7913
8257
  const responses = Array.isArray(data.responses) ? data.responses : void 0;
7914
8258
  if (!targetToolId || !responses) return messages;
7915
8259
  const next = [...messages];
@@ -7924,8 +8268,8 @@ function applyClarifyResponse(messages, data) {
7924
8268
  clarifyAnswers: responses.map((response) => {
7925
8269
  const row = objectValue(response) ?? {};
7926
8270
  return {
7927
- question: stringValue(row.question),
7928
- answer: stringValue(row.answer),
8271
+ question: stringValue2(row.question),
8272
+ answer: stringValue2(row.answer),
7929
8273
  is_other: Boolean(row.is_other)
7930
8274
  };
7931
8275
  })
@@ -7945,7 +8289,7 @@ function applyPolicyDenied(state, data) {
7945
8289
  ...policyMsg,
7946
8290
  content: `${policyMsg.content}
7947
8291
 
7948
- **Policy denied**: ${stringValue(data.reason) || "Action blocked by governance policy."}`,
8292
+ **Policy denied**: ${stringValue2(data.reason) || "Action blocked by governance policy."}`,
7949
8293
  status: "error"
7950
8294
  };
7951
8295
  }
@@ -7997,7 +8341,7 @@ function objectValue(value) {
7997
8341
  }
7998
8342
  return void 0;
7999
8343
  }
8000
- function stringValue(value) {
8344
+ function stringValue2(value) {
8001
8345
  return typeof value === "string" ? value : "";
8002
8346
  }
8003
8347
  function numberValue(value) {
@@ -8231,6 +8575,7 @@ function useAgentChatRuntime({
8231
8575
  }
8232
8576
  }, [adapter, sessionId]);
8233
8577
  return {
8578
+ state,
8234
8579
  session,
8235
8580
  messages: state.messages,
8236
8581
  isRunning: state.isRunning,
@@ -8282,6 +8627,7 @@ function AgentChat({
8282
8627
  const isReadOnlySession = isScheduledRunSession(runtime.session);
8283
8628
  const effectiveDisabled = disabled || isReadOnlySession;
8284
8629
  const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
8630
+ const browserState = runtime.state.browser;
8285
8631
  useEffect(() => {
8286
8632
  onMessagesChange?.(runtime.messages);
8287
8633
  }, [onMessagesChange, runtime.messages]);
@@ -8318,19 +8664,29 @@ function AgentChat({
8318
8664
  retryIndicator: runtime.retryIndicator
8319
8665
  }
8320
8666
  ) }),
8321
- /* @__PURE__ */ jsx(
8322
- WorkspacePanel,
8323
- {
8324
- adapter,
8325
- sessionId,
8326
- selectedPath: workspacePath,
8327
- onSelectedPathChange: setWorkspacePath,
8328
- collapsed: workspaceCollapsed,
8329
- onCollapsedChange: setWorkspaceCollapsed,
8330
- refreshSignal: runtime.workspaceRefreshKey,
8331
- disabled: effectiveDisabled
8332
- }
8333
- )
8667
+ /* @__PURE__ */ jsxs("div", { "data-testid": "right-stack", className: "flex min-h-0 shrink-0 flex-col", children: [
8668
+ browserState !== null && sessionId && /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 border-b border-line", children: /* @__PURE__ */ jsx(
8669
+ BrowserPane,
8670
+ {
8671
+ sessionId,
8672
+ state: browserState,
8673
+ adapter
8674
+ }
8675
+ ) }),
8676
+ /* @__PURE__ */ jsx(
8677
+ WorkspacePanel,
8678
+ {
8679
+ adapter,
8680
+ sessionId,
8681
+ selectedPath: workspacePath,
8682
+ onSelectedPathChange: setWorkspacePath,
8683
+ collapsed: workspaceCollapsed,
8684
+ onCollapsedChange: setWorkspaceCollapsed,
8685
+ refreshSignal: runtime.workspaceRefreshKey,
8686
+ disabled: effectiveDisabled
8687
+ }
8688
+ )
8689
+ ] })
8334
8690
  ] }) })
8335
8691
  }
8336
8692
  );