@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.js CHANGED
@@ -1,6 +1,6 @@
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, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
3
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, EyeIcon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, InboxIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
4
4
  import { cva } from 'class-variance-authority';
5
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';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -82,7 +82,9 @@ function Button({
82
82
  function BrowserControlBar({
83
83
  sessionId,
84
84
  hasControl,
85
- adapter
85
+ adapter,
86
+ onControlAcquired,
87
+ onControlReleased
86
88
  }) {
87
89
  const [pending, setPending] = useState(false);
88
90
  const [error, setError] = useState(null);
@@ -93,8 +95,10 @@ function BrowserControlBar({
93
95
  try {
94
96
  if (hasControl) {
95
97
  await adapter.releaseBrowserControl(sessionId);
98
+ onControlReleased?.();
96
99
  } else {
97
100
  await adapter.acquireBrowserControl(sessionId);
101
+ onControlAcquired?.();
98
102
  }
99
103
  } catch (nextError) {
100
104
  setError(nextError.message);
@@ -124,11 +128,14 @@ function BrowserControlBar({
124
128
  error && /* @__PURE__ */ jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error })
125
129
  ] });
126
130
  }
127
- function BrowserLiveView({ src }) {
131
+ function BrowserLiveView({
132
+ src,
133
+ testId = "browser-iframe"
134
+ }) {
128
135
  return /* @__PURE__ */ jsx(
129
136
  "iframe",
130
137
  {
131
- "data-testid": "browser-iframe",
138
+ "data-testid": testId,
132
139
  title: "Browser live view",
133
140
  src,
134
141
  className: "h-full w-full border-0 bg-black",
@@ -153,38 +160,373 @@ function BrowserStatusDot({
153
160
  }
154
161
  );
155
162
  }
156
- function BrowserPane({ sessionId, state, adapter }) {
157
- const liveViewUrl = useMemo(
158
- () => adapter.browserLiveViewUrl(sessionId),
159
- [adapter, sessionId]
163
+ function Dialog({
164
+ ...props
165
+ }) {
166
+ return /* @__PURE__ */ jsx(Dialog$1.Root, { "data-slot": "dialog", ...props });
167
+ }
168
+ function DialogPortal({
169
+ ...props
170
+ }) {
171
+ return /* @__PURE__ */ jsx(Dialog$1.Portal, { "data-slot": "dialog-portal", ...props });
172
+ }
173
+ function DialogOverlay({
174
+ className,
175
+ ...props
176
+ }) {
177
+ return /* @__PURE__ */ jsx(
178
+ Dialog$1.Overlay,
179
+ {
180
+ "data-slot": "dialog-overlay",
181
+ className: cn(
182
+ "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",
183
+ className
184
+ ),
185
+ ...props
186
+ }
160
187
  );
161
- const hasLiveView = state.status !== "provisioning" && state.status !== "closed";
188
+ }
189
+ function DialogContent({
190
+ className,
191
+ children,
192
+ showCloseButton = true,
193
+ ...props
194
+ }) {
195
+ return /* @__PURE__ */ jsxs(DialogPortal, { children: [
196
+ /* @__PURE__ */ jsx(DialogOverlay, {}),
197
+ /* @__PURE__ */ jsxs(
198
+ Dialog$1.Content,
199
+ {
200
+ "data-slot": "dialog-content",
201
+ className: cn(
202
+ "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",
203
+ className
204
+ ),
205
+ ...props,
206
+ children: [
207
+ children,
208
+ showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ jsxs(
209
+ Button,
210
+ {
211
+ variant: "ghost",
212
+ className: "absolute top-5 right-5 bg-secondary",
213
+ size: "icon-sm",
214
+ children: [
215
+ /* @__PURE__ */ jsx(
216
+ XIcon,
217
+ {}
218
+ ),
219
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
220
+ ]
221
+ }
222
+ ) })
223
+ ]
224
+ }
225
+ )
226
+ ] });
227
+ }
228
+ function DialogHeader({ className, ...props }) {
229
+ return /* @__PURE__ */ jsx(
230
+ "div",
231
+ {
232
+ "data-slot": "dialog-header",
233
+ className: cn("flex flex-col gap-2", className),
234
+ ...props
235
+ }
236
+ );
237
+ }
238
+ function DialogFooter({
239
+ className,
240
+ showCloseButton = false,
241
+ children,
242
+ ...props
243
+ }) {
162
244
  return /* @__PURE__ */ jsxs(
163
245
  "div",
164
246
  {
165
- "data-testid": "browser-pane",
166
- className: "flex h-full min-h-0 flex-col bg-background",
247
+ "data-slot": "dialog-footer",
248
+ className: cn(
249
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
250
+ className
251
+ ),
252
+ ...props,
167
253
  children: [
168
- /* @__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: [
169
- /* @__PURE__ */ jsx(ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
170
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Browser" }),
171
- /* @__PURE__ */ jsx(BrowserStatusDot, { status: state.status }),
172
- state.controlOwner && /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate text-amber-500", children: [
173
- state.controlOwner,
174
- " has control"
175
- ] })
176
- ] }),
177
- /* @__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." }) : /* @__PURE__ */ jsx(BrowserLiveView, { src: liveViewUrl }) }),
178
- hasLiveView && /* @__PURE__ */ jsx(
179
- BrowserControlBar,
180
- {
181
- sessionId,
182
- hasControl: state.status === "user-control",
183
- adapter
184
- }
185
- )
254
+ children,
255
+ showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", children: "Close" }) })
256
+ ]
257
+ }
258
+ );
259
+ }
260
+ function DialogTitle({
261
+ className,
262
+ ...props
263
+ }) {
264
+ return /* @__PURE__ */ jsx(
265
+ Dialog$1.Title,
266
+ {
267
+ "data-slot": "dialog-title",
268
+ className: cn(
269
+ "font-heading text-lg leading-none font-semibold tracking-wider uppercase",
270
+ className
271
+ ),
272
+ ...props
273
+ }
274
+ );
275
+ }
276
+ function DialogDescription({
277
+ className,
278
+ ...props
279
+ }) {
280
+ return /* @__PURE__ */ jsx(
281
+ Dialog$1.Description,
282
+ {
283
+ "data-slot": "dialog-description",
284
+ className: cn(
285
+ "mt-0.5 text-sm leading-relaxed text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
286
+ className
287
+ ),
288
+ ...props
289
+ }
290
+ );
291
+ }
292
+ function TooltipProvider({
293
+ delayDuration = 0,
294
+ ...props
295
+ }) {
296
+ return /* @__PURE__ */ jsx(
297
+ Tooltip$1.Provider,
298
+ {
299
+ "data-slot": "tooltip-provider",
300
+ delayDuration,
301
+ ...props
302
+ }
303
+ );
304
+ }
305
+ function Tooltip({
306
+ ...props
307
+ }) {
308
+ return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
309
+ }
310
+ function TooltipTrigger({
311
+ ...props
312
+ }) {
313
+ return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
314
+ }
315
+ function TooltipContent({
316
+ className,
317
+ sideOffset = 0,
318
+ children,
319
+ ...props
320
+ }) {
321
+ return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
322
+ Tooltip$1.Content,
323
+ {
324
+ "data-slot": "tooltip-content",
325
+ sideOffset,
326
+ className: cn(
327
+ "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",
328
+ className
329
+ ),
330
+ ...props,
331
+ children: [
332
+ children,
333
+ /* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground" })
186
334
  ]
187
335
  }
336
+ ) });
337
+ }
338
+ function BrowserPane({ sessionId, state, adapter }) {
339
+ const [fullscreenOpen, setFullscreenOpen] = useState(false);
340
+ const [openFullscreenOnControl, setOpenFullscreenOnControl] = useState(false);
341
+ const [localControlActive, setLocalControlActive] = useState(false);
342
+ const [previewSnapshot, setPreviewSnapshot] = useState(null);
343
+ const [previewLoading, setPreviewLoading] = useState(false);
344
+ const [previewError, setPreviewError] = useState(null);
345
+ const hasLiveViewAdapter = typeof adapter.browserLiveViewUrl === "function";
346
+ const hasPreviewAdapter = typeof adapter.getBrowserPreviewSnapshot === "function";
347
+ const hasControlAdapter = typeof adapter.acquireBrowserControl === "function" && typeof adapter.releaseBrowserControl === "function";
348
+ const liveViewUrl = useMemo(() => {
349
+ if (!hasLiveViewAdapter) return "";
350
+ return adapter.browserLiveViewUrl(sessionId);
351
+ }, [adapter, hasLiveViewAdapter, sessionId]);
352
+ const hasLiveView = state.status !== "provisioning" && state.status !== "closed";
353
+ const hasUserControl = localControlActive;
354
+ const canUseLiveView = hasLiveView && hasUserControl && Boolean(liveViewUrl);
355
+ const canOpenPreview = hasLiveView && (hasPreviewAdapter || canUseLiveView);
356
+ const canOpenFullscreen = canOpenPreview;
357
+ const refreshPreview = useCallback(
358
+ async (signal) => {
359
+ if (!hasPreviewAdapter || canUseLiveView) return;
360
+ setPreviewLoading(true);
361
+ setPreviewError(null);
362
+ try {
363
+ const snapshot = await adapter.getBrowserPreviewSnapshot?.(sessionId);
364
+ if (signal?.aborted) return;
365
+ setPreviewSnapshot(snapshot ?? null);
366
+ if (!snapshot) {
367
+ setPreviewError("Browser preview is unavailable.");
368
+ }
369
+ } catch (error) {
370
+ if (signal?.aborted) return;
371
+ setPreviewSnapshot(null);
372
+ setPreviewError(
373
+ error instanceof Error ? error.message : "Browser preview failed."
374
+ );
375
+ } finally {
376
+ if (!signal?.aborted) setPreviewLoading(false);
377
+ }
378
+ },
379
+ [adapter, canUseLiveView, hasPreviewAdapter, sessionId]
380
+ );
381
+ useEffect(() => {
382
+ setFullscreenOpen(false);
383
+ setOpenFullscreenOnControl(false);
384
+ setLocalControlActive(false);
385
+ setPreviewSnapshot(null);
386
+ setPreviewLoading(false);
387
+ setPreviewError(null);
388
+ }, [sessionId]);
389
+ useEffect(() => {
390
+ if (!openFullscreenOnControl) return;
391
+ if (!canUseLiveView) return;
392
+ setFullscreenOpen(true);
393
+ setOpenFullscreenOnControl(false);
394
+ }, [canUseLiveView, openFullscreenOnControl]);
395
+ const handleFullscreenOpenChange = useCallback(
396
+ (open) => {
397
+ setFullscreenOpen(open);
398
+ if (open || !hasUserControl || !hasControlAdapter) return;
399
+ setLocalControlActive(false);
400
+ void adapter.releaseBrowserControl(sessionId).catch((error) => {
401
+ console.error("Failed to release browser control", error);
402
+ });
403
+ },
404
+ [adapter, hasControlAdapter, hasUserControl, sessionId]
405
+ );
406
+ useEffect(() => {
407
+ if (!hasLiveView || canUseLiveView) {
408
+ setPreviewLoading(false);
409
+ setPreviewError(null);
410
+ return;
411
+ }
412
+ if (!hasPreviewAdapter) return;
413
+ const abort = new AbortController();
414
+ void refreshPreview(abort.signal);
415
+ const timer = window.setInterval(() => {
416
+ void refreshPreview(abort.signal);
417
+ }, 5e3);
418
+ return () => {
419
+ abort.abort();
420
+ window.clearInterval(timer);
421
+ };
422
+ }, [
423
+ canUseLiveView,
424
+ fullscreenOpen,
425
+ hasLiveView,
426
+ hasPreviewAdapter,
427
+ refreshPreview
428
+ ]);
429
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
430
+ /* @__PURE__ */ jsxs(
431
+ "div",
432
+ {
433
+ "data-testid": "browser-pane",
434
+ className: "flex h-full min-h-0 flex-col bg-background",
435
+ children: [
436
+ /* @__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: [
437
+ /* @__PURE__ */ jsx(ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
438
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Browser" }),
439
+ /* @__PURE__ */ jsx(BrowserStatusDot, { status: state.status }),
440
+ state.controlOwner && /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate text-amber-500", children: [
441
+ state.controlOwner,
442
+ " has control"
443
+ ] }),
444
+ canOpenFullscreen && /* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
445
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
446
+ "button",
447
+ {
448
+ type: "button",
449
+ "aria-label": "Maximize browser",
450
+ 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",
451
+ onClick: () => setFullscreenOpen(true),
452
+ children: /* @__PURE__ */ jsx(Maximize2Icon, { className: "size-3.5", "aria-hidden": "true" })
453
+ }
454
+ ) }),
455
+ /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: "Full screen" })
456
+ ] }) }) })
457
+ ] }),
458
+ /* @__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." }) : canUseLiveView ? /* @__PURE__ */ jsx(BrowserLiveView, { src: liveViewUrl }) : previewSnapshot ? /* @__PURE__ */ jsx(
459
+ BrowserPreviewImage,
460
+ {
461
+ src: previewSnapshot.src,
462
+ fit: "contain",
463
+ testId: "browser-preview-image"
464
+ }
465
+ ) : previewLoading ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Loading browser preview..." }) : previewError ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: previewError }) : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser preview is unavailable." }) }),
466
+ hasLiveView && hasControlAdapter && /* @__PURE__ */ jsx(
467
+ BrowserControlBar,
468
+ {
469
+ sessionId,
470
+ hasControl: localControlActive,
471
+ adapter,
472
+ onControlAcquired: () => {
473
+ setLocalControlActive(true);
474
+ setOpenFullscreenOnControl(true);
475
+ },
476
+ onControlReleased: () => setLocalControlActive(false)
477
+ }
478
+ )
479
+ ]
480
+ }
481
+ ),
482
+ /* @__PURE__ */ jsx(Dialog, { open: fullscreenOpen, onOpenChange: handleFullscreenOpenChange, children: /* @__PURE__ */ jsxs(
483
+ DialogContent,
484
+ {
485
+ "aria-describedby": void 0,
486
+ 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",
487
+ children: [
488
+ /* @__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: [
489
+ /* @__PURE__ */ jsx(ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
490
+ /* @__PURE__ */ jsx(DialogTitle, { className: "text-xs normal-case tracking-normal", children: "Browser" }),
491
+ /* @__PURE__ */ jsx(BrowserStatusDot, { status: state.status }),
492
+ state.controlOwner && /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate text-xs text-amber-500", children: [
493
+ state.controlOwner,
494
+ " has control"
495
+ ] })
496
+ ] }),
497
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-black", children: canUseLiveView ? /* @__PURE__ */ jsx(
498
+ BrowserLiveView,
499
+ {
500
+ src: liveViewUrl,
501
+ testId: "browser-fullscreen-iframe"
502
+ }
503
+ ) : previewSnapshot ? /* @__PURE__ */ jsx(
504
+ BrowserPreviewImage,
505
+ {
506
+ src: previewSnapshot.src,
507
+ fit: "contain",
508
+ testId: "browser-fullscreen-preview-image"
509
+ }
510
+ ) : previewLoading ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Loading browser preview..." }) : previewError ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: previewError }) : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser preview is unavailable." }) })
511
+ ]
512
+ }
513
+ ) })
514
+ ] });
515
+ }
516
+ function BrowserPreviewImage({
517
+ src,
518
+ fit,
519
+ testId
520
+ }) {
521
+ return /* @__PURE__ */ jsx(
522
+ "img",
523
+ {
524
+ "data-testid": testId,
525
+ src,
526
+ alt: "Browser preview",
527
+ className: `h-full w-full bg-black ${fit === "cover" ? "object-cover object-top" : "object-contain"}`,
528
+ draggable: false
529
+ }
188
530
  );
189
531
  }
190
532
  var Conversation = ({ className, ...props }) => /* @__PURE__ */ jsx(
@@ -255,66 +597,20 @@ var ConversationScrollButton = ({
255
597
  }
256
598
  );
257
599
  };
258
- function TooltipProvider({
259
- delayDuration = 0,
260
- ...props
261
- }) {
262
- return /* @__PURE__ */ jsx(
263
- Tooltip$1.Provider,
264
- {
265
- "data-slot": "tooltip-provider",
266
- delayDuration,
267
- ...props
268
- }
269
- );
270
- }
271
- function Tooltip({
272
- ...props
273
- }) {
274
- return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
275
- }
276
- function TooltipTrigger({
277
- ...props
278
- }) {
279
- return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
280
- }
281
- function TooltipContent({
282
- className,
283
- sideOffset = 0,
284
- children,
285
- ...props
286
- }) {
287
- return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
288
- Tooltip$1.Content,
289
- {
290
- "data-slot": "tooltip-content",
291
- sideOffset,
292
- className: cn(
293
- "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",
294
- className
295
- ),
296
- ...props,
297
- children: [
298
- children,
299
- /* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground" })
300
- ]
301
- }
302
- ) });
303
- }
304
- var Message = ({ className, from, ...props }) => /* @__PURE__ */ jsx(
305
- "div",
306
- {
307
- className: cn(
308
- "group flex w-full flex-col gap-2",
309
- from === "user" ? "is-user max-w-[85%]" : "is-assistant max-w-full",
310
- className
311
- ),
312
- ...props
313
- }
314
- );
315
- var MessageContent = ({
316
- children,
317
- className,
600
+ var Message = ({ className, from, ...props }) => /* @__PURE__ */ jsx(
601
+ "div",
602
+ {
603
+ className: cn(
604
+ "group flex w-full flex-col gap-2",
605
+ from === "user" ? "is-user max-w-[85%]" : "is-assistant max-w-full",
606
+ className
607
+ ),
608
+ ...props
609
+ }
610
+ );
611
+ var MessageContent = ({
612
+ children,
613
+ className,
318
614
  ...props
319
615
  }) => /* @__PURE__ */ jsx(
320
616
  "div",
@@ -674,66 +970,82 @@ function TimelineSeparator({
674
970
  );
675
971
  }
676
972
  function BrowserActivityGroup({ calls }) {
677
- const [open, setOpen] = useState(false);
678
- const latest = calls[calls.length - 1];
679
- return /* @__PURE__ */ jsxs("div", { className: "rounded-none border border-line bg-card text-xs", children: [
680
- /* @__PURE__ */ jsxs(
681
- "button",
682
- {
683
- type: "button",
684
- onClick: () => setOpen((current) => !current),
685
- className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-muted/40",
686
- children: [
687
- open ? /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3 shrink-0", "aria-hidden": "true" }),
688
- /* @__PURE__ */ jsx(ZapIcon, { className: "size-3 shrink-0 text-amber-500", "aria-hidden": "true" }),
689
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "browser" }),
690
- /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate text-muted-foreground", children: [
691
- "(",
692
- calls.length,
693
- " ",
694
- calls.length === 1 ? "action" : "actions",
695
- " - latest:",
696
- " ",
697
- latest ? summarise(latest) : "none",
698
- ")"
699
- ] })
700
- ]
701
- }
702
- ),
703
- open && /* @__PURE__ */ jsx("ul", { className: "border-t border-line py-1", children: calls.map((call) => {
973
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border border-line bg-card px-3 py-2 text-xs", children: [
974
+ /* @__PURE__ */ jsx(ZapIcon, { className: "size-3 shrink-0 text-amber-500", "aria-hidden": "true" }),
975
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 font-semibold text-foreground", children: "Browser:" }),
976
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-muted-foreground", children: calls.map((call, index) => {
704
977
  const error = stringValue(parseJson(call.result).error);
705
- return /* @__PURE__ */ jsxs(
706
- "li",
707
- {
708
- className: "flex items-center gap-2 px-6 py-1 font-mono text-[11px]",
709
- children: [
710
- error && /* @__PURE__ */ jsx(
711
- AlertCircleIcon,
712
- {
713
- "data-testid": `activity-error-${call.id}`,
714
- className: "size-3 shrink-0 text-destructive",
715
- "aria-hidden": "true"
716
- }
717
- ),
718
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: summarise(call) }),
719
- error && /* @__PURE__ */ jsx("span", { className: "ml-auto max-w-40 truncate text-destructive", children: error })
720
- ]
721
- },
722
- call.id
723
- );
978
+ return /* @__PURE__ */ jsxs("span", { children: [
979
+ index > 0 && ", ",
980
+ /* @__PURE__ */ jsx(
981
+ "span",
982
+ {
983
+ className: error ? "text-destructive" : void 0,
984
+ "data-testid": error ? `activity-error-${call.id}` : void 0,
985
+ title: error || void 0,
986
+ children: summarize(call)
987
+ }
988
+ )
989
+ ] }, call.id);
724
990
  }) })
725
991
  ] });
726
992
  }
727
- function summarise(call) {
728
- const verb = call.toolName.replace(/^browser_/, "");
993
+ function summarize(call) {
729
994
  const args = parseJson(call.args);
730
- const ref = stringValue(args.ref);
731
- const url = stringValue(args.url);
732
- const text = stringValue(args.text);
733
- if (url) return `${verb} ${url}`;
734
- if (ref) return `${verb} ${ref}`;
735
- if (text) return `${verb} "${text.slice(0, 24)}"`;
736
- return verb;
995
+ const handler = ACTION_SUMMARIES[call.toolName];
996
+ return handler ? handler(args) : call.toolName.replace(/^browser_/, "");
997
+ }
998
+ var ACTION_SUMMARIES = {
999
+ browser_navigate: (args) => `navigate to ${stringValue(args.url) || "?"}`,
1000
+ browser_get_state: () => "get state",
1001
+ browser_close: () => "close",
1002
+ browser_click: (args) => {
1003
+ const ref = stringValue(args.ref);
1004
+ if (ref) return `click ${ref}`;
1005
+ const x = asNumber(args.x);
1006
+ const y = asNumber(args.y);
1007
+ if (x !== null && y !== null) return `click (${x}, ${y})`;
1008
+ return "click";
1009
+ },
1010
+ browser_type: (args) => {
1011
+ const text = stringValue(args.text);
1012
+ const ref = stringValue(args.ref);
1013
+ const quoted = text ? `"${truncateText(text)}"` : "";
1014
+ if (!quoted) return ref ? `type into ${ref}` : "type";
1015
+ return ref ? `type ${quoted} into ${ref}` : `type ${quoted}`;
1016
+ },
1017
+ browser_press_key: (args) => {
1018
+ const keys = Array.isArray(args.keys) ? args.keys.map(String) : [];
1019
+ return keys.length ? `press ${keys.join("+")}` : "press key";
1020
+ },
1021
+ browser_scroll: (args) => {
1022
+ const x = asNumber(args.x);
1023
+ const y = asNumber(args.y);
1024
+ const dx = asNumber(args.delta_x) ?? 0;
1025
+ const dy = asNumber(args.delta_y) ?? 0;
1026
+ const delta = dx || dy ? `\u0394(${dx}, ${dy})` : "";
1027
+ const origin = x !== null && y !== null ? `at (${x}, ${y})` : "";
1028
+ return ["scroll", delta, origin].filter(Boolean).join(" ");
1029
+ },
1030
+ browser_drag: (args) => {
1031
+ const path = Array.isArray(args.path) ? args.path : [];
1032
+ if (path.length < 2) return "drag";
1033
+ const first = path[0];
1034
+ const last = path[path.length - 1];
1035
+ const point = (p) => `(${asNumber(p[0]) ?? "?"}, ${asNumber(p[1]) ?? "?"})`;
1036
+ return `drag ${point(first)} \u2192 ${point(last)}`;
1037
+ },
1038
+ browser_wait: (args) => {
1039
+ const ms = asNumber(args.ms);
1040
+ return ms === null ? "wait" : `wait ${ms}ms`;
1041
+ },
1042
+ browser_screenshot: (args) => args.annotate === true ? "screenshot (annotated)" : "screenshot"
1043
+ };
1044
+ function truncateText(text, max = 32) {
1045
+ return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
1046
+ }
1047
+ function asNumber(value) {
1048
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
737
1049
  }
738
1050
  function parseJson(raw) {
739
1051
  if (!raw) return {};
@@ -1773,135 +2085,20 @@ function WebToolBlock({ tc }) {
1773
2085
  displayText && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: displayText })
1774
2086
  ] });
1775
2087
  }
1776
- function Dialog({
1777
- ...props
1778
- }) {
1779
- return /* @__PURE__ */ jsx(Dialog$1.Root, { "data-slot": "dialog", ...props });
1780
- }
1781
- function DialogPortal({
1782
- ...props
1783
- }) {
1784
- return /* @__PURE__ */ jsx(Dialog$1.Portal, { "data-slot": "dialog-portal", ...props });
1785
- }
1786
- function DialogOverlay({
1787
- className,
1788
- ...props
1789
- }) {
1790
- return /* @__PURE__ */ jsx(
1791
- Dialog$1.Overlay,
1792
- {
1793
- "data-slot": "dialog-overlay",
1794
- className: cn(
1795
- "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",
1796
- className
1797
- ),
1798
- ...props
1799
- }
1800
- );
1801
- }
1802
- function DialogContent({
1803
- className,
1804
- children,
1805
- showCloseButton = true,
1806
- ...props
1807
- }) {
1808
- return /* @__PURE__ */ jsxs(DialogPortal, { children: [
1809
- /* @__PURE__ */ jsx(DialogOverlay, {}),
1810
- /* @__PURE__ */ jsxs(
1811
- Dialog$1.Content,
1812
- {
1813
- "data-slot": "dialog-content",
1814
- className: cn(
1815
- "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",
1816
- className
1817
- ),
1818
- ...props,
1819
- children: [
1820
- children,
1821
- showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ jsxs(
1822
- Button,
1823
- {
1824
- variant: "ghost",
1825
- className: "absolute top-5 right-5 bg-secondary",
1826
- size: "icon-sm",
1827
- children: [
1828
- /* @__PURE__ */ jsx(
1829
- XIcon,
1830
- {}
1831
- ),
1832
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
1833
- ]
1834
- }
1835
- ) })
1836
- ]
1837
- }
1838
- )
2088
+ function VisionAnalyzeBlock({ tc }) {
2089
+ let image = "";
2090
+ try {
2091
+ const args = JSON.parse(tc.args);
2092
+ const ref = String(args.image ?? args.image_url ?? args.image_path ?? "");
2093
+ image = ref.split("/").pop() ?? ref;
2094
+ } catch {
2095
+ }
2096
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm py-0.5", children: [
2097
+ /* @__PURE__ */ jsx(EyeIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
2098
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Vision Analyze" }),
2099
+ image && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: image })
1839
2100
  ] });
1840
2101
  }
1841
- function DialogHeader({ className, ...props }) {
1842
- return /* @__PURE__ */ jsx(
1843
- "div",
1844
- {
1845
- "data-slot": "dialog-header",
1846
- className: cn("flex flex-col gap-2", className),
1847
- ...props
1848
- }
1849
- );
1850
- }
1851
- function DialogFooter({
1852
- className,
1853
- showCloseButton = false,
1854
- children,
1855
- ...props
1856
- }) {
1857
- return /* @__PURE__ */ jsxs(
1858
- "div",
1859
- {
1860
- "data-slot": "dialog-footer",
1861
- className: cn(
1862
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1863
- className
1864
- ),
1865
- ...props,
1866
- children: [
1867
- children,
1868
- showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", children: "Close" }) })
1869
- ]
1870
- }
1871
- );
1872
- }
1873
- function DialogTitle({
1874
- className,
1875
- ...props
1876
- }) {
1877
- return /* @__PURE__ */ jsx(
1878
- Dialog$1.Title,
1879
- {
1880
- "data-slot": "dialog-title",
1881
- className: cn(
1882
- "font-heading text-lg leading-none font-semibold tracking-wider uppercase",
1883
- className
1884
- ),
1885
- ...props
1886
- }
1887
- );
1888
- }
1889
- function DialogDescription({
1890
- className,
1891
- ...props
1892
- }) {
1893
- return /* @__PURE__ */ jsx(
1894
- Dialog$1.Description,
1895
- {
1896
- "data-slot": "dialog-description",
1897
- className: cn(
1898
- "mt-0.5 text-sm leading-relaxed text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
1899
- className
1900
- ),
1901
- ...props
1902
- }
1903
- );
1904
- }
1905
2102
  function splitChangeLines(change) {
1906
2103
  const v = change.value;
1907
2104
  if (v === "") return [];
@@ -3839,6 +4036,8 @@ function ToolCallBlock({
3839
4036
  case "web_search":
3840
4037
  case "web_crawl":
3841
4038
  return /* @__PURE__ */ jsx(WebToolBlock, { tc });
4039
+ case "vision_analyze":
4040
+ return /* @__PURE__ */ jsx(VisionAnalyzeBlock, { tc });
3842
4041
  case "read_file":
3843
4042
  return /* @__PURE__ */ jsx(ReadFileBlock, { tc, onFileSelect });
3844
4043
  case "write_file":
@@ -5332,18 +5531,21 @@ function ChatComposerInner({
5332
5531
  [menuOpen, filteredCommands, selectedIndex, handleCommandSelect, textInput]
5333
5532
  );
5334
5533
  const handleSubmit = useCallback(
5335
- (message) => {
5534
+ async (message) => {
5336
5535
  const text = message.text.trim();
5337
- if (!text || isRunning || disabled) return;
5536
+ if (!text || disabled) return;
5338
5537
  const images = [];
5339
5538
  for (const file of message.files) {
5340
5539
  if (file.mediaType?.startsWith("image/") && file.url) {
5341
5540
  images.push({ data: file.url, mimeType: file.mediaType });
5342
5541
  }
5343
5542
  }
5344
- onSend(text, images.length > 0 ? images : void 0);
5543
+ if (isRunning) {
5544
+ await onStop();
5545
+ }
5546
+ await onSend(text, images.length > 0 ? images : void 0);
5345
5547
  },
5346
- [onSend, isRunning, disabled]
5548
+ [onSend, onStop, isRunning, disabled]
5347
5549
  );
5348
5550
  return /* @__PURE__ */ jsxs(Popover, { open: menuOpen, onOpenChange: (open) => {
5349
5551
  if (!open) {
@@ -7185,10 +7387,9 @@ function WorkspacePanel({
7185
7387
  sessionId,
7186
7388
  selectedPath,
7187
7389
  onSelectedPathChange,
7188
- collapsed = false,
7189
- onCollapsedChange,
7190
7390
  refreshSignal = 0,
7191
- disabled = false
7391
+ disabled = false,
7392
+ fillParent = false
7192
7393
  }) {
7193
7394
  const fileInputRef = useRef(null);
7194
7395
  const [entries, setEntries] = useState([]);
@@ -7389,34 +7590,17 @@ function WorkspacePanel({
7389
7590
  window.removeEventListener("mouseup", onMouseUp);
7390
7591
  };
7391
7592
  }, []);
7392
- if (collapsed) {
7393
- return /* @__PURE__ */ jsx(
7394
- "aside",
7395
- {
7396
- "data-testid": "workspace-panel",
7397
- role: "button",
7398
- tabIndex: 0,
7399
- 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",
7400
- "aria-label": "Expand workspace",
7401
- onClick: () => onCollapsedChange?.(false),
7402
- onKeyDown: (event) => {
7403
- if (event.key === "Enter" || event.key === " ") {
7404
- event.preventDefault();
7405
- onCollapsedChange?.(false);
7406
- }
7407
- },
7408
- children: /* @__PURE__ */ jsx(FolderOpenIcon, { className: "size-4 text-amber-500" })
7409
- }
7410
- );
7411
- }
7412
7593
  return /* @__PURE__ */ jsxs(
7413
7594
  "aside",
7414
7595
  {
7415
7596
  "data-testid": "workspace-panel",
7416
- className: "relative z-10 flex min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
7417
- style: { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
7597
+ className: cn(
7598
+ "relative z-10 flex h-full min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
7599
+ fillParent && "w-full"
7600
+ ),
7601
+ style: fillParent ? void 0 : { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
7418
7602
  children: [
7419
- /* @__PURE__ */ jsx(
7603
+ !fillParent && /* @__PURE__ */ jsx(
7420
7604
  "div",
7421
7605
  {
7422
7606
  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",
@@ -7442,19 +7626,6 @@ function WorkspacePanel({
7442
7626
  /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium text-foreground", children: rootName }),
7443
7627
  /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-faint", children: disabled ? "Workspace - Read-only" : "Workspace" })
7444
7628
  ] }),
7445
- /* @__PURE__ */ jsxs(Tooltip, { children: [
7446
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
7447
- Button,
7448
- {
7449
- variant: "ghost",
7450
- size: "icon-sm",
7451
- onClick: () => onCollapsedChange?.(true),
7452
- "aria-label": "Collapse workspace",
7453
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-4" })
7454
- }
7455
- ) }),
7456
- /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: "Collapse" })
7457
- ] }),
7458
7629
  /* @__PURE__ */ jsxs(Tooltip, { children: [
7459
7630
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
7460
7631
  Button,
@@ -7627,7 +7798,8 @@ var WORKSPACE_MUTATING_TOOLS = /* @__PURE__ */ new Set([
7627
7798
  "terminal",
7628
7799
  "write_file",
7629
7800
  "patch",
7630
- "execute_code"
7801
+ "execute_code",
7802
+ "browser_screenshot"
7631
7803
  ]);
7632
7804
  var AGENT_CHAT_LISTENED_EVENTS = [
7633
7805
  "user.message",
@@ -8553,7 +8725,6 @@ function AgentChat({
8553
8725
  disabled
8554
8726
  }) {
8555
8727
  const [workspacePath, setWorkspacePath] = useState(null);
8556
- const [workspaceCollapsed, setWorkspaceCollapsed] = useState(false);
8557
8728
  const runtime = useAgentChatRuntime({
8558
8729
  adapter,
8559
8730
  agentId,
@@ -8564,6 +8735,7 @@ function AgentChat({
8564
8735
  const effectiveDisabled = disabled || isReadOnlySession;
8565
8736
  const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
8566
8737
  const browserState = runtime.state.browser;
8738
+ const hasBrowserPanel = browserState !== null && sessionId;
8567
8739
  useEffect(() => {
8568
8740
  onMessagesChange?.(runtime.messages);
8569
8741
  }, [onMessagesChange, runtime.messages]);
@@ -8582,52 +8754,767 @@ function AgentChat({
8582
8754
  sessionId,
8583
8755
  onFileSelect: handleFileSelect
8584
8756
  },
8585
- children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs("section", { className: "flex flex-1 min-h-0 overflow-hidden bg-background text-sm text-foreground", children: [
8586
- /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: /* @__PURE__ */ jsx(
8587
- ChatThread,
8588
- {
8589
- sessionId,
8590
- messages: runtime.messages,
8591
- isRunning: runtime.isRunning,
8592
- isLoadingHistory: runtime.isLoadingHistory,
8593
- onSend: (content, images) => void runtime.send(content, images),
8594
- onStop: () => void runtime.stop(),
8595
- onRetry: runtime.retry,
8596
- onFileSelect: handleFileSelect,
8597
- disabled: effectiveDisabled,
8598
- disabledReason,
8599
- tokenUsage: runtime.tokenUsage,
8600
- retryIndicator: runtime.retryIndicator
8601
- }
8602
- ) }),
8603
- /* @__PURE__ */ jsxs("div", { "data-testid": "right-stack", className: "flex min-h-0 shrink-0 flex-col", children: [
8604
- browserState !== null && sessionId && /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 border-b border-line", children: /* @__PURE__ */ jsx(
8605
- BrowserPane,
8606
- {
8607
- sessionId,
8608
- state: browserState,
8609
- adapter
8610
- }
8611
- ) }),
8612
- /* @__PURE__ */ jsx(
8613
- WorkspacePanel,
8614
- {
8615
- adapter,
8616
- sessionId,
8617
- selectedPath: workspacePath,
8618
- onSelectedPathChange: setWorkspacePath,
8619
- collapsed: workspaceCollapsed,
8620
- onCollapsedChange: setWorkspaceCollapsed,
8621
- refreshSignal: runtime.workspaceRefreshKey,
8622
- disabled: effectiveDisabled
8623
- }
8624
- )
8625
- ] })
8626
- ] }) })
8757
+ children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(
8758
+ "section",
8759
+ {
8760
+ "data-testid": "agent-chat-layout",
8761
+ 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",
8762
+ style: { direction: "ltr" },
8763
+ children: [
8764
+ /* @__PURE__ */ jsx(
8765
+ "div",
8766
+ {
8767
+ "data-testid": "chat-panel",
8768
+ 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",
8769
+ style: hasBrowserPanel ? { right: 440 } : void 0,
8770
+ children: /* @__PURE__ */ jsx(
8771
+ ChatThread,
8772
+ {
8773
+ sessionId,
8774
+ messages: runtime.messages,
8775
+ isRunning: runtime.isRunning,
8776
+ isLoadingHistory: runtime.isLoadingHistory,
8777
+ onSend: (content, images) => runtime.send(content, images),
8778
+ onStop: () => runtime.stop(),
8779
+ onRetry: runtime.retry,
8780
+ onFileSelect: handleFileSelect,
8781
+ disabled: effectiveDisabled,
8782
+ disabledReason,
8783
+ tokenUsage: runtime.tokenUsage,
8784
+ retryIndicator: runtime.retryIndicator
8785
+ }
8786
+ )
8787
+ }
8788
+ ),
8789
+ /* @__PURE__ */ jsxs(
8790
+ "div",
8791
+ {
8792
+ "data-testid": "right-stack",
8793
+ className: hasBrowserPanel ? "absolute inset-y-0 flex min-h-0 flex-col overflow-hidden" : "flex min-h-0 shrink-0 flex-col",
8794
+ style: hasBrowserPanel ? { right: 0, width: 440 } : void 0,
8795
+ children: [
8796
+ hasBrowserPanel && /* @__PURE__ */ jsx(
8797
+ "div",
8798
+ {
8799
+ "data-testid": "browser-panel",
8800
+ className: "h-1/2 min-h-0 w-full overflow-hidden border-b border-line",
8801
+ children: /* @__PURE__ */ jsx(
8802
+ BrowserPane,
8803
+ {
8804
+ sessionId,
8805
+ state: browserState,
8806
+ adapter
8807
+ }
8808
+ )
8809
+ }
8810
+ ),
8811
+ /* @__PURE__ */ jsx(
8812
+ "div",
8813
+ {
8814
+ "data-testid": "workspace-panel-frame",
8815
+ className: hasBrowserPanel ? "h-1/2 min-h-0 w-full overflow-hidden" : "min-h-0",
8816
+ children: /* @__PURE__ */ jsx(
8817
+ WorkspacePanel,
8818
+ {
8819
+ adapter,
8820
+ sessionId,
8821
+ selectedPath: workspacePath,
8822
+ onSelectedPathChange: setWorkspacePath,
8823
+ refreshSignal: runtime.workspaceRefreshKey,
8824
+ disabled: effectiveDisabled,
8825
+ fillParent: Boolean(hasBrowserPanel)
8826
+ }
8827
+ )
8828
+ }
8829
+ )
8830
+ ]
8831
+ }
8832
+ )
8833
+ ]
8834
+ }
8835
+ ) })
8627
8836
  }
8628
8837
  );
8629
8838
  }
8630
8839
  var DEFAULT_LIMIT = 50;
8840
+ function requireInboxAdapter(adapter) {
8841
+ if (!adapter.listInbox || !adapter.getInboxItem || !adapter.markInboxItemRead || !adapter.acknowledgeInboxItem || !adapter.respondGovernanceInboxItem || !adapter.openInboxStream) {
8842
+ throw new Error("Inbox is not supported by this adapter.");
8843
+ }
8844
+ return adapter;
8845
+ }
8846
+ var KIND_LABEL2 = {
8847
+ input_required: "Input needed",
8848
+ action_required: "Action needed",
8849
+ task_complete: "Task complete",
8850
+ governance_gate: "Approval",
8851
+ progress_checkin: "Progress"
8852
+ };
8853
+ function kindLabel(kind) {
8854
+ return KIND_LABEL2[kind] ?? kind.replace(/[_-]+/g, " ");
8855
+ }
8856
+ function statusLabel(status) {
8857
+ return status.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
8858
+ }
8859
+ function formatRelative(value) {
8860
+ const date = new Date(value);
8861
+ if (Number.isNaN(date.getTime())) return "";
8862
+ return formatDistanceToNow(date, { addSuffix: true });
8863
+ }
8864
+ function kindIcon(kind) {
8865
+ if (kind === "input_required") return MessageSquareIcon;
8866
+ if (kind === "action_required") return ExternalLinkIcon;
8867
+ if (kind === "task_complete") return ClipboardCheckIcon;
8868
+ if (kind === "governance_gate") return ShieldAlertIcon;
8869
+ if (kind === "progress_checkin") return TimerIcon;
8870
+ return CircleDotIcon;
8871
+ }
8872
+ function sortItems(items) {
8873
+ return [...items].sort((a, b) => {
8874
+ if (a.createdAt !== b.createdAt) return a.createdAt > b.createdAt ? -1 : 1;
8875
+ return b.id - a.id;
8876
+ });
8877
+ }
8878
+ function QuestionInput({
8879
+ prompt,
8880
+ choices,
8881
+ disabled,
8882
+ value,
8883
+ onChange
8884
+ }) {
8885
+ if (choices && choices.length > 0) {
8886
+ return /* @__PURE__ */ jsxs(
8887
+ "select",
8888
+ {
8889
+ "aria-label": prompt,
8890
+ className: "h-9 w-full border border-line bg-background px-2 text-sm text-foreground outline-none focus:border-primary disabled:opacity-50",
8891
+ disabled,
8892
+ value,
8893
+ onChange: (event) => onChange(event.target.value),
8894
+ children: [
8895
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select" }),
8896
+ choices.map((choice) => /* @__PURE__ */ jsx("option", { value: choice.label, children: choice.label }, choice.label))
8897
+ ]
8898
+ }
8899
+ );
8900
+ }
8901
+ return /* @__PURE__ */ jsx(
8902
+ "input",
8903
+ {
8904
+ "aria-label": prompt,
8905
+ className: "h-9 w-full border border-line bg-background px-2 text-sm text-foreground outline-none focus:border-primary disabled:opacity-50",
8906
+ disabled,
8907
+ value,
8908
+ onChange: (event) => onChange(event.target.value)
8909
+ }
8910
+ );
8911
+ }
8912
+ function InboxDetailActions({
8913
+ item,
8914
+ adapter,
8915
+ onDeleted,
8916
+ onSessionSelect,
8917
+ children
8918
+ }) {
8919
+ const [deleting, setDeleting] = useState(false);
8920
+ async function deleteItem() {
8921
+ if (!adapter.deleteInboxItem) return;
8922
+ setDeleting(true);
8923
+ try {
8924
+ await onDeleted(item.id);
8925
+ } finally {
8926
+ setDeleting(false);
8927
+ }
8928
+ }
8929
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
8930
+ /* @__PURE__ */ jsxs(
8931
+ Button,
8932
+ {
8933
+ type: "button",
8934
+ size: "sm",
8935
+ onClick: () => onSessionSelect?.(item.sessionId),
8936
+ "aria-label": "Open session",
8937
+ children: [
8938
+ /* @__PURE__ */ jsx(ExternalLinkIcon, { className: "size-3.5" }),
8939
+ "Open session"
8940
+ ]
8941
+ }
8942
+ ),
8943
+ children,
8944
+ adapter.deleteInboxItem && /* @__PURE__ */ jsxs(
8945
+ Button,
8946
+ {
8947
+ type: "button",
8948
+ size: "sm",
8949
+ variant: "ghost",
8950
+ onClick: () => void deleteItem(),
8951
+ disabled: deleting,
8952
+ "aria-label": "Delete inbox item",
8953
+ title: "Delete inbox item",
8954
+ children: [
8955
+ /* @__PURE__ */ jsx(Trash2Icon, { className: "size-3.5" }),
8956
+ deleting ? "Deleting" : "Delete"
8957
+ ]
8958
+ }
8959
+ )
8960
+ ] });
8961
+ }
8962
+ function InputRequiredDetail({
8963
+ item,
8964
+ adapter,
8965
+ onUpdated,
8966
+ onDeleted,
8967
+ onSessionSelect
8968
+ }) {
8969
+ const questions = Array.isArray(item.payload.questions) ? item.payload.questions.map((question) => ({
8970
+ prompt: typeof question.prompt === "string" ? question.prompt : "",
8971
+ choices: Array.isArray(question.choices) ? question.choices.map((choice) => ({
8972
+ label: typeof choice.label === "string" ? choice.label : "",
8973
+ description: typeof choice.description === "string" ? choice.description : void 0
8974
+ })).filter((choice) => choice.label) : void 0
8975
+ })).filter((question) => question.prompt) : [];
8976
+ const [answers, setAnswers] = useState({});
8977
+ const [submitting, setSubmitting] = useState(false);
8978
+ const disabled = item.status !== "pending" || submitting;
8979
+ const canSubmit = questions.every((question) => answers[question.prompt]?.trim());
8980
+ async function submit() {
8981
+ const toolCallId = typeof item.payload.tool_call_id === "string" ? item.payload.tool_call_id : "";
8982
+ if (!toolCallId || !canSubmit) return;
8983
+ setSubmitting(true);
8984
+ try {
8985
+ const responses = questions.map((question) => ({
8986
+ question: question.prompt,
8987
+ answer: answers[question.prompt]?.trim() ?? "",
8988
+ is_other: false
8989
+ }));
8990
+ await adapter.submitClarifyResponse({
8991
+ sessionId: item.sessionId,
8992
+ toolCallId,
8993
+ responses
8994
+ });
8995
+ onUpdated(await adapter.getInboxItem({ itemId: item.id }));
8996
+ } finally {
8997
+ setSubmitting(false);
8998
+ }
8999
+ }
9000
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
9001
+ item.body && /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap text-sm text-muted-foreground", children: item.body }),
9002
+ questions.map((question) => /* @__PURE__ */ jsxs("label", { className: "block space-y-1.5", children: [
9003
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: question.prompt }),
9004
+ /* @__PURE__ */ jsx(
9005
+ QuestionInput,
9006
+ {
9007
+ prompt: question.prompt,
9008
+ choices: question.choices,
9009
+ disabled,
9010
+ value: answers[question.prompt] ?? "",
9011
+ onChange: (value) => setAnswers((current) => ({
9012
+ ...current,
9013
+ [question.prompt]: value
9014
+ }))
9015
+ }
9016
+ )
9017
+ ] }, question.prompt)),
9018
+ /* @__PURE__ */ jsx(
9019
+ InboxDetailActions,
9020
+ {
9021
+ item,
9022
+ adapter,
9023
+ onDeleted,
9024
+ onSessionSelect,
9025
+ children: /* @__PURE__ */ jsx(
9026
+ Button,
9027
+ {
9028
+ type: "button",
9029
+ size: "sm",
9030
+ onClick: () => void submit(),
9031
+ disabled: disabled || !canSubmit,
9032
+ "aria-label": "Submit inbox response",
9033
+ children: submitting ? "Submitting" : "Submit"
9034
+ }
9035
+ )
9036
+ }
9037
+ )
9038
+ ] });
9039
+ }
9040
+ function AckDetail({
9041
+ item,
9042
+ adapter,
9043
+ onUpdated,
9044
+ onDeleted,
9045
+ onSessionSelect
9046
+ }) {
9047
+ async function acknowledge() {
9048
+ onUpdated(await adapter.acknowledgeInboxItem({ itemId: item.id }));
9049
+ }
9050
+ const outcome = typeof item.payload.outcome === "string" ? item.payload.outcome : "";
9051
+ const duration = typeof item.payload.duration_seconds === "number" ? item.payload.duration_seconds : null;
9052
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
9053
+ outcome && /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: outcome }),
9054
+ item.body && /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap text-sm", children: item.body }),
9055
+ typeof item.payload.error === "string" && /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto bg-destructive/10 p-3 text-xs text-destructive", children: item.payload.error }),
9056
+ duration !== null && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
9057
+ "Duration: ",
9058
+ Math.round(duration / 60),
9059
+ " min (",
9060
+ duration,
9061
+ " s)"
9062
+ ] }),
9063
+ /* @__PURE__ */ jsx(
9064
+ InboxDetailActions,
9065
+ {
9066
+ item,
9067
+ adapter,
9068
+ onDeleted,
9069
+ onSessionSelect,
9070
+ children: item.status === "pending" && /* @__PURE__ */ jsxs(
9071
+ Button,
9072
+ {
9073
+ type: "button",
9074
+ size: "sm",
9075
+ onClick: () => void acknowledge(),
9076
+ "aria-label": "Acknowledge inbox item",
9077
+ children: [
9078
+ /* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5" }),
9079
+ "Acknowledge"
9080
+ ]
9081
+ }
9082
+ )
9083
+ }
9084
+ )
9085
+ ] });
9086
+ }
9087
+ function GovernanceDetail({
9088
+ item,
9089
+ adapter,
9090
+ onUpdated,
9091
+ onDeleted,
9092
+ onSessionSelect
9093
+ }) {
9094
+ async function decide(decision) {
9095
+ onUpdated(
9096
+ await adapter.respondGovernanceInboxItem({ itemId: item.id, decision })
9097
+ );
9098
+ }
9099
+ const toolName = typeof item.payload.tool_name === "string" ? item.payload.tool_name : "tool";
9100
+ const args = typeof item.payload.arguments_excerpt === "string" ? item.payload.arguments_excerpt : "";
9101
+ const reason = typeof item.payload.deny_reason === "string" ? item.payload.deny_reason : "";
9102
+ const disabled = item.status !== "pending";
9103
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
9104
+ /* @__PURE__ */ jsxs("div", { children: [
9105
+ /* @__PURE__ */ jsxs("div", { className: "text-base font-semibold text-foreground", children: [
9106
+ "Approve ",
9107
+ toolName,
9108
+ "?"
9109
+ ] }),
9110
+ reason && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: reason })
9111
+ ] }),
9112
+ args && /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto border border-line bg-muted p-3 text-xs", children: args }),
9113
+ /* @__PURE__ */ jsxs(
9114
+ InboxDetailActions,
9115
+ {
9116
+ item,
9117
+ adapter,
9118
+ onDeleted,
9119
+ onSessionSelect,
9120
+ children: [
9121
+ /* @__PURE__ */ jsx(
9122
+ Button,
9123
+ {
9124
+ type: "button",
9125
+ size: "sm",
9126
+ disabled,
9127
+ onClick: () => void decide("approve"),
9128
+ children: "Approve"
9129
+ }
9130
+ ),
9131
+ /* @__PURE__ */ jsx(
9132
+ Button,
9133
+ {
9134
+ type: "button",
9135
+ size: "sm",
9136
+ variant: "destructive",
9137
+ disabled,
9138
+ onClick: () => void decide("reject"),
9139
+ children: "Reject"
9140
+ }
9141
+ )
9142
+ ]
9143
+ }
9144
+ )
9145
+ ] });
9146
+ }
9147
+ function ActionRequiredDetail({
9148
+ item,
9149
+ adapter,
9150
+ onUpdated,
9151
+ onDeleted,
9152
+ onSessionSelect
9153
+ }) {
9154
+ const [submitting, setSubmitting] = useState(false);
9155
+ const actionType = typeof item.payload.action_type === "string" ? item.payload.action_type : "";
9156
+ const context = typeof item.payload.context === "string" ? item.payload.context : "";
9157
+ const disabled = item.status !== "pending" || submitting || !adapter.respondActionRequiredInboxItem;
9158
+ async function complete() {
9159
+ if (!adapter.respondActionRequiredInboxItem) return;
9160
+ setSubmitting(true);
9161
+ try {
9162
+ onUpdated(await adapter.respondActionRequiredInboxItem({ itemId: item.id }));
9163
+ } finally {
9164
+ setSubmitting(false);
9165
+ }
9166
+ }
9167
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
9168
+ item.body && /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap text-sm", children: item.body }),
9169
+ context && context !== item.body && /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap text-sm text-muted-foreground", children: context }),
9170
+ actionType && /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: actionType }),
9171
+ /* @__PURE__ */ jsx(
9172
+ InboxDetailActions,
9173
+ {
9174
+ item,
9175
+ adapter,
9176
+ onDeleted,
9177
+ onSessionSelect,
9178
+ children: /* @__PURE__ */ jsxs(
9179
+ Button,
9180
+ {
9181
+ type: "button",
9182
+ size: "sm",
9183
+ onClick: () => void complete(),
9184
+ disabled,
9185
+ "aria-label": "Mark action complete",
9186
+ children: [
9187
+ /* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5" }),
9188
+ submitting ? "Marking" : "I completed this"
9189
+ ]
9190
+ }
9191
+ )
9192
+ }
9193
+ )
9194
+ ] });
9195
+ }
9196
+ function ProgressDetail({
9197
+ item,
9198
+ adapter,
9199
+ onUpdated,
9200
+ onDeleted,
9201
+ onSessionSelect
9202
+ }) {
9203
+ async function acknowledge() {
9204
+ onUpdated(await adapter.acknowledgeInboxItem({ itemId: item.id }));
9205
+ }
9206
+ const rows = [];
9207
+ for (const row of [
9208
+ ["Iterations", item.payload.iterations],
9209
+ ["Last tool", item.payload.last_tool],
9210
+ ["Elapsed", item.payload.elapsed_seconds]
9211
+ ]) {
9212
+ if (row[1] !== void 0 && row[1] !== null && row[1] !== "") {
9213
+ rows.push(row);
9214
+ }
9215
+ }
9216
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
9217
+ item.body && /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap text-sm", children: item.body }),
9218
+ rows.length > 0 && /* @__PURE__ */ jsx("dl", { className: "space-y-1 text-xs text-muted-foreground", children: rows.map(([label, value]) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
9219
+ /* @__PURE__ */ jsx("dt", { className: "w-20 shrink-0", children: String(label) }),
9220
+ /* @__PURE__ */ jsx("dd", { className: "min-w-0", children: String(value) })
9221
+ ] }, String(label))) }),
9222
+ /* @__PURE__ */ jsx(
9223
+ InboxDetailActions,
9224
+ {
9225
+ item,
9226
+ adapter,
9227
+ onDeleted,
9228
+ onSessionSelect,
9229
+ children: item.status === "pending" && /* @__PURE__ */ jsxs(
9230
+ Button,
9231
+ {
9232
+ type: "button",
9233
+ size: "sm",
9234
+ onClick: () => void acknowledge(),
9235
+ "aria-label": "Acknowledge inbox item",
9236
+ children: [
9237
+ /* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5" }),
9238
+ "Acknowledge"
9239
+ ]
9240
+ }
9241
+ )
9242
+ }
9243
+ )
9244
+ ] });
9245
+ }
9246
+ function InboxDetail({
9247
+ item,
9248
+ adapter,
9249
+ onUpdated,
9250
+ onDeleted,
9251
+ onSessionSelect
9252
+ }) {
9253
+ const Icon = kindIcon(item.kind);
9254
+ return /* @__PURE__ */ jsxs("section", { className: "min-w-0 flex-1 overflow-y-auto p-6", children: [
9255
+ /* @__PURE__ */ jsxs("div", { className: "mb-5 flex min-w-0 items-start gap-3", children: [
9256
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex size-8 shrink-0 items-center justify-center border border-line text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { className: "size-4" }) }),
9257
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
9258
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
9259
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: kindLabel(item.kind) }),
9260
+ /* @__PURE__ */ jsx(Badge, { variant: item.status === "pending" ? "default" : "secondary", children: statusLabel(item.status) })
9261
+ ] }),
9262
+ /* @__PURE__ */ jsx("h2", { className: "mt-2 text-lg font-semibold leading-snug text-foreground", children: item.title })
9263
+ ] })
9264
+ ] }),
9265
+ item.kind === "input_required" ? /* @__PURE__ */ jsx(
9266
+ InputRequiredDetail,
9267
+ {
9268
+ item,
9269
+ adapter,
9270
+ onUpdated,
9271
+ onDeleted,
9272
+ onSessionSelect
9273
+ }
9274
+ ) : item.kind === "action_required" ? /* @__PURE__ */ jsx(
9275
+ ActionRequiredDetail,
9276
+ {
9277
+ item,
9278
+ adapter,
9279
+ onUpdated,
9280
+ onDeleted,
9281
+ onSessionSelect
9282
+ }
9283
+ ) : item.kind === "governance_gate" ? /* @__PURE__ */ jsx(
9284
+ GovernanceDetail,
9285
+ {
9286
+ item,
9287
+ adapter,
9288
+ onUpdated,
9289
+ onDeleted,
9290
+ onSessionSelect
9291
+ }
9292
+ ) : item.kind === "progress_checkin" ? /* @__PURE__ */ jsx(
9293
+ ProgressDetail,
9294
+ {
9295
+ item,
9296
+ adapter,
9297
+ onUpdated,
9298
+ onDeleted,
9299
+ onSessionSelect
9300
+ }
9301
+ ) : /* @__PURE__ */ jsx(
9302
+ AckDetail,
9303
+ {
9304
+ item,
9305
+ adapter,
9306
+ onUpdated,
9307
+ onDeleted,
9308
+ onSessionSelect
9309
+ }
9310
+ )
9311
+ ] });
9312
+ }
9313
+ function InboxPanel({
9314
+ adapter,
9315
+ title = "Inbox",
9316
+ selectedId,
9317
+ onSelectedIdChange,
9318
+ onSessionSelect,
9319
+ hideHeader = false,
9320
+ limit = DEFAULT_LIMIT
9321
+ }) {
9322
+ const inboxAdapter = useMemo(() => requireInboxAdapter(adapter), [adapter]);
9323
+ const [items, setItems] = useState([]);
9324
+ const [nextCursor, setNextCursor] = useState(null);
9325
+ const [internalSelectedId, setInternalSelectedId] = useState(null);
9326
+ const [loading, setLoading] = useState(false);
9327
+ const [error, setError] = useState(null);
9328
+ const requestId = useRef(0);
9329
+ const selectedItemId = selectedId ?? internalSelectedId;
9330
+ const selectedItem = useMemo(
9331
+ () => items.find((item) => item.id === selectedItemId) ?? null,
9332
+ [items, selectedItemId]
9333
+ );
9334
+ const applyItem = useCallback((nextItem) => {
9335
+ setItems(
9336
+ (current) => sortItems(
9337
+ current.map((item) => item.id === nextItem.id ? nextItem : item)
9338
+ )
9339
+ );
9340
+ }, []);
9341
+ const selectItem = useCallback(
9342
+ async (itemId) => {
9343
+ if (selectedId === void 0) setInternalSelectedId(itemId);
9344
+ onSelectedIdChange?.(itemId);
9345
+ const item = await inboxAdapter.getInboxItem({ itemId });
9346
+ applyItem(item);
9347
+ if (!item.readAt) {
9348
+ applyItem(await inboxAdapter.markInboxItemRead({ itemId }));
9349
+ }
9350
+ },
9351
+ [applyItem, inboxAdapter, onSelectedIdChange, selectedId]
9352
+ );
9353
+ const load = useCallback(
9354
+ async (cursor) => {
9355
+ const id = ++requestId.current;
9356
+ setLoading(true);
9357
+ setError(null);
9358
+ try {
9359
+ const response = await inboxAdapter.listInbox({
9360
+ cursor: cursor ?? void 0,
9361
+ limit
9362
+ });
9363
+ if (id !== requestId.current) return;
9364
+ setItems(
9365
+ (current) => sortItems(cursor ? [...current, ...response.items] : response.items)
9366
+ );
9367
+ setNextCursor(response.nextCursor);
9368
+ } catch (err) {
9369
+ if (id === requestId.current) {
9370
+ setError(err instanceof Error ? err.message : "Failed to load inbox");
9371
+ }
9372
+ } finally {
9373
+ if (id === requestId.current) setLoading(false);
9374
+ }
9375
+ },
9376
+ [inboxAdapter, limit]
9377
+ );
9378
+ useEffect(() => {
9379
+ void load(null);
9380
+ }, [load]);
9381
+ useEffect(() => {
9382
+ const stream = inboxAdapter.openInboxStream();
9383
+ stream.addEventListener("item", (event) => {
9384
+ const payload = JSON.parse(event.data);
9385
+ if (typeof payload.item_id !== "number") return;
9386
+ void inboxAdapter.getInboxItem({ itemId: payload.item_id }).then(applyItem);
9387
+ });
9388
+ return () => stream.close();
9389
+ }, [applyItem, inboxAdapter]);
9390
+ function updateSelectedItem(item) {
9391
+ applyItem(item);
9392
+ }
9393
+ const deleteItem = useCallback(
9394
+ async (itemId) => {
9395
+ if (!inboxAdapter.deleteInboxItem) return;
9396
+ await inboxAdapter.deleteInboxItem({ itemId });
9397
+ setItems((current) => current.filter((item) => item.id !== itemId));
9398
+ if (selectedItemId === itemId) {
9399
+ if (selectedId === void 0) setInternalSelectedId(null);
9400
+ onSelectedIdChange?.(null);
9401
+ }
9402
+ },
9403
+ [inboxAdapter, onSelectedIdChange, selectedId, selectedItemId]
9404
+ );
9405
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 min-w-0 bg-background text-foreground", children: [
9406
+ /* @__PURE__ */ jsxs("aside", { className: "flex w-80 min-w-72 max-w-sm shrink-0 flex-col border-r border-line", children: [
9407
+ !hideHeader && /* @__PURE__ */ jsxs("div", { className: "flex min-h-14 items-center gap-2 border-b border-line px-4", children: [
9408
+ /* @__PURE__ */ jsx(InboxIcon, { className: "size-4 text-muted-foreground" }),
9409
+ /* @__PURE__ */ jsx("h1", { className: "font-semibold", children: title })
9410
+ ] }),
9411
+ /* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto", children: [
9412
+ error && /* @__PURE__ */ jsx("div", { className: "px-4 py-3 text-sm text-destructive", children: error }),
9413
+ !error && items.length === 0 && !loading && /* @__PURE__ */ jsx("div", { className: "px-4 py-8 text-sm text-muted-foreground", children: "No inbox items" }),
9414
+ items.map((item) => {
9415
+ const Icon = kindIcon(item.kind);
9416
+ const selected = item.id === selectedItemId;
9417
+ return /* @__PURE__ */ jsxs(
9418
+ "button",
9419
+ {
9420
+ type: "button",
9421
+ "aria-label": `Open inbox item ${item.title}`,
9422
+ className: cn(
9423
+ "flex w-full min-w-0 gap-3 border-l-2 px-3 py-3 text-left transition-colors hover:bg-input",
9424
+ selected ? "border-l-primary bg-line text-foreground" : "border-l-transparent text-subtle",
9425
+ !item.readAt && "font-medium text-foreground"
9426
+ ),
9427
+ onClick: () => void selectItem(item.id),
9428
+ children: [
9429
+ /* @__PURE__ */ jsx(Icon, { className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }),
9430
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
9431
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center justify-between gap-2", children: [
9432
+ /* @__PURE__ */ jsx("span", { className: "truncate text-xs uppercase tracking-wide text-muted-foreground", children: kindLabel(item.kind) }),
9433
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatRelative(item.createdAt) })
9434
+ ] }),
9435
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block truncate text-sm", children: item.title }),
9436
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block text-xs text-muted-foreground", children: statusLabel(item.status) })
9437
+ ] })
9438
+ ]
9439
+ },
9440
+ item.id
9441
+ );
9442
+ }),
9443
+ nextCursor && /* @__PURE__ */ jsx(
9444
+ "button",
9445
+ {
9446
+ type: "button",
9447
+ className: "w-full px-3 py-3 text-sm text-muted-foreground hover:bg-input",
9448
+ onClick: () => void load(nextCursor),
9449
+ disabled: loading,
9450
+ children: loading ? "Loading" : "Load more"
9451
+ }
9452
+ )
9453
+ ] })
9454
+ ] }),
9455
+ selectedItem ? /* @__PURE__ */ jsx(
9456
+ InboxDetail,
9457
+ {
9458
+ item: selectedItem,
9459
+ adapter: inboxAdapter,
9460
+ onUpdated: updateSelectedItem,
9461
+ onDeleted: deleteItem,
9462
+ onSessionSelect
9463
+ }
9464
+ ) : /* @__PURE__ */ jsx("main", { className: "flex min-w-0 flex-1 items-center justify-center px-4 text-sm text-muted-foreground", children: "Select an item" })
9465
+ ] });
9466
+ }
9467
+ function useInboxUnreadCount(adapter) {
9468
+ const [unreadCount, setUnreadCount] = useState(0);
9469
+ const [hasLoaded, setHasLoaded] = useState(false);
9470
+ const [error, setError] = useState(null);
9471
+ useEffect(() => {
9472
+ let cancelled = false;
9473
+ let stream = null;
9474
+ if (!adapter.listInbox || !adapter.openInboxStream) {
9475
+ setError("Inbox is not supported by this adapter.");
9476
+ setHasLoaded(true);
9477
+ return () => {
9478
+ cancelled = true;
9479
+ };
9480
+ }
9481
+ adapter.listInbox({ status: "pending", limit: 200 }).then((response) => {
9482
+ if (cancelled) return;
9483
+ setUnreadCount(response.items.filter((item) => !item.readAt).length);
9484
+ setHasLoaded(true);
9485
+ }).catch((err) => {
9486
+ if (cancelled) return;
9487
+ setError(err instanceof Error ? err.message : "Failed to load inbox");
9488
+ setHasLoaded(true);
9489
+ });
9490
+ try {
9491
+ stream = adapter.openInboxStream();
9492
+ stream.addEventListener("snapshot", (event) => {
9493
+ if (cancelled) return;
9494
+ const payload = JSON.parse(event.data);
9495
+ const ids = Array.isArray(payload.unread_ids) ? payload.unread_ids : [];
9496
+ setUnreadCount(ids.length);
9497
+ setHasLoaded(true);
9498
+ });
9499
+ stream.addEventListener("item", () => {
9500
+ if (cancelled) return;
9501
+ setUnreadCount((count) => count + 1);
9502
+ setHasLoaded(true);
9503
+ });
9504
+ stream.onerror = () => {
9505
+ if (!cancelled) setError("Inbox stream disconnected.");
9506
+ };
9507
+ } catch (err) {
9508
+ setError(err instanceof Error ? err.message : "Failed to open inbox stream");
9509
+ }
9510
+ return () => {
9511
+ cancelled = true;
9512
+ stream?.close();
9513
+ };
9514
+ }, [adapter]);
9515
+ return { unreadCount, hasLoaded, error };
9516
+ }
9517
+ var DEFAULT_LIMIT2 = 50;
8631
9518
  var DEFAULT_POLL_INTERVAL_MS = 3e4;
8632
9519
  function fingerprint(items) {
8633
9520
  return items.map(
@@ -8642,7 +9529,7 @@ function formatKind(value) {
8642
9529
  if (!value) return "Scheduled";
8643
9530
  return value.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
8644
9531
  }
8645
- function formatRelative(value, label) {
9532
+ function formatRelative2(value, label) {
8646
9533
  if (!value) return null;
8647
9534
  const date = new Date(value);
8648
9535
  if (Number.isNaN(date.getTime())) return null;
@@ -8692,10 +9579,10 @@ function ScheduledWorkRow({
8692
9579
  const lastSessionId = item.lastSessionId ?? null;
8693
9580
  const meta = [
8694
9581
  scheduleDisplay(item),
8695
- formatRelative(item.nextRunAt, "Next"),
8696
- formatRelative(item.lastRunAt, "Last"),
9582
+ formatRelative2(item.nextRunAt, "Next"),
9583
+ formatRelative2(item.lastRunAt, "Last"),
8697
9584
  formatRuns(item.runCount),
8698
- item.expiresAt ? formatRelative(item.expiresAt, "Expires") : null
9585
+ item.expiresAt ? formatRelative2(item.expiresAt, "Expires") : null
8699
9586
  ].filter(Boolean);
8700
9587
  const openLastRun = () => {
8701
9588
  if (lastSessionId) onOpenLastRun(lastSessionId);
@@ -8775,7 +9662,7 @@ function ScheduledWorkPanel({
8775
9662
  adapter,
8776
9663
  agentId,
8777
9664
  title = "Scheduled Work",
8778
- limit = DEFAULT_LIMIT,
9665
+ limit = DEFAULT_LIMIT2,
8779
9666
  status = "active",
8780
9667
  hideHeader = false,
8781
9668
  pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
@@ -9295,6 +10182,6 @@ function SessionTreePanel({
9295
10182
  ] });
9296
10183
  }
9297
10184
 
9298
- export { AgentChat, AgentChatAdapterProvider, MessageResponse, ScheduledWorkPanel, SessionTreePanel, useAgentChatAdapterContext, useAgentChatRuntime };
10185
+ export { AgentChat, AgentChatAdapterProvider, InboxPanel, MessageResponse, ScheduledWorkPanel, SessionTreePanel, useAgentChatAdapterContext, useAgentChatRuntime, useInboxUnreadCount };
9299
10186
  //# sourceMappingURL=index.js.map
9300
10187
  //# sourceMappingURL=index.js.map