@invergent/agent-chat-react 2.0.9 → 2.1.3

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, useEffect, useCallback, useMemo, useState, lazy, useContext, Fragment as Fragment$1, Suspense, Children } from 'react';
3
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, ChevronRightIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, FolderOpenIcon, Loader2Icon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, CheckIcon, CopyIcon, TerminalIcon, SparklesIcon, ClockIcon, PlusIcon, CloudIcon, HardDriveIcon, PaperclipIcon, GlobeIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, SearchIcon, GitBranchIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, EyeIcon, ListTodoIcon, ArrowUpIcon, XCircleIcon, CheckCircle2Icon, Code, CircleCheckIcon, CircleXIcon, SkullIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
3
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, ChevronRightIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, FolderOpenIcon, Loader2Icon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, CheckIcon, CopyIcon, TerminalIcon, SparklesIcon, ClockIcon, PlusIcon, CloudIcon, HardDriveIcon, PaperclipIcon, GlobeIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, SearchIcon, ThumbsUpIcon, ThumbsDownIcon, CircleCheckIcon, GitBranchIcon, ActivityIcon, EyeIcon, ListTodoIcon, ArrowUpIcon, ListIcon, PenLineIcon, ArrowRight, BookOpenIcon, FileEditIcon, WrenchIcon, FileCodeIcon, CodeIcon, FileArchiveIcon, FileVideoIcon, FileAudioIcon, FileImageIcon, FileChartLineIcon, FileSpreadsheetIcon, XCircleIcon, CheckCircle2Icon, Code, CircleXIcon, SkullIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
4
4
  import { cva } from 'class-variance-authority';
5
5
  import { Collapsible as Collapsible$1, Slot, Tooltip as Tooltip$1, Progress as Progress$1, Tabs as Tabs$1, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Popover as Popover$1 } from 'radix-ui';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -79,87 +79,6 @@ function Button({
79
79
  }
80
80
  );
81
81
  }
82
- function BrowserControlBar({
83
- sessionId,
84
- hasControl,
85
- adapter,
86
- onControlAcquired,
87
- onControlReleased
88
- }) {
89
- const [pending, setPending] = useState(false);
90
- const [error, setError] = useState(null);
91
- async function toggleControl() {
92
- if (!sessionId || pending) return;
93
- setPending(true);
94
- setError(null);
95
- try {
96
- if (hasControl) {
97
- await adapter.releaseBrowserControl(sessionId);
98
- onControlReleased?.();
99
- } else {
100
- await adapter.acquireBrowserControl(sessionId);
101
- onControlAcquired?.();
102
- }
103
- } catch (nextError) {
104
- setError(nextError.message);
105
- } finally {
106
- setPending(false);
107
- }
108
- }
109
- return /* @__PURE__ */ jsxs("div", { className: "flex min-h-11 items-center gap-2 border-t border-line bg-card px-3 py-2", children: [
110
- /* @__PURE__ */ jsxs(
111
- Button,
112
- {
113
- type: "button",
114
- size: "xs",
115
- variant: hasControl ? "secondary" : "default",
116
- disabled: !sessionId || pending,
117
- onClick: () => void toggleControl(),
118
- children: [
119
- hasControl ? /* @__PURE__ */ jsx(RotateCcwIcon, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(MousePointer2Icon, { className: "size-3", "aria-hidden": "true" }),
120
- hasControl ? "Return control" : "Take control"
121
- ]
122
- }
123
- ),
124
- /* @__PURE__ */ jsxs("span", { className: "ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
125
- /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-red-500", "aria-hidden": "true" }),
126
- "Live"
127
- ] }),
128
- error && /* @__PURE__ */ jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error })
129
- ] });
130
- }
131
- function BrowserLiveView({
132
- src,
133
- testId = "browser-iframe"
134
- }) {
135
- return /* @__PURE__ */ jsx(
136
- "iframe",
137
- {
138
- "data-testid": testId,
139
- title: "Browser live view",
140
- src,
141
- className: "h-full w-full border-0 bg-black",
142
- allow: "clipboard-read; clipboard-write"
143
- }
144
- );
145
- }
146
- function BrowserStatusDot({
147
- status
148
- }) {
149
- return /* @__PURE__ */ jsx(
150
- "span",
151
- {
152
- "aria-hidden": "true",
153
- className: cn(
154
- "inline-block size-2 shrink-0 rounded-full",
155
- status === "live" && "bg-emerald-500",
156
- status === "user-control" && "bg-amber-500",
157
- status === "provisioning" && "animate-pulse bg-sky-500",
158
- status === "closed" && "bg-muted-foreground"
159
- )
160
- }
161
- );
162
- }
163
82
  function Dialog({
164
83
  ...props
165
84
  }) {
@@ -289,6 +208,194 @@ function DialogDescription({
289
208
  }
290
209
  );
291
210
  }
211
+ function ConfirmDialog({
212
+ open,
213
+ title,
214
+ description,
215
+ confirmLabel = "Confirm",
216
+ confirmIcon,
217
+ variant = "destructive",
218
+ confirmDisabled = false,
219
+ onConfirm,
220
+ onCancel
221
+ }) {
222
+ const [loading, setLoading] = useState(false);
223
+ const handleConfirm = async () => {
224
+ setLoading(true);
225
+ try {
226
+ await onConfirm();
227
+ } finally {
228
+ setLoading(false);
229
+ }
230
+ };
231
+ return /* @__PURE__ */ jsx(
232
+ Dialog,
233
+ {
234
+ open,
235
+ onOpenChange: (next) => {
236
+ if (!next && !loading) onCancel();
237
+ },
238
+ children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
239
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
240
+ /* @__PURE__ */ jsx(DialogTitle, { children: title }),
241
+ /* @__PURE__ */ jsx(DialogDescription, { children: description })
242
+ ] }),
243
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
244
+ /* @__PURE__ */ jsx(Button, { variant: "outline", disabled: loading, onClick: onCancel, children: "Cancel" }),
245
+ /* @__PURE__ */ jsxs(
246
+ Button,
247
+ {
248
+ variant,
249
+ disabled: loading || confirmDisabled,
250
+ onClick: () => void handleConfirm(),
251
+ children: [
252
+ loading ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 size-3.5 animate-spin", "aria-hidden": "true" }) : confirmIcon,
253
+ confirmLabel
254
+ ]
255
+ }
256
+ )
257
+ ] })
258
+ ] })
259
+ }
260
+ );
261
+ }
262
+ function BrowserControlBar({
263
+ sessionId,
264
+ hasControl,
265
+ adapter,
266
+ onControlAcquired,
267
+ onControlReleased,
268
+ onClose
269
+ }) {
270
+ const [pending, setPending] = useState(false);
271
+ const [confirmOpen, setConfirmOpen] = useState(false);
272
+ const [error, setError] = useState(null);
273
+ async function toggleControl() {
274
+ if (!sessionId || pending) return;
275
+ setPending(true);
276
+ setError(null);
277
+ try {
278
+ if (hasControl) {
279
+ await adapter.releaseBrowserControl(sessionId);
280
+ onControlReleased?.();
281
+ } else {
282
+ await adapter.acquireBrowserControl(sessionId);
283
+ onControlAcquired?.();
284
+ }
285
+ } catch (nextError) {
286
+ setError(nextError.message);
287
+ } finally {
288
+ setPending(false);
289
+ }
290
+ }
291
+ async function handleConfirmClose() {
292
+ setError(null);
293
+ try {
294
+ if (hasControl) {
295
+ try {
296
+ await adapter.releaseBrowserControl(sessionId);
297
+ onControlReleased?.();
298
+ } catch (releaseError) {
299
+ console.error(
300
+ "Failed to release browser control before close",
301
+ releaseError
302
+ );
303
+ }
304
+ }
305
+ if (adapter.closeBrowserSession) {
306
+ await adapter.closeBrowserSession(sessionId);
307
+ }
308
+ setConfirmOpen(false);
309
+ onClose?.();
310
+ } catch (nextError) {
311
+ setError(nextError.message);
312
+ throw nextError;
313
+ }
314
+ }
315
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-11 items-center gap-2 border-t border-line bg-card px-3 py-2", children: [
316
+ /* @__PURE__ */ jsxs(
317
+ Button,
318
+ {
319
+ type: "button",
320
+ size: "xs",
321
+ variant: hasControl ? "secondary" : "default",
322
+ disabled: !sessionId || pending,
323
+ onClick: () => void toggleControl(),
324
+ children: [
325
+ hasControl ? /* @__PURE__ */ jsx(RotateCcwIcon, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(MousePointer2Icon, { className: "size-3", "aria-hidden": "true" }),
326
+ hasControl ? "Return control" : "Take control"
327
+ ]
328
+ }
329
+ ),
330
+ onClose && /* @__PURE__ */ jsxs(
331
+ Button,
332
+ {
333
+ type: "button",
334
+ size: "xs",
335
+ variant: "secondary",
336
+ disabled: !sessionId,
337
+ onClick: () => {
338
+ setError(null);
339
+ setConfirmOpen(true);
340
+ },
341
+ children: [
342
+ /* @__PURE__ */ jsx(XIcon, { className: "size-3", "aria-hidden": "true" }),
343
+ "Close"
344
+ ]
345
+ }
346
+ ),
347
+ /* @__PURE__ */ jsxs("span", { className: "ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
348
+ /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-red-500", "aria-hidden": "true" }),
349
+ "Live"
350
+ ] }),
351
+ error && /* @__PURE__ */ jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error }),
352
+ /* @__PURE__ */ jsx(
353
+ ConfirmDialog,
354
+ {
355
+ open: confirmOpen,
356
+ title: "Close browser session?",
357
+ description: adapter.closeBrowserSession ? "This permanently shuts down the browser sandbox for this session. The agent will lose access to the page until it re-opens a browser. This cannot be undone." : "This hides the browser panel. The sandbox stays running so the agent can keep using it.",
358
+ confirmLabel: "Close browser",
359
+ variant: "destructive",
360
+ confirmIcon: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5", "aria-hidden": "true" }),
361
+ onConfirm: handleConfirmClose,
362
+ onCancel: () => setConfirmOpen(false)
363
+ }
364
+ )
365
+ ] });
366
+ }
367
+ function BrowserLiveView({
368
+ src,
369
+ testId = "browser-iframe"
370
+ }) {
371
+ return /* @__PURE__ */ jsx(
372
+ "iframe",
373
+ {
374
+ "data-testid": testId,
375
+ title: "Browser live view",
376
+ src,
377
+ className: "h-full w-full border-0 bg-black",
378
+ allow: "clipboard-read; clipboard-write"
379
+ }
380
+ );
381
+ }
382
+ function BrowserStatusDot({
383
+ status
384
+ }) {
385
+ return /* @__PURE__ */ jsx(
386
+ "span",
387
+ {
388
+ "aria-hidden": "true",
389
+ className: cn(
390
+ "inline-block size-2 shrink-0 rounded-full",
391
+ status === "live" && "bg-emerald-500",
392
+ status === "user-control" && "bg-amber-500",
393
+ status === "provisioning" && "animate-pulse bg-sky-500",
394
+ status === "closed" && "bg-muted-foreground"
395
+ )
396
+ }
397
+ );
398
+ }
292
399
  function TooltipProvider({
293
400
  delayDuration = 0,
294
401
  ...props
@@ -335,7 +442,12 @@ function TooltipContent({
335
442
  }
336
443
  ) });
337
444
  }
338
- function BrowserPane({ sessionId, state, adapter }) {
445
+ function BrowserPane({
446
+ sessionId,
447
+ state,
448
+ adapter,
449
+ onClose
450
+ }) {
339
451
  const [fullscreenOpen, setFullscreenOpen] = useState(false);
340
452
  const [openFullscreenOnControl, setOpenFullscreenOnControl] = useState(false);
341
453
  const [localControlActive, setLocalControlActive] = useState(false);
@@ -395,14 +507,19 @@ function BrowserPane({ sessionId, state, adapter }) {
395
507
  const handleFullscreenOpenChange = useCallback(
396
508
  (open) => {
397
509
  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
510
  },
404
- [adapter, hasControlAdapter, hasUserControl, sessionId]
511
+ []
405
512
  );
513
+ useEffect(() => {
514
+ if (!canUseLiveView || !hasControlAdapter) return;
515
+ const handle = window.setInterval(() => {
516
+ void adapter.acquireBrowserControl(sessionId).catch((error) => {
517
+ console.error("Failed to refresh browser control", error);
518
+ setLocalControlActive(false);
519
+ });
520
+ }, 25e3);
521
+ return () => window.clearInterval(handle);
522
+ }, [adapter, canUseLiveView, hasControlAdapter, sessionId]);
406
523
  useEffect(() => {
407
524
  if (!hasLiveView || canUseLiveView) {
408
525
  setPreviewLoading(false);
@@ -473,7 +590,8 @@ function BrowserPane({ sessionId, state, adapter }) {
473
590
  setLocalControlActive(true);
474
591
  setOpenFullscreenOnControl(true);
475
592
  },
476
- onControlReleased: () => setLocalControlActive(false)
593
+ onControlReleased: () => setLocalControlActive(false),
594
+ onClose
477
595
  }
478
596
  )
479
597
  ]
@@ -3480,7 +3598,7 @@ function buildAnswer(q, sel) {
3480
3598
  if (!choice) return null;
3481
3599
  return { question: q.prompt, answer: choice.label, is_other: false };
3482
3600
  }
3483
- function ClarifyToolBlock({ tc }) {
3601
+ function AskUserQuestionToolBlock({ tc }) {
3484
3602
  const { adapter, sessionId } = useAgentChatAdapterContext();
3485
3603
  const args = useMemo(() => parseArgs(tc.args), [tc.args]);
3486
3604
  const questions = args?.questions ?? [];
@@ -3490,7 +3608,7 @@ function ClarifyToolBlock({ tc }) {
3490
3608
  );
3491
3609
  const [submitting, setSubmitting] = useState(false);
3492
3610
  const [error, setError] = useState(null);
3493
- const locked = tc.clarifyAnswers !== void 0 || tc.status !== "running";
3611
+ const locked = tc.askUserQuestionAnswers !== void 0 || tc.status !== "running";
3494
3612
  useEffect(() => {
3495
3613
  if (active >= questions.length && questions.length > 0) setActive(0);
3496
3614
  }, [active, questions.length]);
@@ -3518,7 +3636,7 @@ function ClarifyToolBlock({ tc }) {
3518
3636
  setError(null);
3519
3637
  setSubmitting(true);
3520
3638
  try {
3521
- await adapter.submitClarifyResponse({
3639
+ await adapter.submitAskUserQuestionResponse({
3522
3640
  sessionId,
3523
3641
  toolCallId: tc.id,
3524
3642
  responses: answers
@@ -3551,10 +3669,10 @@ function ClarifyToolBlock({ tc }) {
3551
3669
  return () => window.removeEventListener("keydown", handler);
3552
3670
  }, [handleCancel, handleSubmit, locked]);
3553
3671
  if (questions.length === 0) {
3554
- return /* @__PURE__ */ jsx("div", { className: "rounded-md border border-destructive/50 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: "clarify: no questions provided" });
3672
+ return /* @__PURE__ */ jsx("div", { className: "rounded-md border border-destructive/50 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: "ask_user_question: no questions provided" });
3555
3673
  }
3556
3674
  if (locked) {
3557
- return /* @__PURE__ */ jsx(ClarifyLocked, { tc, questions });
3675
+ return /* @__PURE__ */ jsx(AskUserQuestionLocked, { tc, questions });
3558
3676
  }
3559
3677
  const current = questions[active];
3560
3678
  const currentSel = selections[active] ?? emptySelection();
@@ -3566,7 +3684,7 @@ function ClarifyToolBlock({ tc }) {
3566
3684
  "max-w-2xl"
3567
3685
  ),
3568
3686
  role: "group",
3569
- "aria-label": "Clarifying questions",
3687
+ "aria-label": "Questions for the user",
3570
3688
  children: [
3571
3689
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 pt-2", children: [
3572
3690
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3 overflow-x-auto text-sm", children: questions.map((_, i) => {
@@ -3730,11 +3848,11 @@ function Radio({ selected }) {
3730
3848
  }
3731
3849
  );
3732
3850
  }
3733
- function ClarifyLocked({
3851
+ function AskUserQuestionLocked({
3734
3852
  tc,
3735
3853
  questions
3736
3854
  }) {
3737
- const answers = tc.clarifyAnswers;
3855
+ const answers = tc.askUserQuestionAnswers;
3738
3856
  const byPrompt = new Map(
3739
3857
  answers?.map((a) => [a.question, a]) ?? []
3740
3858
  );
@@ -4142,8 +4260,8 @@ function ToolCallBlock({
4142
4260
  return /* @__PURE__ */ jsx(SkillViewBlock, { tc });
4143
4261
  case "skill_manage":
4144
4262
  return /* @__PURE__ */ jsx(SkillManageToolBlock, { tc });
4145
- case "clarify":
4146
- return /* @__PURE__ */ jsx(ClarifyToolBlock, { tc });
4263
+ case "ask_user_question":
4264
+ return /* @__PURE__ */ jsx(AskUserQuestionToolBlock, { tc });
4147
4265
  case "create_artifact":
4148
4266
  return /* @__PURE__ */ jsx(ArtifactToolBlock, { tc, resolvedName: resolvedArtifactName });
4149
4267
  case "delegate_task":
@@ -5684,7 +5802,9 @@ function ChatComposerInner({
5684
5802
  showWorkspace = false,
5685
5803
  onToggleWorkspace,
5686
5804
  canShowBrowser = false,
5687
- canShowWorkspace = false
5805
+ canShowWorkspace = false,
5806
+ viewMode = "simple",
5807
+ onViewModeChange
5688
5808
  }) {
5689
5809
  const { adapter } = useAgentChatAdapterContext();
5690
5810
  const { textInput, attachments } = usePromptInputController();
@@ -5991,6 +6111,36 @@ function ChatComposerInner({
5991
6111
  }
5992
6112
  )
5993
6113
  ] }),
6114
+ onViewModeChange && /* @__PURE__ */ jsxs(
6115
+ "div",
6116
+ {
6117
+ role: "group",
6118
+ "aria-label": "Chat view mode",
6119
+ className: "inline-flex overflow-hidden rounded-md border border-border",
6120
+ children: [
6121
+ /* @__PURE__ */ jsx(
6122
+ "button",
6123
+ {
6124
+ type: "button",
6125
+ "aria-pressed": viewMode === "simple",
6126
+ onClick: () => onViewModeChange("simple"),
6127
+ className: viewMode === "simple" ? "bg-accent px-2 py-1 text-xs text-foreground" : "px-2 py-1 text-xs text-muted-foreground hover:text-foreground",
6128
+ children: "Simple"
6129
+ }
6130
+ ),
6131
+ /* @__PURE__ */ jsx(
6132
+ "button",
6133
+ {
6134
+ type: "button",
6135
+ "aria-pressed": viewMode === "expert",
6136
+ onClick: () => onViewModeChange("expert"),
6137
+ className: viewMode === "expert" ? "bg-accent px-2 py-1 text-xs text-foreground" : "px-2 py-1 text-xs text-muted-foreground hover:text-foreground",
6138
+ children: "Expert"
6139
+ }
6140
+ )
6141
+ ]
6142
+ }
6143
+ ),
5994
6144
  canShowBrowser && onToggleBrowser && /* @__PURE__ */ jsx(
5995
6145
  PromptInputButton,
5996
6146
  {
@@ -6157,16 +6307,26 @@ function ChatComposerInner({
6157
6307
  CommandItem,
6158
6308
  {
6159
6309
  value: cmd.value,
6160
- keywords: [cmd.description],
6310
+ keywords: cmd.isExpert ? [cmd.description, "expert"] : [cmd.description],
6161
6311
  onSelect: () => handleCommandSelect(cmd.value),
6162
6312
  className: "grid grid-cols-[12rem_1fr] items-baseline gap-3 [&_svg]:hidden",
6163
6313
  children: [
6164
- /* @__PURE__ */ jsx(
6314
+ /* @__PURE__ */ jsxs(
6165
6315
  "span",
6166
6316
  {
6167
- className: "font-mono font-semibold text-foreground truncate",
6317
+ className: "inline-flex items-center gap-1.5 min-w-0 max-w-full",
6168
6318
  title: cmd.label,
6169
- children: cmd.label
6319
+ children: [
6320
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-foreground truncate", children: cmd.label }),
6321
+ cmd.isExpert && /* @__PURE__ */ jsx(
6322
+ "span",
6323
+ {
6324
+ className: "shrink-0 rounded-sm bg-primary/10 px-1 text-[9px] font-semibold uppercase tracking-wider text-primary",
6325
+ "aria-label": "Expert specialist",
6326
+ children: "expert"
6327
+ }
6328
+ )
6329
+ ]
6170
6330
  }
6171
6331
  ),
6172
6332
  /* @__PURE__ */ jsx(
@@ -6958,1194 +7118,1976 @@ var ErrorMessage = memo(function ErrorMessage2({
6958
7118
  }
6959
7119
  );
6960
7120
  });
6961
- function messageToEntries(msg, isLast, artifactFallbacks) {
6962
- if (msg.role === "system") {
6963
- if (msg.systemKind === "skill_invoked") {
6964
- return [{
6965
- kind: "skill_invoked",
6966
- key: msg.id,
6967
- skill: msg.systemMeta?.skill ?? msg.content,
6968
- stagedAt: msg.systemMeta?.staged_at ?? null
6969
- }];
6970
- }
6971
- if (msg.systemKind === "artifact") {
6972
- const { artifactId, name, kind, version } = unpackArtifactMeta(
6973
- msg.systemMeta,
6974
- msg.content
6975
- );
6976
- return [{
6977
- kind: "artifact",
6978
- key: msg.id,
6979
- artifactId,
6980
- name,
6981
- artifactKind: kind,
6982
- version
6983
- }];
6984
- }
6985
- return [];
6986
- }
6987
- const entries = [];
6988
- const hasToolCalls = !!(msg.toolCalls && msg.toolCalls.length > 0);
6989
- const hasContent = !!(msg.content && msg.content !== msg.reasoning);
6990
- const isStreaming = msg.status === "streaming" && isLast;
6991
- const effectiveReasoning = hasToolCalls && hasContent ? msg.reasoning ? msg.reasoning + "\n" + msg.content : msg.content : msg.reasoning;
6992
- const effectiveHasContent = hasContent && !hasToolCalls;
6993
- if (effectiveReasoning) {
6994
- entries.push({
6995
- kind: "reasoning",
6996
- key: `${msg.id}-reasoning`,
6997
- reasoning: effectiveReasoning,
6998
- isStreaming: isStreaming && !effectiveHasContent && !hasToolCalls
6999
- });
7000
- }
7001
- if (hasToolCalls) {
7002
- for (const tc of msg.toolCalls) {
7003
- if (tc.toolName === "create_artifact") {
7004
- const resolvedName = parseArgs(tc.args)?.name ?? artifactFallbacks[tc.id];
7005
- const status = effectiveStatus(tc);
7006
- if (!resolvedName && status !== "error") continue;
7007
- entries.push({
7008
- kind: "tool",
7009
- key: tc.id,
7010
- tc,
7011
- resolvedArtifactName: resolvedName
7012
- });
7013
- continue;
7014
- }
7015
- if (tc.toolName === "terminal" && tc.status !== "running") {
7016
- const parsed = parseTerminalResult(tc.result, tc.args);
7017
- if (parsed && (parsed.exit_code !== 0 || parsed.error)) continue;
7018
- }
7019
- entries.push({ kind: "tool", key: tc.id, tc });
7020
- }
7021
- }
7022
- if (effectiveHasContent) {
7023
- entries.push({
7024
- kind: "text",
7025
- key: `${msg.id}-text`,
7026
- content: msg.content,
7027
- isStreaming,
7028
- msg
7029
- });
7030
- }
7031
- const hasRunningTool = hasToolCalls && msg.toolCalls.some(
7032
- (tc) => tc.status === "running"
7033
- );
7034
- if (isStreaming && !effectiveHasContent && !hasRunningTool) {
7035
- entries.push({ kind: "thinking", key: `${msg.id}-thinking` });
7036
- }
7037
- return entries;
7038
- }
7039
- function groupBrowserActivityEntries(entries) {
7040
- const grouped = [];
7041
- let buffer = [];
7042
- const flush = () => {
7043
- if (buffer.length === 0) return;
7044
- grouped.push({
7045
- kind: "browser_activity",
7046
- key: `browser-activity-${buffer[0]?.id}-${buffer[buffer.length - 1]?.id}`,
7047
- calls: buffer
7048
- });
7049
- buffer = [];
7050
- };
7051
- for (const entry of entries) {
7052
- if (entry.kind === "tool" && isBrowserToolCall(entry.tc)) {
7053
- buffer.push(entry.tc);
7054
- continue;
7055
- }
7056
- flush();
7057
- grouped.push(entry);
7058
- }
7059
- flush();
7060
- return grouped;
7061
- }
7062
- function isBrowserToolCall(call) {
7063
- return call.toolName.startsWith("browser_");
7121
+
7122
+ // src/lib/format.ts
7123
+ function formatFileSize(bytes) {
7124
+ if (bytes < 1024) return `${bytes} B`;
7125
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
7126
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
7064
7127
  }
7065
- function groupWebSearchEntries(entries) {
7066
- const grouped = [];
7067
- let buffer = [];
7068
- const flush = () => {
7069
- if (buffer.length === 0) return;
7070
- if (buffer.length === 1) {
7071
- grouped.push({ kind: "tool", key: buffer[0].id, tc: buffer[0] });
7072
- } else {
7073
- grouped.push({
7074
- kind: "web_search_group",
7075
- key: `web-search-${buffer[0].id}-${buffer[buffer.length - 1].id}`,
7076
- calls: buffer
7077
- });
7078
- }
7079
- buffer = [];
7128
+ function getLanguageHint(path) {
7129
+ const name = path.split("/").pop() ?? path;
7130
+ const dot = name.lastIndexOf(".");
7131
+ if (dot < 0) return "plaintext";
7132
+ const ext = name.slice(dot).toLowerCase();
7133
+ const map = {
7134
+ ".py": "python",
7135
+ ".js": "javascript",
7136
+ ".ts": "typescript",
7137
+ ".tsx": "tsx",
7138
+ ".jsx": "jsx",
7139
+ ".json": "json",
7140
+ ".yaml": "yaml",
7141
+ ".yml": "yaml",
7142
+ ".toml": "toml",
7143
+ ".sql": "sql",
7144
+ ".sh": "bash",
7145
+ ".css": "css",
7146
+ ".html": "html",
7147
+ ".md": "markdown",
7148
+ ".rs": "rust",
7149
+ ".go": "go",
7150
+ ".java": "java",
7151
+ ".rb": "ruby",
7152
+ ".php": "php",
7153
+ ".c": "c",
7154
+ ".cpp": "cpp",
7155
+ ".h": "c",
7156
+ ".hpp": "cpp"
7080
7157
  };
7081
- for (const entry of entries) {
7082
- if (entry.kind === "tool" && entry.tc.toolName === "web_search") {
7083
- buffer.push(entry.tc);
7084
- continue;
7085
- }
7086
- flush();
7087
- grouped.push(entry);
7088
- }
7089
- flush();
7090
- return grouped;
7158
+ return map[ext] ?? "plaintext";
7091
7159
  }
7092
- function groupMessages(messages) {
7093
- const groups = [];
7094
- let pendingSystem = [];
7095
- const drainPendingAsOrphans = () => {
7096
- for (const { msg, index } of pendingSystem) {
7097
- groups.push({ role: "system", messages: [msg], lastGlobalIndex: index });
7098
- }
7099
- pendingSystem = [];
7100
- };
7101
- for (let i = 0; i < messages.length; i++) {
7102
- const msg = messages[i];
7103
- if (msg.role === "system") {
7104
- pendingSystem.push({ msg, index: i });
7105
- continue;
7106
- }
7107
- if (msg.role === "assistant") {
7108
- const prev = groups[groups.length - 1];
7109
- if (prev && prev.role === "assistant" && pendingSystem.length === 0) {
7110
- prev.messages.push(msg);
7111
- prev.lastGlobalIndex = i;
7112
- } else {
7113
- const folded = pendingSystem.map((p) => p.msg);
7114
- pendingSystem = [];
7115
- groups.push({
7116
- role: "assistant",
7117
- messages: [...folded, msg],
7118
- lastGlobalIndex: i
7119
- });
7120
- }
7121
- continue;
7160
+ function Skeleton({ className, ...props }) {
7161
+ return /* @__PURE__ */ jsx(
7162
+ "div",
7163
+ {
7164
+ className: cn("animate-pulse rounded-md bg-muted", className),
7165
+ ...props
7122
7166
  }
7123
- drainPendingAsOrphans();
7124
- groups.push({ role: "user", messages: [msg], lastGlobalIndex: i });
7125
- }
7126
- drainPendingAsOrphans();
7127
- return groups;
7128
- }
7129
- function unpackArtifactMeta(systemMeta, fallbackName) {
7130
- const meta = systemMeta ?? {};
7131
- return {
7132
- artifactId: meta.artifact_id ?? "",
7133
- name: meta.name ?? fallbackName,
7134
- kind: meta.kind ?? "markdown",
7135
- version: meta.version ?? 1
7136
- };
7167
+ );
7137
7168
  }
7138
- function OrphanSystemMarker({
7139
- message,
7140
- sessionId,
7141
- onRetry
7169
+ var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
7170
+ var PDF_WORKER_SRC = new URL(
7171
+ "pdfjs-dist/legacy/build/pdf.worker.mjs",
7172
+ import.meta.url
7173
+ ).toString();
7174
+ function FileViewer({
7175
+ file,
7176
+ loading,
7177
+ error,
7178
+ downloadUrl,
7179
+ onDelete,
7180
+ onClose
7142
7181
  }) {
7143
- if (message.systemKind === "skill_invoked") {
7144
- const skill = message.systemMeta?.skill ?? message.content;
7145
- return /* @__PURE__ */ jsxs("div", { className: "my-2 flex items-center gap-2 px-4 text-xs text-muted-foreground ", children: [
7146
- /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-emerald-500" }),
7147
- /* @__PURE__ */ jsxs("span", { children: [
7148
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Skill" }),
7149
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate", children: skill })
7150
- ] })
7151
- ] });
7152
- }
7153
- if (message.systemKind === "artifact" && sessionId) {
7154
- const unpacked = unpackArtifactMeta(message.systemMeta, message.content);
7155
- return /* @__PURE__ */ jsx(
7156
- ArtifactBlock,
7157
- {
7158
- sessionId,
7159
- artifactId: unpacked.artifactId,
7160
- name: unpacked.name,
7161
- kind: unpacked.kind,
7162
- version: unpacked.version
7163
- }
7164
- );
7165
- }
7166
- if (message.systemKind === "error" && message.errorInfo) {
7167
- return /* @__PURE__ */ jsx("div", { className: "mx-auto my-2 w-full max-w-4xl", children: /* @__PURE__ */ jsx(ErrorMessage, { errorInfo: message.errorInfo, onRetry }) });
7168
- }
7169
- return null;
7170
- }
7171
- function cancelledToolLabel(toolName) {
7172
- const map = {
7173
- terminal: "Command",
7174
- execute_code: "Execute Code",
7175
- read_file: "Read",
7176
- write_file: "Write",
7177
- patch: "Patch",
7178
- search_files: "Search Files",
7179
- list_files: "List Files",
7180
- web_search: "Web Search",
7181
- web_extract: "Web Fetch",
7182
- web_crawl: "Web Crawl",
7183
- session_search: "Session Search",
7184
- memory: "Memory",
7185
- todo: "Todo",
7186
- skills_list: "Skills",
7187
- skill_view: "Skill",
7188
- consult_expert: "Expert",
7189
- delegate_task: "Delegate",
7190
- clarify: "Clarify",
7191
- process: "Process",
7192
- create_artifact: "Artifact"
7193
- };
7194
- return map[toolName] ?? toolName;
7195
- }
7196
- function CancelledToolRow({ tc }) {
7197
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-sm", children: [
7198
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-muted-foreground", children: cancelledToolLabel(tc.toolName) }),
7199
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70", children: "Cancelled" })
7200
- ] });
7201
- }
7202
- function TimelineEntryItem({
7203
- entry,
7204
- step,
7205
- sessionId,
7206
- onFileSelect
7207
- }) {
7208
- if (entry.kind === "reasoning") {
7209
- return /* @__PURE__ */ jsx(ReasoningEntry, { entry, step });
7210
- }
7211
- if (entry.kind === "tool") {
7212
- const rawStatus = effectiveStatus(entry.tc);
7213
- const hideArtifactFailure = rawStatus === "error" && entry.tc.toolName === "create_artifact";
7214
- const indicatorStatus = hideArtifactFailure ? "running" : rawStatus;
7215
- const failureSummary = rawStatus === "error" && !hideArtifactFailure ? toolErrorSummary(entry.tc.result) : "";
7216
- return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
7217
- /* @__PURE__ */ jsxs(TimelineHeader, { children: [
7218
- /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7219
- /* @__PURE__ */ jsx(
7220
- TimelineIndicator,
7221
- {
7222
- className: cn("size-2 border-none", statusColorClass(indicatorStatus))
7223
- }
7224
- )
7225
- ] }),
7226
- /* @__PURE__ */ jsx(TimelineContent, { children: entry.tc.cancelled ? /* @__PURE__ */ jsx(CancelledToolRow, { tc: entry.tc }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
7227
- /* @__PURE__ */ jsx(
7228
- ToolCallBlock,
7229
- {
7230
- tc: entry.tc,
7231
- resolvedArtifactName: entry.resolvedArtifactName,
7232
- onFileSelect
7233
- }
7234
- ),
7235
- failureSummary ? /* @__PURE__ */ jsx("div", { className: "max-w-full truncate text-xs text-destructive", title: failureSummary, children: failureSummary }) : null
7236
- ] }) })
7237
- ] });
7238
- }
7239
- if (entry.kind === "browser_activity") {
7240
- return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
7241
- /* @__PURE__ */ jsxs(TimelineHeader, { children: [
7242
- /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7243
- /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-amber-500" })
7244
- ] }),
7245
- /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(BrowserActivityGroup, { calls: entry.calls }) })
7246
- ] });
7247
- }
7248
- if (entry.kind === "web_search_group") {
7249
- const allComplete = entry.calls.every((tc) => tc.status === "complete");
7250
- const anyRunning = entry.calls.some((tc) => tc.status === "running");
7251
- const indicatorClass = anyRunning ? "bg-emerald-500 animate-pulse" : allComplete ? "bg-emerald-500" : "bg-red-500";
7252
- return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
7253
- /* @__PURE__ */ jsxs(TimelineHeader, { children: [
7254
- /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7255
- /* @__PURE__ */ jsx(TimelineIndicator, { className: cn("size-2 border-none", indicatorClass) })
7256
- ] }),
7257
- /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(WebSearchGroupBlock, { tcs: entry.calls }) })
7258
- ] });
7259
- }
7260
- if (entry.kind === "text") {
7261
- return /* @__PURE__ */ jsx(TextEntry, { entry, step });
7262
- }
7263
- if (entry.kind === "skill_invoked") {
7264
- return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
7265
- /* @__PURE__ */ jsxs(TimelineHeader, { children: [
7266
- /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7267
- /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-emerald-500" })
7268
- ] }),
7269
- /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 py-1 text-sm ", children: [
7270
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Skill" }),
7271
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate", children: entry.skill }),
7272
- entry.stagedAt && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground/70 ", children: [
7273
- "staged at ",
7274
- entry.stagedAt
7182
+ const visible = loading || file !== null || error !== null;
7183
+ if (!visible) return null;
7184
+ const fileName2 = file?.path.split("/").pop() ?? "File";
7185
+ const lang = file ? getLanguageHint(file.path) : "";
7186
+ const isPdf = file?.mime_type === "application/pdf" || file?.path.toLowerCase().endsWith(".pdf") || false;
7187
+ const isImage = file?.encoding === "base64" && !isPdf;
7188
+ const HeaderIcon = isImage ? ImageIcon : FileTextIcon;
7189
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col border-t border-line", children: [
7190
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
7191
+ /* @__PURE__ */ jsx(HeaderIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
7192
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
7193
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs font-medium", children: fileName2 }),
7194
+ /* @__PURE__ */ jsxs("div", { className: "truncate text-[10px] text-muted-foreground", children: [
7195
+ file?.path ?? "Loading...",
7196
+ file && /* @__PURE__ */ jsxs("span", { className: "ml-1.5", children: [
7197
+ formatFileSize(file.size),
7198
+ file.truncated && " (truncated)"
7199
+ ] })
7275
7200
  ] })
7276
- ] }) })
7277
- ] });
7278
- }
7279
- if (entry.kind === "artifact") {
7280
- return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
7281
- /* @__PURE__ */ jsxs(TimelineHeader, { children: [
7282
- /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7283
- /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-sky-500" })
7284
7201
  ] }),
7285
- /* @__PURE__ */ jsx(TimelineContent, { children: sessionId ? /* @__PURE__ */ jsx(
7286
- ArtifactBlock,
7202
+ downloadUrl && /* @__PURE__ */ jsx(
7203
+ "a",
7287
7204
  {
7288
- sessionId,
7289
- artifactId: entry.artifactId,
7290
- name: entry.name,
7291
- kind: entry.artifactKind,
7292
- version: entry.version
7205
+ href: downloadUrl,
7206
+ download: fileName2,
7207
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
7208
+ "aria-label": `Download ${fileName2}`,
7209
+ title: "Download",
7210
+ children: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-3.5" })
7293
7211
  }
7294
- ) : null })
7295
- ] });
7296
- }
7297
- return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
7298
- /* @__PURE__ */ jsxs(TimelineHeader, { children: [
7299
- /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7300
- /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-primary animate-pulse" })
7301
- ] }),
7302
- /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: "Working on it..." }) })
7303
- ] });
7304
- }
7305
- function ReasoningEntry({
7306
- entry,
7307
- step
7308
- }) {
7309
- const reasoning = useSmoothStream(entry.reasoning, entry.isStreaming);
7310
- return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
7311
- /* @__PURE__ */ jsxs(TimelineHeader, { children: [
7312
- /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7313
- /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2" })
7212
+ ),
7213
+ onDelete && /* @__PURE__ */ jsx(
7214
+ "button",
7215
+ {
7216
+ type: "button",
7217
+ onClick: onDelete,
7218
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
7219
+ "aria-label": `Delete ${fileName2}`,
7220
+ title: "Delete",
7221
+ children: /* @__PURE__ */ jsx(TrashIcon, { className: "size-3.5" })
7222
+ }
7223
+ ),
7224
+ /* @__PURE__ */ jsx(
7225
+ "button",
7226
+ {
7227
+ type: "button",
7228
+ onClick: onClose,
7229
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
7230
+ "aria-label": "Close file",
7231
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5" })
7232
+ }
7233
+ )
7314
7234
  ] }),
7315
- /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsxs(Reasoning, { isStreaming: entry.isStreaming, children: [
7316
- /* @__PURE__ */ jsx(ReasoningTrigger, {}),
7317
- /* @__PURE__ */ jsx(ReasoningContent, { children: reasoning })
7318
- ] }) })
7235
+ /* @__PURE__ */ jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [
7236
+ loading && /* @__PURE__ */ jsx("div", { className: "space-y-1.5 p-3", children: Array.from({ length: 10 }).map((_, index) => /* @__PURE__ */ jsx(
7237
+ Skeleton,
7238
+ {
7239
+ className: "h-3.5 rounded",
7240
+ style: {
7241
+ width: `${SKELETON_WIDTHS[index % SKELETON_WIDTHS.length]}%`
7242
+ }
7243
+ },
7244
+ index
7245
+ )) }),
7246
+ error && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
7247
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
7248
+ /* @__PURE__ */ jsx("span", { children: error })
7249
+ ] }),
7250
+ file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
7251
+ file && isPdf && file.encoding === "base64" && /* @__PURE__ */ jsx(PdfPreview, { file, fileName: fileName2 }),
7252
+ file && isImage && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsx(
7253
+ "img",
7254
+ {
7255
+ src: `data:${file.mime_type ?? "image/png"};base64,${file.content}`,
7256
+ alt: fileName2,
7257
+ className: "max-h-[60vh] max-w-full rounded object-contain"
7258
+ }
7259
+ ) }),
7260
+ file && !isImage && !isPdf && /* @__PURE__ */ jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-[11px] leading-relaxed text-foreground", children: /* @__PURE__ */ jsx("code", { "data-language": lang, children: file.content }) })
7261
+ ] })
7319
7262
  ] });
7320
7263
  }
7321
- function TextEntry({
7322
- entry,
7323
- step
7324
- }) {
7325
- const content = useSmoothStream(entry.content, entry.isStreaming);
7326
- return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
7327
- /* @__PURE__ */ jsxs(TimelineHeader, { children: [
7328
- /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7329
- /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-foreground/40" })
7330
- ] }),
7331
- /* @__PURE__ */ jsxs(TimelineContent, { children: [
7332
- /* @__PURE__ */ jsx(MessageResponse, { children: content }),
7333
- entry.isFinalTurnText && entry.msg.status === "complete" && /* @__PURE__ */ jsx(TurnFeedback, { msg: entry.msg })
7334
- ] })
7264
+ function FileViewerError({ message }) {
7265
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
7266
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
7267
+ /* @__PURE__ */ jsx("span", { children: message })
7335
7268
  ] });
7336
7269
  }
7337
- function AssistantGroup({
7338
- messages,
7339
- lastGlobalIndex,
7340
- totalMessages,
7341
- isRunning,
7342
- sessionId,
7343
- artifactFallbacks,
7344
- onFileSelect,
7345
- onRetry
7270
+ function PdfPreview({
7271
+ file,
7272
+ fileName: fileName2
7346
7273
  }) {
7347
- let entries = [];
7348
- for (let i = 0; i < messages.length; i++) {
7349
- const isLast = i === messages.length - 1 && lastGlobalIndex === totalMessages - 1;
7350
- entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
7351
- }
7352
- entries = groupBrowserActivityEntries(entries);
7353
- entries = groupWebSearchEntries(entries);
7354
- for (let i = entries.length - 1; i >= 0; i--) {
7355
- const entry = entries[i];
7356
- if (entry?.kind === "text") {
7357
- entries[i] = { ...entry, isFinalTurnText: true };
7358
- break;
7359
- }
7360
- }
7361
- const isTailGroup = lastGlobalIndex === totalMessages - 1;
7362
- const lastEntry = entries[entries.length - 1];
7363
- const hasRunningTool = entries.some(
7364
- (e) => e.kind === "tool" && e.tc.status === "running"
7365
- );
7366
- const tailMsg = messages[messages.length - 1];
7367
- if (isTailGroup && isRunning && !hasRunningTool && lastEntry?.kind !== "thinking") {
7368
- entries.push({ kind: "thinking", key: `${tailMsg.id}-tail-thinking` });
7369
- }
7370
- const tail = messages[messages.length - 1];
7371
- const showErrorInfo = tail && tail.role === "assistant" && tail.status === "error" && !!tail.errorInfo;
7372
- return /* @__PURE__ */ jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsxs(MessageContent, { children: [
7373
- /* @__PURE__ */ jsx(Timeline, { defaultValue: 999, className: "gap-0", children: entries.map((entry, i) => /* @__PURE__ */ jsx(
7374
- TimelineEntryItem,
7375
- {
7376
- entry,
7377
- step: i + 1,
7378
- sessionId,
7379
- onFileSelect
7380
- },
7381
- entry.key
7382
- )) }),
7383
- showErrorInfo && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(ErrorMessage, { errorInfo: tail.errorInfo, onRetry }) })
7384
- ] }) });
7385
- }
7386
- function RetryBanner({ indicator }) {
7387
- const [open, setOpen] = useState(false);
7388
- return /* @__PURE__ */ jsxs(
7389
- "div",
7390
- {
7391
- role: "status",
7392
- className: "flex flex-col gap-1 border-l-2 border-amber-500 bg-amber-500/5 px-3 py-2 text-xs",
7393
- children: [
7394
- /* @__PURE__ */ jsxs(
7395
- "button",
7396
- {
7397
- type: "button",
7398
- onClick: () => setOpen((prev) => !prev),
7399
- className: "flex items-center gap-2 text-left text-amber-600 hover:text-amber-700",
7400
- "aria-expanded": open,
7401
- children: [
7402
- /* @__PURE__ */ jsx(AlertTriangle, { className: "size-3 shrink-0" }),
7403
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate font-medium", children: indicator.title }),
7404
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
7405
- "attempt ",
7406
- indicator.attempt
7407
- ] }),
7408
- indicator.detail && (open ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-3" }))
7409
- ]
7410
- }
7411
- ),
7412
- open && indicator.detail && /* @__PURE__ */ jsx("pre", { className: "mt-1 overflow-x-auto rounded-none bg-background p-2 font-mono text-[11px] whitespace-pre-wrap wrap-break-word text-muted-foreground", children: indicator.detail })
7413
- ]
7414
- }
7415
- );
7416
- }
7417
- function ChatThread({
7418
- sessionId,
7419
- messages,
7420
- isRunning,
7421
- isLoadingHistory = false,
7422
- onSend,
7423
- onStop,
7424
- onFileSelect,
7425
- disabled = false,
7426
- disabledReason,
7427
- tokenUsage,
7428
- retryIndicator,
7429
- onRetry,
7430
- onComposerError,
7431
- showBrowser = false,
7432
- onToggleBrowser,
7433
- showWorkspace = false,
7434
- onToggleWorkspace,
7435
- canShowBrowser = false,
7436
- canShowWorkspace = false
7437
- }) {
7438
- const groups = useMemo(() => groupMessages(messages), [messages]);
7439
- const artifactFallbacks = useMemo(() => {
7440
- const fallbacks = {};
7441
- const createArtifactToolCallIds = [];
7442
- let artifactIdx = 0;
7443
- for (const msg of messages) {
7444
- if (msg.role === "assistant" && msg.toolCalls) {
7445
- for (const tc of msg.toolCalls) {
7446
- if (tc.toolName === "create_artifact" && !tc.cancelled && tc.status !== "error") {
7447
- createArtifactToolCallIds.push(tc.id);
7448
- }
7274
+ const containerRef = useRef(null);
7275
+ const viewerRef = useRef(null);
7276
+ const pdfViewerRef = useRef(null);
7277
+ const eventBusRef = useRef(null);
7278
+ const findControllerRef = useRef(null);
7279
+ const [error, setError] = useState(null);
7280
+ const [pageNumber, setPageNumber] = useState(1);
7281
+ const [pagesCount, setPagesCount] = useState(0);
7282
+ const [scale, setScale] = useState(1);
7283
+ const [query, setQuery] = useState("");
7284
+ useEffect(() => {
7285
+ let cancelled = false;
7286
+ let loadingTask = null;
7287
+ let pdfDocument = null;
7288
+ const renderPdf = async () => {
7289
+ setError(null);
7290
+ const container = containerRef.current;
7291
+ const viewerElement = viewerRef.current;
7292
+ if (!container || !viewerElement) return;
7293
+ const pdfBytes = decodeBase64(file.content);
7294
+ const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
7295
+ pdfjs.GlobalWorkerOptions.workerSrc = PDF_WORKER_SRC;
7296
+ globalThis.pdfjsLib = pdfjs;
7297
+ const pdfViewerModule = await import('pdfjs-dist/web/pdf_viewer.mjs');
7298
+ loadingTask = pdfjs.getDocument({ data: pdfBytes });
7299
+ const pdf = await loadingTask.promise;
7300
+ pdfDocument = pdf;
7301
+ if (cancelled) return;
7302
+ const eventBus = new pdfViewerModule.EventBus();
7303
+ const linkService = new pdfViewerModule.PDFLinkService({ eventBus });
7304
+ const findController = new pdfViewerModule.PDFFindController({
7305
+ eventBus,
7306
+ linkService
7307
+ });
7308
+ const pdfViewer = new pdfViewerModule.PDFViewer({
7309
+ container,
7310
+ viewer: viewerElement,
7311
+ eventBus,
7312
+ linkService,
7313
+ findController,
7314
+ removePageBorders: true
7315
+ });
7316
+ linkService.setViewer(pdfViewer);
7317
+ linkService.setDocument(pdf);
7318
+ findController.setDocument(pdf);
7319
+ const onPagesInit = () => {
7320
+ pdfViewer.currentScaleValue = "page-width";
7321
+ setScale(pdfViewer.currentScale || 1);
7322
+ setPagesCount(pdfViewer.pagesCount);
7323
+ setPageNumber(pdfViewer.currentPageNumber);
7324
+ };
7325
+ const onPageChanging = (event) => {
7326
+ if (typeof event.pageNumber === "number") {
7327
+ setPageNumber(event.pageNumber);
7449
7328
  }
7450
- } else if (msg.role === "system" && msg.systemKind === "artifact") {
7451
- const tcId = createArtifactToolCallIds[artifactIdx];
7452
- if (tcId) {
7453
- const { name } = unpackArtifactMeta(msg.systemMeta, msg.content);
7454
- if (name) fallbacks[tcId] = name;
7329
+ };
7330
+ const onScaleChanging = (event) => {
7331
+ if (typeof event.scale === "number") {
7332
+ setScale(event.scale);
7455
7333
  }
7456
- artifactIdx += 1;
7334
+ };
7335
+ eventBus.on("pagesinit", onPagesInit);
7336
+ eventBus.on("pagechanging", onPageChanging);
7337
+ eventBus.on("scalechanging", onScaleChanging);
7338
+ pdfViewerRef.current = pdfViewer;
7339
+ eventBusRef.current = eventBus;
7340
+ findControllerRef.current = findController;
7341
+ setPagesCount(pdf.numPages);
7342
+ pdfViewer.setDocument(pdf);
7343
+ };
7344
+ void renderPdf().catch((nextError) => {
7345
+ if (cancelled) return;
7346
+ if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
7347
+ return;
7457
7348
  }
7458
- }
7459
- return fallbacks;
7460
- }, [messages]);
7461
- const activeFailureId = useMemo(() => {
7462
- if (messages.length === 0) return null;
7463
- const tail = messages[messages.length - 1];
7464
- if (tail.role === "system" && tail.systemKind === "error" && tail.errorInfo) {
7465
- return tail.id;
7466
- }
7467
- if (tail.role === "assistant" && tail.status === "error" && tail.errorInfo) {
7468
- return tail.id;
7469
- }
7470
- return null;
7471
- }, [messages]);
7472
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col overflow-hidden bg-background text-sm", children: [
7473
- /* @__PURE__ */ jsxs(Conversation, { className: "relative flex-1 min-h-0", children: [
7474
- /* @__PURE__ */ jsx(ConversationContent, { className: "mx-auto w-full max-w-4xl", children: messages.length === 0 && isLoadingHistory ? /* @__PURE__ */ jsx(
7475
- ConversationEmptyState,
7349
+ setError(formatPdfPreviewError(nextError));
7350
+ });
7351
+ return () => {
7352
+ cancelled = true;
7353
+ loadingTask?.destroy();
7354
+ pdfDocument?.destroy();
7355
+ pdfViewerRef.current?.cleanup();
7356
+ pdfViewerRef.current = null;
7357
+ eventBusRef.current = null;
7358
+ findControllerRef.current = null;
7359
+ };
7360
+ }, [file.content]);
7361
+ const setViewerPage = (nextPage) => {
7362
+ const viewer = pdfViewerRef.current;
7363
+ if (!viewer) return;
7364
+ const clampedPage = Math.min(
7365
+ Math.max(1, nextPage),
7366
+ Math.max(1, viewer.pagesCount)
7367
+ );
7368
+ viewer.currentPageNumber = clampedPage;
7369
+ setPageNumber(clampedPage);
7370
+ };
7371
+ const setViewerScale = (nextScale) => {
7372
+ const viewer = pdfViewerRef.current;
7373
+ if (!viewer) return;
7374
+ const clampedScale = Math.min(3, Math.max(0.5, nextScale));
7375
+ viewer.currentScale = clampedScale;
7376
+ setScale(clampedScale);
7377
+ };
7378
+ const runFind = (findPrevious = false) => {
7379
+ const eventBus = eventBusRef.current;
7380
+ if (!eventBus || !query.trim()) return;
7381
+ eventBus.dispatch("find", {
7382
+ source: eventBus,
7383
+ type: "again",
7384
+ query,
7385
+ phraseSearch: true,
7386
+ caseSensitive: false,
7387
+ entireWord: false,
7388
+ highlightAll: true,
7389
+ findPrevious,
7390
+ matchDiacritics: true
7391
+ });
7392
+ };
7393
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col", children: [
7394
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
7395
+ /* @__PURE__ */ jsx(
7396
+ Button,
7476
7397
  {
7477
- icon: /* @__PURE__ */ jsx(MessageSquareIcon, { className: "size-8 opacity-40" }),
7478
- title: "Loading conversation",
7479
- description: "Fetching the session history."
7398
+ type: "button",
7399
+ variant: "ghost",
7400
+ size: "icon-xs",
7401
+ onClick: () => setViewerPage(pageNumber - 1),
7402
+ disabled: pageNumber <= 1,
7403
+ "aria-label": "Previous PDF page",
7404
+ title: "Previous page",
7405
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-3.5" })
7480
7406
  }
7481
- ) : messages.length === 0 && !disabled ? /* @__PURE__ */ jsx(
7482
- ConversationEmptyState,
7407
+ ),
7408
+ /* @__PURE__ */ jsx(
7409
+ Input,
7483
7410
  {
7484
- icon: /* @__PURE__ */ jsx(MessageSquareIcon, { className: "size-8 opacity-40" }),
7485
- title: "Start a conversation",
7486
- description: "Ask me anything \u2014 I can search, analyze, write code, and more."
7411
+ type: "number",
7412
+ min: 1,
7413
+ max: pagesCount || 1,
7414
+ value: pageNumber,
7415
+ onChange: (event) => setViewerPage(Number(event.target.value)),
7416
+ "aria-label": "PDF page number",
7417
+ className: "h-7 w-12 border-input px-1 text-center text-xs"
7487
7418
  }
7488
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
7489
- groups.map((group) => {
7490
- if (group.role === "user") {
7491
- const msg = group.messages[0];
7492
- return /* @__PURE__ */ jsx(
7493
- ChatMessage,
7494
- {
7495
- message: msg,
7496
- onFileSelect
7497
- },
7498
- msg.id
7499
- );
7500
- }
7501
- if (group.role === "system") {
7502
- const msg = group.messages[0];
7503
- const groupRetry2 = msg.id === activeFailureId ? onRetry : void 0;
7504
- return /* @__PURE__ */ jsx(
7505
- OrphanSystemMarker,
7506
- {
7507
- message: msg,
7508
- sessionId,
7509
- onRetry: groupRetry2
7510
- },
7511
- msg.id
7512
- );
7513
- }
7514
- const groupTail = group.messages[group.messages.length - 1];
7515
- const groupRetry = groupTail.id === activeFailureId ? onRetry : void 0;
7516
- return /* @__PURE__ */ jsx(
7517
- AssistantGroup,
7518
- {
7519
- messages: group.messages,
7520
- lastGlobalIndex: group.lastGlobalIndex,
7521
- totalMessages: messages.length,
7522
- isRunning,
7523
- sessionId,
7524
- artifactFallbacks,
7525
- onFileSelect,
7526
- onRetry: groupRetry
7527
- },
7528
- group.messages[0].id
7529
- );
7530
- }),
7531
- isRunning && messages.length > 0 && messages[messages.length - 1].role === "user" && /* @__PURE__ */ jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: "Working on it..." }) }) })
7532
- ] }) }),
7533
- /* @__PURE__ */ jsx(ConversationScrollButton, {})
7534
- ] }),
7535
- /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
7536
- retryIndicator && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(RetryBanner, { indicator: retryIndicator }) }),
7537
- disabled && disabledReason ? /* @__PURE__ */ jsx(
7538
- "div",
7419
+ ),
7420
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
7421
+ "/ ",
7422
+ pagesCount || "-"
7423
+ ] }),
7424
+ /* @__PURE__ */ jsx(
7425
+ Button,
7539
7426
  {
7540
- className: "rounded border border-line bg-muted/40 px-3 py-2 text-sm text-muted-foreground",
7541
- role: "status",
7542
- children: disabledReason
7427
+ type: "button",
7428
+ variant: "ghost",
7429
+ size: "icon-xs",
7430
+ onClick: () => setViewerPage(pageNumber + 1),
7431
+ disabled: pagesCount > 0 && pageNumber >= pagesCount,
7432
+ "aria-label": "Next PDF page",
7433
+ title: "Next page",
7434
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5" })
7543
7435
  }
7544
- ) : /* @__PURE__ */ jsx(
7545
- ChatComposer,
7436
+ ),
7437
+ /* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
7438
+ /* @__PURE__ */ jsx(
7439
+ Button,
7546
7440
  {
7547
- onSend,
7548
- onStop,
7549
- isRunning,
7550
- disabled,
7551
- disabledReason,
7552
- tokenUsage,
7553
- onComposerError,
7554
- showBrowser,
7555
- onToggleBrowser,
7556
- showWorkspace,
7557
- onToggleWorkspace,
7558
- canShowBrowser,
7559
- canShowWorkspace
7441
+ type: "button",
7442
+ variant: "ghost",
7443
+ size: "icon-xs",
7444
+ onClick: () => setViewerScale(scale - 0.1),
7445
+ disabled: scale <= 0.5,
7446
+ "aria-label": "Zoom out PDF",
7447
+ title: "Zoom out",
7448
+ children: /* @__PURE__ */ jsx(MinusIcon, { className: "size-3.5" })
7449
+ }
7450
+ ),
7451
+ /* @__PURE__ */ jsxs(
7452
+ "button",
7453
+ {
7454
+ type: "button",
7455
+ className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
7456
+ onClick: () => {
7457
+ const viewer = pdfViewerRef.current;
7458
+ if (!viewer) return;
7459
+ viewer.currentScaleValue = "page-width";
7460
+ setScale(viewer.currentScale || 1);
7461
+ },
7462
+ "aria-label": "Fit PDF to width",
7463
+ title: "Fit width",
7464
+ children: [
7465
+ Math.round(scale * 100),
7466
+ "%"
7467
+ ]
7468
+ }
7469
+ ),
7470
+ /* @__PURE__ */ jsx(
7471
+ Button,
7472
+ {
7473
+ type: "button",
7474
+ variant: "ghost",
7475
+ size: "icon-xs",
7476
+ onClick: () => setViewerScale(scale + 0.1),
7477
+ disabled: scale >= 3,
7478
+ "aria-label": "Zoom in PDF",
7479
+ title: "Zoom in",
7480
+ children: /* @__PURE__ */ jsx(PlusIcon, { className: "size-3.5" })
7481
+ }
7482
+ ),
7483
+ /* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
7484
+ /* @__PURE__ */ jsx(SearchIcon, { className: "size-3.5 text-muted-foreground" }),
7485
+ /* @__PURE__ */ jsx(
7486
+ Input,
7487
+ {
7488
+ type: "search",
7489
+ value: query,
7490
+ onChange: (event) => setQuery(event.target.value),
7491
+ onKeyDown: (event) => {
7492
+ if (event.key === "Enter") runFind(event.shiftKey);
7493
+ },
7494
+ placeholder: "Find",
7495
+ "aria-label": "Find in PDF",
7496
+ className: "h-7 w-28 border-input px-1 text-xs"
7497
+ }
7498
+ ),
7499
+ /* @__PURE__ */ jsx(
7500
+ Button,
7501
+ {
7502
+ type: "button",
7503
+ variant: "ghost",
7504
+ size: "xs",
7505
+ onClick: () => runFind(false),
7506
+ disabled: !query.trim(),
7507
+ children: "Find"
7560
7508
  }
7561
7509
  )
7562
- ] })
7510
+ ] }),
7511
+ error && /* @__PURE__ */ jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
7512
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
7513
+ /* @__PURE__ */ jsx("span", { children: error })
7514
+ ] }),
7515
+ !error && /* @__PURE__ */ jsx("div", { className: "relative h-[70vh] min-h-[420px] flex-1 bg-muted/20", children: /* @__PURE__ */ jsx(
7516
+ "div",
7517
+ {
7518
+ ref: containerRef,
7519
+ "aria-label": `PDF viewer for ${fileName2}`,
7520
+ className: "absolute inset-0 overflow-auto",
7521
+ children: /* @__PURE__ */ jsx("div", { ref: viewerRef, className: "pdfViewer" })
7522
+ }
7523
+ ) })
7563
7524
  ] });
7564
7525
  }
7565
- var noop = () => {
7526
+ function decodeBase64(content) {
7527
+ const binary = globalThis.atob(content);
7528
+ const bytes = new Uint8Array(binary.length);
7529
+ for (let index = 0; index < binary.length; index += 1) {
7530
+ bytes[index] = binary.charCodeAt(index);
7531
+ }
7532
+ return bytes;
7533
+ }
7534
+ function formatPdfPreviewError(error) {
7535
+ if (error instanceof Error && (error.name === "InvalidCharacterError" || error.message === "Invalid character")) {
7536
+ return "PDF preview data is not valid base64.";
7537
+ }
7538
+ return error instanceof Error ? error.message : "Failed to render PDF preview.";
7539
+ }
7540
+ var _EXTENSION_KIND = {
7541
+ // Documents
7542
+ pdf: { icon: FileTextIcon, typeLabel: "PDF" },
7543
+ doc: { icon: FileTextIcon, typeLabel: "Word document" },
7544
+ docx: { icon: FileTextIcon, typeLabel: "Word document" },
7545
+ odt: { icon: FileTextIcon, typeLabel: "OpenDocument text" },
7546
+ rtf: { icon: FileTextIcon, typeLabel: "Rich text" },
7547
+ txt: { icon: FileTextIcon, typeLabel: "Text" },
7548
+ md: { icon: FileTextIcon, typeLabel: "Markdown" },
7549
+ // Spreadsheets
7550
+ xls: { icon: FileSpreadsheetIcon, typeLabel: "Spreadsheet" },
7551
+ xlsx: { icon: FileSpreadsheetIcon, typeLabel: "Spreadsheet" },
7552
+ ods: { icon: FileSpreadsheetIcon, typeLabel: "OpenDocument spreadsheet" },
7553
+ csv: { icon: FileSpreadsheetIcon, typeLabel: "CSV" },
7554
+ tsv: { icon: FileSpreadsheetIcon, typeLabel: "TSV" },
7555
+ // Presentations / charts
7556
+ ppt: { icon: FileChartLineIcon, typeLabel: "Presentation" },
7557
+ pptx: { icon: FileChartLineIcon, typeLabel: "Presentation" },
7558
+ // Images
7559
+ png: { icon: FileImageIcon, typeLabel: "Image" },
7560
+ jpg: { icon: FileImageIcon, typeLabel: "Image" },
7561
+ jpeg: { icon: FileImageIcon, typeLabel: "Image" },
7562
+ gif: { icon: FileImageIcon, typeLabel: "Image" },
7563
+ svg: { icon: FileImageIcon, typeLabel: "SVG image" },
7564
+ webp: { icon: FileImageIcon, typeLabel: "Image" },
7565
+ bmp: { icon: FileImageIcon, typeLabel: "Image" },
7566
+ // Audio / video
7567
+ mp3: { icon: FileAudioIcon, typeLabel: "Audio" },
7568
+ wav: { icon: FileAudioIcon, typeLabel: "Audio" },
7569
+ ogg: { icon: FileAudioIcon, typeLabel: "Audio" },
7570
+ mp4: { icon: FileVideoIcon, typeLabel: "Video" },
7571
+ mov: { icon: FileVideoIcon, typeLabel: "Video" },
7572
+ webm: { icon: FileVideoIcon, typeLabel: "Video" },
7573
+ // Archives
7574
+ zip: { icon: FileArchiveIcon, typeLabel: "Archive" },
7575
+ tar: { icon: FileArchiveIcon, typeLabel: "Archive" },
7576
+ gz: { icon: FileArchiveIcon, typeLabel: "Archive" },
7577
+ bz2: { icon: FileArchiveIcon, typeLabel: "Archive" },
7578
+ "7z": { icon: FileArchiveIcon, typeLabel: "Archive" },
7579
+ // Code
7580
+ py: { icon: FileCodeIcon, typeLabel: "Python" },
7581
+ js: { icon: FileCodeIcon, typeLabel: "JavaScript" },
7582
+ ts: { icon: FileCodeIcon, typeLabel: "TypeScript" },
7583
+ tsx: { icon: FileCodeIcon, typeLabel: "TypeScript" },
7584
+ jsx: { icon: FileCodeIcon, typeLabel: "JavaScript" },
7585
+ json: { icon: FileCodeIcon, typeLabel: "JSON" },
7586
+ yaml: { icon: FileCodeIcon, typeLabel: "YAML" },
7587
+ yml: { icon: FileCodeIcon, typeLabel: "YAML" },
7588
+ toml: { icon: FileCodeIcon, typeLabel: "TOML" },
7589
+ sh: { icon: CodeIcon, typeLabel: "Shell" },
7590
+ rs: { icon: FileCodeIcon, typeLabel: "Rust" },
7591
+ go: { icon: FileCodeIcon, typeLabel: "Go" },
7592
+ rb: { icon: FileCodeIcon, typeLabel: "Ruby" },
7593
+ java: { icon: FileCodeIcon, typeLabel: "Java" },
7594
+ cpp: { icon: FileCodeIcon, typeLabel: "C++" },
7595
+ c: { icon: FileCodeIcon, typeLabel: "C" },
7596
+ html: { icon: FileCodeIcon, typeLabel: "HTML" },
7597
+ css: { icon: FileCodeIcon, typeLabel: "CSS" },
7598
+ scss: { icon: FileCodeIcon, typeLabel: "Sass" },
7599
+ sql: { icon: FileCodeIcon, typeLabel: "SQL" }
7566
7600
  };
7567
- var FileTreeContext = createContext({
7568
- expandedPaths: /* @__PURE__ */ new Set(),
7569
- togglePath: noop
7570
- });
7571
- function FileTree({
7572
- expanded: controlledExpanded,
7573
- defaultExpanded = /* @__PURE__ */ new Set(),
7574
- selectedPath,
7575
- onSelect,
7576
- onExpandedChange,
7577
- className,
7578
- children,
7579
- ...props
7580
- }) {
7581
- const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
7582
- const expandedPaths = controlledExpanded ?? internalExpanded;
7583
- const togglePath = useCallback(
7584
- (path) => {
7585
- const next = new Set(expandedPaths);
7586
- if (next.has(path)) {
7587
- next.delete(path);
7588
- } else {
7589
- next.add(path);
7590
- }
7591
- setInternalExpanded(next);
7592
- onExpandedChange?.(next);
7593
- },
7594
- [expandedPaths, onExpandedChange]
7595
- );
7596
- const contextValue = useMemo(
7597
- () => ({
7598
- expandedPaths,
7599
- onSelect,
7600
- selectedPath: selectedPath ?? void 0,
7601
- togglePath
7602
- }),
7603
- [expandedPaths, onSelect, selectedPath, togglePath]
7604
- );
7605
- return /* @__PURE__ */ jsx(FileTreeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
7606
- "div",
7607
- {
7608
- className: cn("rounded-lg border bg-background text-sm", className),
7609
- role: "tree",
7610
- ...props,
7611
- children: /* @__PURE__ */ jsx("div", { className: "p-2", children })
7612
- }
7613
- ) });
7601
+ function _extension(path) {
7602
+ const idx = path.lastIndexOf(".");
7603
+ if (idx < 0 || idx === path.length - 1) return "";
7604
+ return path.slice(idx + 1).toLowerCase();
7614
7605
  }
7615
- function FileTreeIcon({
7616
- className,
7617
- children,
7618
- ...props
7619
- }) {
7620
- return /* @__PURE__ */ jsx("span", { className: cn("shrink-0", className), ...props, children });
7606
+ function _fileKindFor(path) {
7607
+ const ext = _extension(path);
7608
+ return _EXTENSION_KIND[ext] ?? { icon: FileIcon, typeLabel: "File" };
7621
7609
  }
7622
- function FileTreeName({
7623
- className,
7624
- children,
7625
- ...props
7626
- }) {
7627
- return /* @__PURE__ */ jsx("span", { className: cn("truncate", className), ...props, children });
7610
+ function _basename(path) {
7611
+ const idx = path.lastIndexOf("/");
7612
+ return idx >= 0 ? path.slice(idx + 1) : path;
7628
7613
  }
7629
- function FileTreeFolder({
7614
+ function WorkspaceFileCard({
7615
+ sessionId,
7630
7616
  path,
7631
- name,
7632
- className,
7633
- children,
7634
- ...props
7617
+ label
7635
7618
  }) {
7636
- const { expandedPaths, togglePath, selectedPath, onSelect } = useContext(FileTreeContext);
7637
- const isExpanded = expandedPaths.has(path);
7638
- const isSelected = selectedPath === path;
7639
- const handleOpenChange = useCallback(() => {
7640
- togglePath(path);
7641
- }, [togglePath, path]);
7642
- const handleSelect = useCallback(() => {
7643
- onSelect?.(path);
7644
- }, [onSelect, path]);
7645
- return /* @__PURE__ */ jsx(Collapsible, { onOpenChange: handleOpenChange, open: isExpanded, children: /* @__PURE__ */ jsxs("div", { className: cn("", className), role: "treeitem", tabIndex: 0, ...props, children: [
7619
+ const { adapter } = useAgentChatAdapterContext();
7620
+ const kind = _fileKindFor(path);
7621
+ const Icon = kind.icon;
7622
+ const downloadUrl = adapter.getWorkspaceDownloadUrl({ sessionId, path });
7623
+ const downloadName = _basename(path) || label;
7624
+ const [previewOpen, setPreviewOpen] = useState(false);
7625
+ const handleCardClick = useCallback(() => {
7626
+ setPreviewOpen(true);
7627
+ }, []);
7628
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
7646
7629
  /* @__PURE__ */ jsxs(
7647
7630
  "div",
7648
7631
  {
7632
+ role: "button",
7633
+ tabIndex: 0,
7634
+ onClick: handleCardClick,
7635
+ onKeyDown: (e) => {
7636
+ if (e.key === "Enter" || e.key === " ") {
7637
+ e.preventDefault();
7638
+ handleCardClick();
7639
+ }
7640
+ },
7649
7641
  className: cn(
7650
- "flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50",
7651
- isSelected && "bg-muted"
7642
+ "group/file flex items-center gap-3 rounded-lg border border-border",
7643
+ "bg-muted/20 px-3 py-2 text-left transition-colors",
7644
+ "cursor-pointer hover:bg-muted/40"
7652
7645
  ),
7653
7646
  children: [
7654
- /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
7655
- "button",
7656
- {
7657
- className: "flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0",
7658
- type: "button",
7659
- "aria-label": isExpanded ? `Collapse ${name}` : `Expand ${name}`,
7660
- children: /* @__PURE__ */ jsx(
7661
- ChevronRightIcon,
7662
- {
7663
- className: cn(
7664
- "size-4 shrink-0 text-muted-foreground transition-transform",
7665
- isExpanded && "rotate-90"
7666
- )
7667
- }
7668
- )
7669
- }
7670
- ) }),
7647
+ /* @__PURE__ */ jsx("div", { className: "flex size-10 shrink-0 items-center justify-center rounded-md border border-border bg-background", children: /* @__PURE__ */ jsx(Icon, { className: "size-5 text-muted-foreground", "aria-hidden": true }) }),
7648
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
7649
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium text-foreground", children: label }),
7650
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: kind.typeLabel })
7651
+ ] }),
7671
7652
  /* @__PURE__ */ jsxs(
7672
- "button",
7653
+ "a",
7673
7654
  {
7674
- className: "flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left",
7675
- onClick: handleSelect,
7676
- type: "button",
7655
+ href: downloadUrl,
7656
+ download: downloadName,
7657
+ onClick: (e) => e.stopPropagation(),
7658
+ className: cn(
7659
+ "shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border",
7660
+ "bg-background px-2.5 py-1.5 text-xs font-medium text-foreground",
7661
+ "transition-colors hover:bg-muted"
7662
+ ),
7663
+ "aria-label": `Download ${downloadName}`,
7677
7664
  children: [
7678
- /* @__PURE__ */ jsx(FileTreeIcon, { children: isExpanded ? /* @__PURE__ */ jsx(FolderOpenIcon, { className: "size-4 text-blue-500" }) : /* @__PURE__ */ jsx(FolderIcon, { className: "size-4 text-blue-500" }) }),
7679
- /* @__PURE__ */ jsx(FileTreeName, { children: name })
7665
+ /* @__PURE__ */ jsx(DownloadIcon, { className: "size-3.5", "aria-hidden": true }),
7666
+ "Download"
7680
7667
  ]
7681
7668
  }
7682
7669
  )
7683
7670
  ]
7684
7671
  }
7685
7672
  ),
7686
- /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "ml-4 border-l border-muted-foreground/50 pl-2", children }) })
7687
- ] }) });
7673
+ /* @__PURE__ */ jsx(
7674
+ WorkspaceFilePreviewDialog,
7675
+ {
7676
+ open: previewOpen,
7677
+ onOpenChange: setPreviewOpen,
7678
+ sessionId,
7679
+ path,
7680
+ downloadUrl
7681
+ }
7682
+ )
7683
+ ] });
7688
7684
  }
7689
- function FileTreeFile({
7685
+ function WorkspaceFilePreviewDialog({
7686
+ open,
7687
+ onOpenChange,
7688
+ sessionId,
7690
7689
  path,
7691
- name,
7692
- icon,
7693
- className,
7694
- children,
7695
- ...props
7690
+ downloadUrl
7696
7691
  }) {
7697
- const { selectedPath, onSelect } = useContext(FileTreeContext);
7698
- const isSelected = selectedPath === path;
7699
- const handleClick = useCallback(() => {
7700
- onSelect?.(path);
7701
- }, [onSelect, path]);
7702
- const handleKeyDown = useCallback(
7703
- (event) => {
7704
- if (event.key === "Enter" || event.key === " ") {
7705
- onSelect?.(path);
7692
+ const { adapter } = useAgentChatAdapterContext();
7693
+ const [file, setFile] = useState(null);
7694
+ const [loading, setLoading] = useState(false);
7695
+ const [error, setError] = useState(null);
7696
+ useEffect(() => {
7697
+ if (!open) return;
7698
+ let cancelled = false;
7699
+ setLoading(true);
7700
+ setError(null);
7701
+ setFile(null);
7702
+ adapter.getWorkspaceFile({ sessionId, path }).then((result) => {
7703
+ if (cancelled) return;
7704
+ setFile(result);
7705
+ setLoading(false);
7706
+ }).catch((err) => {
7707
+ if (cancelled) return;
7708
+ const message = err instanceof Error ? err.message : "Failed to load file";
7709
+ setError(message);
7710
+ setLoading(false);
7711
+ });
7712
+ return () => {
7713
+ cancelled = true;
7714
+ };
7715
+ }, [adapter, sessionId, path, open]);
7716
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "flex h-[80vh] w-full max-w-3xl flex-col gap-0 overflow-hidden p-0", children: [
7717
+ /* @__PURE__ */ jsx(DialogHeader, { className: "border-b border-line px-4 py-3", children: /* @__PURE__ */ jsx(DialogTitle, { className: "truncate text-sm", children: _basename(path) }) }),
7718
+ /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsx(
7719
+ FileViewer,
7720
+ {
7721
+ file,
7722
+ loading,
7723
+ error,
7724
+ downloadUrl,
7725
+ onDelete: null,
7726
+ onClose: () => onOpenChange(false)
7706
7727
  }
7707
- },
7708
- [onSelect, path]
7709
- );
7710
- return /* @__PURE__ */ jsx(
7711
- "div",
7712
- {
7713
- className: cn(
7714
- "group flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50",
7715
- isSelected && "bg-muted",
7716
- className
7717
- ),
7718
- onClick: handleClick,
7719
- onKeyDown: handleKeyDown,
7720
- role: "treeitem",
7721
- tabIndex: 0,
7722
- ...props,
7723
- children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
7724
- /* @__PURE__ */ jsx("span", { className: "size-4 shrink-0" }),
7725
- /* @__PURE__ */ jsx(FileTreeIcon, { children: icon ?? /* @__PURE__ */ jsx(FileIcon, { className: "size-4 text-muted-foreground" }) }),
7726
- /* @__PURE__ */ jsx(FileTreeName, { children: name })
7727
- ] })
7728
- }
7729
- );
7730
- }
7731
- function Skeleton({ className, ...props }) {
7732
- return /* @__PURE__ */ jsx(
7733
- "div",
7734
- {
7735
- className: cn("animate-pulse rounded-md bg-muted", className),
7736
- ...props
7737
- }
7738
- );
7739
- }
7740
-
7741
- // src/lib/format.ts
7742
- function formatFileSize(bytes) {
7743
- if (bytes < 1024) return `${bytes} B`;
7744
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
7745
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
7728
+ ) })
7729
+ ] }) });
7746
7730
  }
7747
- function getLanguageHint(path) {
7748
- const name = path.split("/").pop() ?? path;
7749
- const dot = name.lastIndexOf(".");
7750
- if (dot < 0) return "plaintext";
7751
- const ext = name.slice(dot).toLowerCase();
7752
- const map = {
7753
- ".py": "python",
7754
- ".js": "javascript",
7755
- ".ts": "typescript",
7756
- ".tsx": "tsx",
7757
- ".jsx": "jsx",
7758
- ".json": "json",
7759
- ".yaml": "yaml",
7760
- ".yml": "yaml",
7761
- ".toml": "toml",
7762
- ".sql": "sql",
7763
- ".sh": "bash",
7764
- ".css": "css",
7765
- ".html": "html",
7766
- ".md": "markdown",
7767
- ".rs": "rust",
7768
- ".go": "go",
7769
- ".java": "java",
7770
- ".rb": "ruby",
7771
- ".php": "php",
7772
- ".c": "c",
7773
- ".cpp": "cpp",
7774
- ".h": "c",
7775
- ".hpp": "cpp"
7776
- };
7777
- return map[ext] ?? "plaintext";
7731
+ function resolveArtifactRef(ref, messages) {
7732
+ for (const msg of messages) {
7733
+ if (msg.role !== "system" || msg.systemKind !== "artifact") continue;
7734
+ const meta = msg.systemMeta ?? {};
7735
+ if (meta.artifact_id !== ref) continue;
7736
+ const kind = meta.kind ?? "markdown";
7737
+ const version = typeof meta.version === "number" ? meta.version : 1;
7738
+ const name = typeof meta.name === "string" ? meta.name : "";
7739
+ return { artifactId: ref, name, kind, version };
7740
+ }
7741
+ return null;
7778
7742
  }
7779
- var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
7780
- var PDF_WORKER_SRC = new URL(
7781
- "pdfjs-dist/legacy/build/pdf.worker.mjs",
7782
- import.meta.url
7783
- ).toString();
7784
- function FileViewer({
7785
- file,
7786
- loading,
7787
- error,
7788
- downloadUrl,
7789
- onDelete,
7790
- onClose
7743
+ function TurnSummaryCard({
7744
+ summary,
7745
+ sessionId,
7746
+ messages,
7747
+ onFileSelect,
7748
+ onCommandSelect
7791
7749
  }) {
7792
- const visible = loading || file !== null || error !== null;
7793
- if (!visible) return null;
7794
- const fileName2 = file?.path.split("/").pop() ?? "File";
7795
- const lang = file ? getLanguageHint(file.path) : "";
7796
- const isPdf = file?.mime_type === "application/pdf" || file?.path.toLowerCase().endsWith(".pdf") || false;
7797
- const isImage = file?.encoding === "base64" && !isPdf;
7798
- const HeaderIcon = isImage ? ImageIcon : FileTextIcon;
7799
- return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col border-t border-line", children: [
7800
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
7801
- /* @__PURE__ */ jsx(HeaderIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
7802
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
7803
- /* @__PURE__ */ jsx("div", { className: "truncate text-xs font-medium", children: fileName2 }),
7804
- /* @__PURE__ */ jsxs("div", { className: "truncate text-[10px] text-muted-foreground", children: [
7805
- file?.path ?? "Loading...",
7806
- file && /* @__PURE__ */ jsxs("span", { className: "ml-1.5", children: [
7807
- formatFileSize(file.size),
7808
- file.truncated && " (truncated)"
7809
- ] })
7810
- ] })
7811
- ] }),
7812
- downloadUrl && /* @__PURE__ */ jsx(
7813
- "a",
7814
- {
7815
- href: downloadUrl,
7816
- download: fileName2,
7817
- className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
7818
- "aria-label": `Download ${fileName2}`,
7819
- title: "Download",
7820
- children: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-3.5" })
7821
- }
7822
- ),
7823
- onDelete && /* @__PURE__ */ jsx(
7824
- "button",
7825
- {
7826
- type: "button",
7827
- onClick: onDelete,
7828
- className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
7829
- "aria-label": `Delete ${fileName2}`,
7830
- title: "Delete",
7831
- children: /* @__PURE__ */ jsx(TrashIcon, { className: "size-3.5" })
7832
- }
7833
- ),
7834
- /* @__PURE__ */ jsx(
7835
- "button",
7836
- {
7837
- type: "button",
7838
- onClick: onClose,
7839
- className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
7840
- "aria-label": "Close file",
7841
- children: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5" })
7750
+ const hasRecap = summary.recap.trim().length > 0;
7751
+ const hasArtifacts = summary.artifacts.length > 0;
7752
+ if (!hasRecap && !hasArtifacts) return null;
7753
+ return /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded border border-border bg-muted/20 px-3 py-2 text-sm", children: [
7754
+ hasRecap && /* @__PURE__ */ jsx("p", { className: "mb-2 whitespace-pre-wrap text-foreground", children: summary.recap }),
7755
+ hasArtifacts && /* @__PURE__ */ jsx("div", { className: "space-y-2", children: summary.artifacts.map((artifact, i) => {
7756
+ const key = `${artifact.kind}:${artifact.ref}:${i}`;
7757
+ if (artifact.kind === "file" && sessionId) {
7758
+ return /* @__PURE__ */ jsx(
7759
+ WorkspaceFileCard,
7760
+ {
7761
+ sessionId,
7762
+ path: artifact.ref,
7763
+ label: artifact.label
7764
+ },
7765
+ key
7766
+ );
7767
+ }
7768
+ if (artifact.kind === "artifact") {
7769
+ const resolved = sessionId ? resolveArtifactRef(artifact.ref, messages) : null;
7770
+ if (resolved && sessionId) {
7771
+ return /* @__PURE__ */ jsx(
7772
+ ArtifactBlock,
7773
+ {
7774
+ sessionId,
7775
+ artifactId: resolved.artifactId,
7776
+ name: resolved.name || artifact.label,
7777
+ kind: resolved.kind,
7778
+ version: resolved.version
7779
+ },
7780
+ key
7781
+ );
7842
7782
  }
7843
- )
7844
- ] }),
7845
- /* @__PURE__ */ jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [
7846
- loading && /* @__PURE__ */ jsx("div", { className: "space-y-1.5 p-3", children: Array.from({ length: 10 }).map((_, index) => /* @__PURE__ */ jsx(
7847
- Skeleton,
7783
+ }
7784
+ return /* @__PURE__ */ jsxs(
7785
+ "div",
7848
7786
  {
7849
- className: "h-3.5 rounded",
7850
- style: {
7851
- width: `${SKELETON_WIDTHS[index % SKELETON_WIDTHS.length]}%`
7852
- }
7787
+ className: "flex items-baseline gap-2 text-sm",
7788
+ children: [
7789
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", "aria-hidden": true, children: "\u2022" }),
7790
+ /* @__PURE__ */ jsx(
7791
+ ArtifactRow,
7792
+ {
7793
+ artifact,
7794
+ sessionId,
7795
+ messages,
7796
+ onFileSelect,
7797
+ onCommandSelect
7798
+ }
7799
+ )
7800
+ ]
7853
7801
  },
7854
- index
7855
- )) }),
7856
- error && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
7857
- /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
7858
- /* @__PURE__ */ jsx("span", { children: error })
7859
- ] }),
7860
- file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
7861
- file && isPdf && file.encoding === "base64" && /* @__PURE__ */ jsx(PdfPreview, { file, fileName: fileName2 }),
7862
- file && isImage && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsx(
7863
- "img",
7864
- {
7865
- src: `data:${file.mime_type ?? "image/png"};base64,${file.content}`,
7866
- alt: fileName2,
7867
- className: "max-h-[60vh] max-w-full rounded object-contain"
7868
- }
7869
- ) }),
7870
- file && !isImage && !isPdf && /* @__PURE__ */ jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-[11px] leading-relaxed text-foreground", children: /* @__PURE__ */ jsx("code", { "data-language": lang, children: file.content }) })
7871
- ] })
7872
- ] });
7873
- }
7874
- function FileViewerError({ message }) {
7875
- return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
7876
- /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
7877
- /* @__PURE__ */ jsx("span", { children: message })
7802
+ key
7803
+ );
7804
+ }) })
7878
7805
  ] });
7879
7806
  }
7880
- function PdfPreview({
7881
- file,
7882
- fileName: fileName2
7807
+ function ArtifactRow({
7808
+ artifact,
7809
+ sessionId,
7810
+ messages,
7811
+ onFileSelect,
7812
+ onCommandSelect
7883
7813
  }) {
7884
- const containerRef = useRef(null);
7885
- const viewerRef = useRef(null);
7886
- const pdfViewerRef = useRef(null);
7887
- const eventBusRef = useRef(null);
7888
- const findControllerRef = useRef(null);
7889
- const [error, setError] = useState(null);
7890
- const [pageNumber, setPageNumber] = useState(1);
7891
- const [pagesCount, setPagesCount] = useState(0);
7892
- const [scale, setScale] = useState(1);
7893
- const [query, setQuery] = useState("");
7894
- useEffect(() => {
7895
- let cancelled = false;
7896
- let loadingTask = null;
7897
- let pdfDocument = null;
7898
- const renderPdf = async () => {
7899
- setError(null);
7900
- const container = containerRef.current;
7901
- const viewerElement = viewerRef.current;
7902
- if (!container || !viewerElement) return;
7903
- const pdfBytes = decodeBase64(file.content);
7904
- const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
7905
- pdfjs.GlobalWorkerOptions.workerSrc = PDF_WORKER_SRC;
7906
- globalThis.pdfjsLib = pdfjs;
7907
- const pdfViewerModule = await import('pdfjs-dist/web/pdf_viewer.mjs');
7908
- loadingTask = pdfjs.getDocument({ data: pdfBytes });
7909
- const pdf = await loadingTask.promise;
7910
- pdfDocument = pdf;
7911
- if (cancelled) return;
7912
- const eventBus = new pdfViewerModule.EventBus();
7913
- const linkService = new pdfViewerModule.PDFLinkService({ eventBus });
7914
- const findController = new pdfViewerModule.PDFFindController({
7915
- eventBus,
7916
- linkService
7917
- });
7918
- const pdfViewer = new pdfViewerModule.PDFViewer({
7919
- container,
7920
- viewer: viewerElement,
7921
- eventBus,
7922
- linkService,
7923
- findController,
7924
- removePageBorders: true
7925
- });
7926
- linkService.setViewer(pdfViewer);
7927
- linkService.setDocument(pdf);
7928
- findController.setDocument(pdf);
7929
- const onPagesInit = () => {
7930
- pdfViewer.currentScaleValue = "page-width";
7931
- setScale(pdfViewer.currentScale || 1);
7932
- setPagesCount(pdfViewer.pagesCount);
7933
- setPageNumber(pdfViewer.currentPageNumber);
7934
- };
7935
- const onPageChanging = (event) => {
7936
- if (typeof event.pageNumber === "number") {
7937
- setPageNumber(event.pageNumber);
7938
- }
7939
- };
7940
- const onScaleChanging = (event) => {
7941
- if (typeof event.scale === "number") {
7942
- setScale(event.scale);
7943
- }
7944
- };
7945
- eventBus.on("pagesinit", onPagesInit);
7946
- eventBus.on("pagechanging", onPageChanging);
7947
- eventBus.on("scalechanging", onScaleChanging);
7948
- pdfViewerRef.current = pdfViewer;
7949
- eventBusRef.current = eventBus;
7950
- findControllerRef.current = findController;
7951
- setPagesCount(pdf.numPages);
7952
- pdfViewer.setDocument(pdf);
7953
- };
7954
- void renderPdf().catch((nextError) => {
7955
- if (cancelled) return;
7956
- if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
7957
- return;
7814
+ if (artifact.kind === "file") {
7815
+ if (!onFileSelect) {
7816
+ return /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
7817
+ }
7818
+ return /* @__PURE__ */ jsx(
7819
+ "button",
7820
+ {
7821
+ type: "button",
7822
+ onClick: () => onFileSelect(artifact.ref),
7823
+ className: "truncate cursor-pointer text-primary hover:underline",
7824
+ children: artifact.label
7958
7825
  }
7959
- setError(formatPdfPreviewError(nextError));
7960
- });
7961
- return () => {
7962
- cancelled = true;
7963
- loadingTask?.destroy();
7964
- pdfDocument?.destroy();
7965
- pdfViewerRef.current?.cleanup();
7966
- pdfViewerRef.current = null;
7967
- eventBusRef.current = null;
7968
- findControllerRef.current = null;
7969
- };
7970
- }, [file.content]);
7971
- const setViewerPage = (nextPage) => {
7972
- const viewer = pdfViewerRef.current;
7973
- if (!viewer) return;
7974
- const clampedPage = Math.min(
7975
- Math.max(1, nextPage),
7976
- Math.max(1, viewer.pagesCount)
7977
7826
  );
7978
- viewer.currentPageNumber = clampedPage;
7979
- setPageNumber(clampedPage);
7980
- };
7981
- const setViewerScale = (nextScale) => {
7982
- const viewer = pdfViewerRef.current;
7983
- if (!viewer) return;
7984
- const clampedScale = Math.min(3, Math.max(0.5, nextScale));
7985
- viewer.currentScale = clampedScale;
7986
- setScale(clampedScale);
7987
- };
7988
- const runFind = (findPrevious = false) => {
7989
- const eventBus = eventBusRef.current;
7990
- if (!eventBus || !query.trim()) return;
7991
- eventBus.dispatch("find", {
7992
- source: eventBus,
7993
- type: "again",
7994
- query,
7995
- phraseSearch: true,
7996
- caseSensitive: false,
7997
- entireWord: false,
7998
- highlightAll: true,
7999
- findPrevious,
8000
- matchDiacritics: true
7827
+ }
7828
+ if (artifact.kind === "url") {
7829
+ return /* @__PURE__ */ jsx(
7830
+ "a",
7831
+ {
7832
+ href: artifact.ref,
7833
+ target: "_blank",
7834
+ rel: "noopener noreferrer",
7835
+ className: "truncate text-primary hover:underline",
7836
+ children: artifact.label
7837
+ }
7838
+ );
7839
+ }
7840
+ if (artifact.kind === "command") {
7841
+ if (!onCommandSelect) {
7842
+ return /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
7843
+ }
7844
+ return /* @__PURE__ */ jsx(
7845
+ "button",
7846
+ {
7847
+ type: "button",
7848
+ onClick: () => onCommandSelect(artifact.ref),
7849
+ className: "truncate text-left cursor-pointer text-primary hover:underline",
7850
+ children: artifact.label
7851
+ }
7852
+ );
7853
+ }
7854
+ const resolved = sessionId ? resolveArtifactRef(artifact.ref, messages) : null;
7855
+ if (!resolved) {
7856
+ return /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
7857
+ }
7858
+ return /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx(
7859
+ ArtifactBlock,
7860
+ {
7861
+ sessionId,
7862
+ artifactId: resolved.artifactId,
7863
+ name: resolved.name || artifact.label,
7864
+ kind: resolved.kind,
7865
+ version: resolved.version
7866
+ }
7867
+ ) });
7868
+ }
7869
+ function messageToEntries(msg, isLast, artifactFallbacks) {
7870
+ if (msg.role === "system") {
7871
+ if (msg.systemKind === "skill_invoked") {
7872
+ return [{
7873
+ kind: "skill_invoked",
7874
+ key: msg.id,
7875
+ skill: msg.systemMeta?.skill ?? msg.content,
7876
+ stagedAt: msg.systemMeta?.staged_at ?? null
7877
+ }];
7878
+ }
7879
+ if (msg.systemKind === "artifact") {
7880
+ const { artifactId, name, kind, version } = unpackArtifactMeta(
7881
+ msg.systemMeta,
7882
+ msg.content
7883
+ );
7884
+ return [{
7885
+ kind: "artifact",
7886
+ key: msg.id,
7887
+ artifactId,
7888
+ name,
7889
+ artifactKind: kind,
7890
+ version
7891
+ }];
7892
+ }
7893
+ return [];
7894
+ }
7895
+ const entries = [];
7896
+ const hasToolCalls = !!(msg.toolCalls && msg.toolCalls.length > 0);
7897
+ const hasContent = !!(msg.content && msg.content !== msg.reasoning);
7898
+ const isStreaming = msg.status === "streaming" && isLast;
7899
+ const effectiveReasoning = hasToolCalls && hasContent ? msg.reasoning ? msg.reasoning + "\n" + msg.content : msg.content : msg.reasoning;
7900
+ const effectiveHasContent = hasContent && !hasToolCalls;
7901
+ if (effectiveReasoning) {
7902
+ entries.push({
7903
+ kind: "reasoning",
7904
+ key: `${msg.id}-reasoning`,
7905
+ reasoning: effectiveReasoning,
7906
+ isStreaming: isStreaming && !effectiveHasContent && !hasToolCalls
7907
+ });
7908
+ }
7909
+ if (hasToolCalls) {
7910
+ for (const tc of msg.toolCalls) {
7911
+ if (tc.toolName === "create_artifact") {
7912
+ const resolvedName = parseArgs(tc.args)?.name ?? artifactFallbacks[tc.id];
7913
+ const status = effectiveStatus(tc);
7914
+ if (!resolvedName && status !== "error") continue;
7915
+ entries.push({
7916
+ kind: "tool",
7917
+ key: tc.id,
7918
+ tc,
7919
+ resolvedArtifactName: resolvedName
7920
+ });
7921
+ continue;
7922
+ }
7923
+ if (tc.toolName === "terminal" && tc.status !== "running") {
7924
+ const parsed = parseTerminalResult(tc.result, tc.args);
7925
+ if (parsed && (parsed.exit_code !== 0 || parsed.error)) continue;
7926
+ }
7927
+ entries.push({ kind: "tool", key: tc.id, tc });
7928
+ }
7929
+ }
7930
+ if (effectiveHasContent) {
7931
+ entries.push({
7932
+ kind: "text",
7933
+ key: `${msg.id}-text`,
7934
+ content: msg.content,
7935
+ isStreaming,
7936
+ msg
7937
+ });
7938
+ }
7939
+ const hasRunningTool = hasToolCalls && msg.toolCalls.some(
7940
+ (tc) => tc.status === "running"
7941
+ );
7942
+ if (isStreaming && !effectiveHasContent && !hasRunningTool) {
7943
+ entries.push({ kind: "thinking", key: `${msg.id}-thinking` });
7944
+ }
7945
+ return entries;
7946
+ }
7947
+ function groupBrowserActivityEntries(entries) {
7948
+ const grouped = [];
7949
+ let buffer = [];
7950
+ const flush = () => {
7951
+ if (buffer.length === 0) return;
7952
+ grouped.push({
7953
+ kind: "browser_activity",
7954
+ key: `browser-activity-${buffer[0]?.id}-${buffer[buffer.length - 1]?.id}`,
7955
+ calls: buffer
8001
7956
  });
7957
+ buffer = [];
8002
7958
  };
8003
- return /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col", children: [
8004
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
8005
- /* @__PURE__ */ jsx(
8006
- Button,
8007
- {
8008
- type: "button",
8009
- variant: "ghost",
8010
- size: "icon-xs",
8011
- onClick: () => setViewerPage(pageNumber - 1),
8012
- disabled: pageNumber <= 1,
8013
- "aria-label": "Previous PDF page",
8014
- title: "Previous page",
8015
- children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-3.5" })
8016
- }
8017
- ),
8018
- /* @__PURE__ */ jsx(
8019
- Input,
8020
- {
8021
- type: "number",
8022
- min: 1,
8023
- max: pagesCount || 1,
8024
- value: pageNumber,
8025
- onChange: (event) => setViewerPage(Number(event.target.value)),
8026
- "aria-label": "PDF page number",
8027
- className: "h-7 w-12 border-input px-1 text-center text-xs"
8028
- }
8029
- ),
8030
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
8031
- "/ ",
8032
- pagesCount || "-"
7959
+ for (const entry of entries) {
7960
+ if (entry.kind === "tool" && isBrowserToolCall(entry.tc)) {
7961
+ buffer.push(entry.tc);
7962
+ continue;
7963
+ }
7964
+ flush();
7965
+ grouped.push(entry);
7966
+ }
7967
+ flush();
7968
+ return grouped;
7969
+ }
7970
+ function isBrowserToolCall(call) {
7971
+ return call.toolName.startsWith("browser_");
7972
+ }
7973
+ function groupWebSearchEntries(entries) {
7974
+ const grouped = [];
7975
+ let buffer = [];
7976
+ const flush = () => {
7977
+ if (buffer.length === 0) return;
7978
+ if (buffer.length === 1) {
7979
+ grouped.push({ kind: "tool", key: buffer[0].id, tc: buffer[0] });
7980
+ } else {
7981
+ grouped.push({
7982
+ kind: "web_search_group",
7983
+ key: `web-search-${buffer[0].id}-${buffer[buffer.length - 1].id}`,
7984
+ calls: buffer
7985
+ });
7986
+ }
7987
+ buffer = [];
7988
+ };
7989
+ for (const entry of entries) {
7990
+ if (entry.kind === "tool" && entry.tc.toolName === "web_search") {
7991
+ buffer.push(entry.tc);
7992
+ continue;
7993
+ }
7994
+ flush();
7995
+ grouped.push(entry);
7996
+ }
7997
+ flush();
7998
+ return grouped;
7999
+ }
8000
+ function groupMessages(messages) {
8001
+ const groups = [];
8002
+ let pendingSystem = [];
8003
+ const drainPendingAsOrphans = () => {
8004
+ for (const { msg, index } of pendingSystem) {
8005
+ groups.push({ role: "system", messages: [msg], lastGlobalIndex: index });
8006
+ }
8007
+ pendingSystem = [];
8008
+ };
8009
+ for (let i = 0; i < messages.length; i++) {
8010
+ const msg = messages[i];
8011
+ if (msg.role === "system") {
8012
+ pendingSystem.push({ msg, index: i });
8013
+ continue;
8014
+ }
8015
+ if (msg.role === "assistant") {
8016
+ const prev = groups[groups.length - 1];
8017
+ if (prev && prev.role === "assistant" && pendingSystem.length === 0) {
8018
+ prev.messages.push(msg);
8019
+ prev.lastGlobalIndex = i;
8020
+ } else {
8021
+ const folded = pendingSystem.map((p) => p.msg);
8022
+ pendingSystem = [];
8023
+ groups.push({
8024
+ role: "assistant",
8025
+ messages: [...folded, msg],
8026
+ lastGlobalIndex: i
8027
+ });
8028
+ }
8029
+ continue;
8030
+ }
8031
+ drainPendingAsOrphans();
8032
+ groups.push({ role: "user", messages: [msg], lastGlobalIndex: i });
8033
+ }
8034
+ drainPendingAsOrphans();
8035
+ return groups;
8036
+ }
8037
+ function unpackArtifactMeta(systemMeta, fallbackName) {
8038
+ const meta = systemMeta ?? {};
8039
+ return {
8040
+ artifactId: meta.artifact_id ?? "",
8041
+ name: meta.name ?? fallbackName,
8042
+ kind: meta.kind ?? "markdown",
8043
+ version: meta.version ?? 1
8044
+ };
8045
+ }
8046
+ function OrphanSystemMarker({
8047
+ message,
8048
+ sessionId,
8049
+ onRetry
8050
+ }) {
8051
+ if (message.systemKind === "skill_invoked") {
8052
+ const skill = message.systemMeta?.skill ?? message.content;
8053
+ return /* @__PURE__ */ jsxs("div", { className: "my-2 flex items-center gap-2 px-4 text-xs text-foreground/60 ", children: [
8054
+ /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-emerald-500" }),
8055
+ /* @__PURE__ */ jsxs("span", { children: [
8056
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Skill" }),
8057
+ /* @__PURE__ */ jsx("span", { className: "text-foreground/60 truncate", children: skill })
8058
+ ] })
8059
+ ] });
8060
+ }
8061
+ if (message.systemKind === "artifact" && sessionId) {
8062
+ const unpacked = unpackArtifactMeta(message.systemMeta, message.content);
8063
+ return /* @__PURE__ */ jsx(
8064
+ ArtifactBlock,
8065
+ {
8066
+ sessionId,
8067
+ artifactId: unpacked.artifactId,
8068
+ name: unpacked.name,
8069
+ kind: unpacked.kind,
8070
+ version: unpacked.version
8071
+ }
8072
+ );
8073
+ }
8074
+ if (message.systemKind === "error" && message.errorInfo) {
8075
+ return /* @__PURE__ */ jsx("div", { className: "mx-auto my-2 w-full max-w-4xl", children: /* @__PURE__ */ jsx(ErrorMessage, { errorInfo: message.errorInfo, onRetry }) });
8076
+ }
8077
+ return null;
8078
+ }
8079
+ function cancelledToolLabel(toolName) {
8080
+ const map = {
8081
+ terminal: "Command",
8082
+ execute_code: "Execute Code",
8083
+ read_file: "Read",
8084
+ write_file: "Write",
8085
+ patch: "Patch",
8086
+ search_files: "Search Files",
8087
+ list_files: "List Files",
8088
+ web_search: "Web Search",
8089
+ web_extract: "Web Fetch",
8090
+ web_crawl: "Web Crawl",
8091
+ session_search: "Session Search",
8092
+ memory: "Memory",
8093
+ todo: "Todo",
8094
+ skills_list: "Skills",
8095
+ skill_view: "Skill",
8096
+ consult_expert: "Expert",
8097
+ delegate_task: "Delegate",
8098
+ ask_user_question: "Ask User Question",
8099
+ process: "Process",
8100
+ create_artifact: "Artifact"
8101
+ };
8102
+ return map[toolName] ?? toolName;
8103
+ }
8104
+ function CancelledToolRow({ tc }) {
8105
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-sm", children: [
8106
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground/60", children: cancelledToolLabel(tc.toolName) }),
8107
+ /* @__PURE__ */ jsx("span", { className: "text-foreground/60/70", children: "Cancelled" })
8108
+ ] });
8109
+ }
8110
+ function TimelineEntryItem({
8111
+ entry,
8112
+ step,
8113
+ sessionId,
8114
+ onFileSelect
8115
+ }) {
8116
+ if (entry.kind === "reasoning") {
8117
+ return /* @__PURE__ */ jsx(ReasoningEntry, { entry, step });
8118
+ }
8119
+ if (entry.kind === "tool") {
8120
+ const rawStatus = effectiveStatus(entry.tc);
8121
+ const hideArtifactFailure = rawStatus === "error" && entry.tc.toolName === "create_artifact";
8122
+ const indicatorStatus = hideArtifactFailure ? "running" : rawStatus;
8123
+ const failureSummary = rawStatus === "error" && !hideArtifactFailure ? toolErrorSummary(entry.tc.result) : "";
8124
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
8125
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
8126
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8127
+ /* @__PURE__ */ jsx(
8128
+ TimelineIndicator,
8129
+ {
8130
+ className: cn("size-2 border-none", statusColorClass(indicatorStatus))
8131
+ }
8132
+ )
8033
8133
  ] }),
8034
- /* @__PURE__ */ jsx(
8035
- Button,
8134
+ /* @__PURE__ */ jsx(TimelineContent, { children: entry.tc.cancelled ? /* @__PURE__ */ jsx(CancelledToolRow, { tc: entry.tc }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
8135
+ /* @__PURE__ */ jsx(
8136
+ ToolCallBlock,
8137
+ {
8138
+ tc: entry.tc,
8139
+ resolvedArtifactName: entry.resolvedArtifactName,
8140
+ onFileSelect
8141
+ }
8142
+ ),
8143
+ failureSummary ? /* @__PURE__ */ jsx("div", { className: "max-w-full truncate text-xs text-destructive", title: failureSummary, children: failureSummary }) : null
8144
+ ] }) })
8145
+ ] });
8146
+ }
8147
+ if (entry.kind === "browser_activity") {
8148
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
8149
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
8150
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8151
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-amber-500" })
8152
+ ] }),
8153
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(BrowserActivityGroup, { calls: entry.calls }) })
8154
+ ] });
8155
+ }
8156
+ if (entry.kind === "web_search_group") {
8157
+ const allComplete = entry.calls.every((tc) => tc.status === "complete");
8158
+ const anyRunning = entry.calls.some((tc) => tc.status === "running");
8159
+ const indicatorClass = anyRunning ? "bg-emerald-500 animate-pulse" : allComplete ? "bg-emerald-500" : "bg-red-500";
8160
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
8161
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
8162
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8163
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: cn("size-2 border-none", indicatorClass) })
8164
+ ] }),
8165
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(WebSearchGroupBlock, { tcs: entry.calls }) })
8166
+ ] });
8167
+ }
8168
+ if (entry.kind === "text") {
8169
+ return /* @__PURE__ */ jsx(TextEntry, { entry, step });
8170
+ }
8171
+ if (entry.kind === "skill_invoked") {
8172
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
8173
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
8174
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8175
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-emerald-500" })
8176
+ ] }),
8177
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 py-1 text-sm ", children: [
8178
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Skill" }),
8179
+ /* @__PURE__ */ jsx("span", { className: "text-foreground/60 truncate", children: entry.skill }),
8180
+ entry.stagedAt && /* @__PURE__ */ jsxs("span", { className: "text-xs text-foreground/60/70 ", children: [
8181
+ "staged at ",
8182
+ entry.stagedAt
8183
+ ] })
8184
+ ] }) })
8185
+ ] });
8186
+ }
8187
+ if (entry.kind === "artifact") {
8188
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
8189
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
8190
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8191
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-sky-500" })
8192
+ ] }),
8193
+ /* @__PURE__ */ jsx(TimelineContent, { children: sessionId ? /* @__PURE__ */ jsx(
8194
+ ArtifactBlock,
8036
8195
  {
8037
- type: "button",
8038
- variant: "ghost",
8039
- size: "icon-xs",
8040
- onClick: () => setViewerPage(pageNumber + 1),
8041
- disabled: pagesCount > 0 && pageNumber >= pagesCount,
8042
- "aria-label": "Next PDF page",
8043
- title: "Next page",
8044
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5" })
8196
+ sessionId,
8197
+ artifactId: entry.artifactId,
8198
+ name: entry.name,
8199
+ kind: entry.artifactKind,
8200
+ version: entry.version
8201
+ }
8202
+ ) : null })
8203
+ ] });
8204
+ }
8205
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
8206
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
8207
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8208
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-primary animate-pulse" })
8209
+ ] }),
8210
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: "Working on it..." }) })
8211
+ ] });
8212
+ }
8213
+ function ReasoningEntry({
8214
+ entry,
8215
+ step
8216
+ }) {
8217
+ const reasoning = useSmoothStream(entry.reasoning, entry.isStreaming);
8218
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
8219
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
8220
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8221
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2" })
8222
+ ] }),
8223
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsxs(Reasoning, { isStreaming: entry.isStreaming, children: [
8224
+ /* @__PURE__ */ jsx(ReasoningTrigger, {}),
8225
+ /* @__PURE__ */ jsx(ReasoningContent, { children: reasoning })
8226
+ ] }) })
8227
+ ] });
8228
+ }
8229
+ function TextEntry({
8230
+ entry,
8231
+ step
8232
+ }) {
8233
+ const content = useSmoothStream(entry.content, entry.isStreaming);
8234
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
8235
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
8236
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8237
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-foreground/40" })
8238
+ ] }),
8239
+ /* @__PURE__ */ jsxs(TimelineContent, { children: [
8240
+ /* @__PURE__ */ jsx(MessageResponse, { children: content }),
8241
+ entry.isFinalTurnText && entry.msg.status === "complete" && /* @__PURE__ */ jsx(TurnFeedback, { msg: entry.msg })
8242
+ ] })
8243
+ ] });
8244
+ }
8245
+ function deriveIterationLabel(message) {
8246
+ const calls = visibleToolCalls(message);
8247
+ if (calls.length === 0) {
8248
+ return message.reasoning ? "Thought through the problem" : null;
8249
+ }
8250
+ if (calls.length === 1) {
8251
+ const tc = calls[0];
8252
+ return deriveSingleToolLabel(tc);
8253
+ }
8254
+ const firstName = calls[0].toolName;
8255
+ const allSame = calls.every((tc) => tc.toolName === firstName);
8256
+ if (allSame) {
8257
+ const human = cancelledToolLabel(firstName);
8258
+ return `${human} \xD7 ${calls.length}`;
8259
+ }
8260
+ return `Used ${calls.length} tools`;
8261
+ }
8262
+ function deriveSingleToolLabel(tc) {
8263
+ const name = cancelledToolLabel(tc.toolName);
8264
+ if (_HEADER_HIDES_DETAIL.has(tc.toolName)) {
8265
+ return _HEADER_GENERIC_VERB[tc.toolName] ?? name;
8266
+ }
8267
+ const detail = extractToolDetail(tc);
8268
+ return detail ? `${name} \xB7 ${detail}` : name;
8269
+ }
8270
+ var _HEADER_HIDES_DETAIL = /* @__PURE__ */ new Set([
8271
+ "terminal",
8272
+ "execute_code",
8273
+ "memory"
8274
+ ]);
8275
+ var _HEADER_GENERIC_VERB = {
8276
+ terminal: "Ran a command",
8277
+ execute_code: "Executed code",
8278
+ memory: "Updated memory"
8279
+ };
8280
+ function extractToolDetail(tc) {
8281
+ const args = parseArgs(tc.args);
8282
+ if (!args) return null;
8283
+ const stringArg = (key) => {
8284
+ const v = args[key];
8285
+ return typeof v === "string" && v.length > 0 ? v : null;
8286
+ };
8287
+ switch (tc.toolName) {
8288
+ case "read_file":
8289
+ case "write_file":
8290
+ case "patch":
8291
+ case "list_files":
8292
+ case "search_files": {
8293
+ const path = stringArg("path") ?? stringArg("file_path");
8294
+ return path ? lastPathSegment(path) : null;
8295
+ }
8296
+ case "terminal": {
8297
+ const cmd = stringArg("command");
8298
+ return cmd ? truncate2(cmd, 40) : null;
8299
+ }
8300
+ case "execute_code": {
8301
+ const code3 = stringArg("code");
8302
+ return code3 ? truncate2(code3.split("\n")[0] ?? "", 40) : null;
8303
+ }
8304
+ case "web_search":
8305
+ case "web_crawl":
8306
+ return stringArg("query");
8307
+ case "web_extract":
8308
+ return stringArg("url");
8309
+ case "skill_view":
8310
+ case "skill_manage":
8311
+ return stringArg("name") ?? stringArg("skill");
8312
+ case "create_artifact":
8313
+ case "consult_expert":
8314
+ case "delegate_task":
8315
+ return stringArg("name") ?? stringArg("task") ?? stringArg("title");
8316
+ case "memory":
8317
+ return stringArg("action") ?? stringArg("key");
8318
+ default:
8319
+ return null;
8320
+ }
8321
+ }
8322
+ function lastPathSegment(path) {
8323
+ const cleaned = path.replace(/\/+$/, "");
8324
+ const idx = cleaned.lastIndexOf("/");
8325
+ return idx >= 0 ? cleaned.slice(idx + 1) : cleaned;
8326
+ }
8327
+ function truncate2(s, max) {
8328
+ return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
8329
+ }
8330
+ var _SIMPLE_MODE_HIDDEN_TOOLS = /* @__PURE__ */ new Set([
8331
+ "list_files",
8332
+ "search_files",
8333
+ "session_search",
8334
+ "skills_list",
8335
+ "process",
8336
+ "memory",
8337
+ // Shell commands and code execution are infrastructure plumbing the
8338
+ // user doesn't need to see when the goal is to know *what* the
8339
+ // agent accomplished, not *how*. Expert mode still has the full
8340
+ // command + output block.
8341
+ "terminal",
8342
+ "execute_code"
8343
+ ]);
8344
+ function _isHiddenSimpleTool(tc) {
8345
+ if (_SIMPLE_MODE_HIDDEN_TOOLS.has(tc.toolName)) return true;
8346
+ if (tc.toolName.startsWith("browser_")) return true;
8347
+ return false;
8348
+ }
8349
+ function visibleToolCalls(message) {
8350
+ return (message.toolCalls ?? []).filter((tc) => {
8351
+ if (_isHiddenSimpleTool(tc)) return false;
8352
+ const status = effectiveStatus(tc);
8353
+ if (status === "error" || status === "cancelled") return false;
8354
+ return true;
8355
+ });
8356
+ }
8357
+ function isIterationLive(message) {
8358
+ const calls = message.toolCalls ?? [];
8359
+ if (calls.length > 0) {
8360
+ return calls.some((tc) => tc.status === "running");
8361
+ }
8362
+ return message.status === "streaming";
8363
+ }
8364
+ function liveIterationLabel(message) {
8365
+ const running = (message.toolCalls ?? []).filter(
8366
+ (tc) => tc.status === "running" && !_isHiddenSimpleTool(tc)
8367
+ );
8368
+ if (running.length === 0) return "Thinking\u2026";
8369
+ if (running.length === 1) {
8370
+ const tc = running[0];
8371
+ const name = cancelledToolLabel(tc.toolName);
8372
+ const detail = extractToolDetail(tc);
8373
+ return detail ? `Running ${name} \xB7 ${detail}\u2026` : `Running ${name}\u2026`;
8374
+ }
8375
+ const firstName = running[0].toolName;
8376
+ const allSame = running.every((tc) => tc.toolName === firstName);
8377
+ if (allSame) {
8378
+ return `Running ${cancelledToolLabel(firstName)} \xD7 ${running.length}\u2026`;
8379
+ }
8380
+ return `Running ${running.length} tools\u2026`;
8381
+ }
8382
+ var _TOOL_ROW_ICON = {
8383
+ read_file: FileTextIcon,
8384
+ write_file: FileTextIcon,
8385
+ patch: FileEditIcon,
8386
+ list_files: ListIcon,
8387
+ search_files: SearchIcon,
8388
+ terminal: TerminalIcon,
8389
+ execute_code: TerminalIcon,
8390
+ web_search: GlobeIcon,
8391
+ web_extract: GlobeIcon,
8392
+ web_crawl: GlobeIcon,
8393
+ session_search: SearchIcon,
8394
+ skill_view: BookOpenIcon,
8395
+ skills_list: BookOpenIcon,
8396
+ skill_manage: PenLineIcon,
8397
+ consult_expert: SparklesIcon,
8398
+ delegate_task: ArrowRight,
8399
+ create_artifact: FileTextIcon,
8400
+ memory: PenLineIcon,
8401
+ todo: ListIcon
8402
+ };
8403
+ function _toolRowIcon(toolName) {
8404
+ return _TOOL_ROW_ICON[toolName] ?? WrenchIcon;
8405
+ }
8406
+ function _toolRowLabel(tc) {
8407
+ const detail = extractToolDetail(tc);
8408
+ switch (tc.toolName) {
8409
+ case "read_file":
8410
+ return detail ? `Read ${detail}` : "Read a file";
8411
+ case "write_file":
8412
+ return detail ? `Wrote ${detail}` : "Wrote a file";
8413
+ case "patch":
8414
+ return detail ? `Edited ${detail}` : "Edited a file";
8415
+ case "list_files":
8416
+ return detail ? `Listed ${detail}` : "Listed files";
8417
+ case "search_files":
8418
+ return detail ? `Searched files for "${detail}"` : "Searched files";
8419
+ case "terminal":
8420
+ return "Ran a command";
8421
+ case "execute_code":
8422
+ return "Executed code";
8423
+ case "web_search":
8424
+ return detail ? `Searched the web for "${detail}"` : "Searched the web";
8425
+ case "web_crawl":
8426
+ return detail ? `Crawled "${detail}"` : "Crawled the web";
8427
+ case "web_extract":
8428
+ return detail ? `Fetched ${detail}` : "Fetched a page";
8429
+ case "session_search":
8430
+ return detail ? `Searched session for "${detail}"` : "Searched session";
8431
+ case "skill_view":
8432
+ return detail ? `Read the ${detail} skill` : "Read a skill";
8433
+ case "skills_list":
8434
+ return "Listed available skills";
8435
+ case "skill_manage":
8436
+ return detail ? `Updated skill ${detail}` : "Managed skills";
8437
+ case "consult_expert":
8438
+ return detail ? `Consulted ${detail} expert` : "Consulted an expert";
8439
+ case "delegate_task":
8440
+ return detail ? `Delegated: ${detail}` : "Delegated a task";
8441
+ case "create_artifact":
8442
+ return detail ? `Created artifact "${detail}"` : "Created an artifact";
8443
+ case "memory":
8444
+ return detail ? `Memory ${detail}` : "Updated memory";
8445
+ case "todo":
8446
+ return detail ? `Todo ${detail}` : "Updated todo list";
8447
+ default:
8448
+ return cancelledToolLabel(tc.toolName);
8449
+ }
8450
+ }
8451
+ function SimpleDetailRow({ icon: Icon, children }) {
8452
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 text-sm", children: [
8453
+ /* @__PURE__ */ jsx(
8454
+ Icon,
8455
+ {
8456
+ className: "mt-0.5 size-4 shrink-0 text-foreground/60",
8457
+ "aria-hidden": true
8458
+ }
8459
+ ),
8460
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 leading-relaxed text-foreground/60", children })
8461
+ ] });
8462
+ }
8463
+ function ClampedReasoning({
8464
+ text,
8465
+ previewParagraphs = 2
8466
+ }) {
8467
+ const paragraphs = splitReasoningParagraphs(text);
8468
+ const [expanded, setExpanded] = useState(false);
8469
+ const hasMore = paragraphs.length > previewParagraphs;
8470
+ const visible = expanded || !hasMore ? paragraphs : paragraphs.slice(0, previewParagraphs);
8471
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
8472
+ visible.map((paragraph, i) => /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: paragraph }, i)),
8473
+ hasMore && /* @__PURE__ */ jsx(
8474
+ "button",
8475
+ {
8476
+ type: "button",
8477
+ onClick: () => setExpanded((prev) => !prev),
8478
+ className: "text-xs font-medium text-foreground hover:text-foreground/70 cursor-pointer",
8479
+ children: expanded ? "Show less" : "Show more..."
8480
+ }
8481
+ )
8482
+ ] });
8483
+ }
8484
+ function splitReasoningParagraphs(text) {
8485
+ const trimmed = text.trim();
8486
+ if (!trimmed) return [];
8487
+ const blankSplit = trimmed.split(/\n\s*\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
8488
+ if (blankSplit.length > 1) return blankSplit;
8489
+ return trimmed.split(/\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
8490
+ }
8491
+ function IterationExpanded({
8492
+ message
8493
+ }) {
8494
+ const calls = visibleToolCalls(message);
8495
+ const reasoning = (message.reasoning ?? "").trim();
8496
+ const anyVisibleRunning = calls.some((tc) => tc.status === "running");
8497
+ const showDone = !anyVisibleRunning && (calls.length > 0 || !!reasoning);
8498
+ if (!reasoning && calls.length === 0) return null;
8499
+ return /* @__PURE__ */ jsxs("div", { className: "ml-2 space-y-3 border-l border-border/40 pl-4 py-1", children: [
8500
+ reasoning && /* @__PURE__ */ jsx(SimpleDetailRow, { icon: ClockIcon, children: /* @__PURE__ */ jsx(ClampedReasoning, { text: reasoning }) }),
8501
+ calls.map((tc) => {
8502
+ if (tc.toolName === "todo") {
8503
+ return /* @__PURE__ */ jsx("div", { className: "pr-1", children: /* @__PURE__ */ jsx(TodoToolBlock, { tc }) }, tc.id);
8504
+ }
8505
+ return /* @__PURE__ */ jsx(SimpleDetailRow, { icon: _toolRowIcon(tc.toolName), children: /* @__PURE__ */ jsx("span", { children: _toolRowLabel(tc) }) }, tc.id);
8506
+ }),
8507
+ showDone && /* @__PURE__ */ jsx(SimpleDetailRow, { icon: CircleCheckIcon, children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children: "Done" }) })
8508
+ ] });
8509
+ }
8510
+ function IterationGroup({
8511
+ message,
8512
+ sessionId,
8513
+ artifactFallbacks,
8514
+ onFileSelect
8515
+ }) {
8516
+ const summary = message.iterationSummary?.summary;
8517
+ const [open, setOpen] = useState(false);
8518
+ if (isIterationLive(message)) {
8519
+ const askCall = (message.toolCalls ?? []).find(
8520
+ (tc) => tc.toolName === "ask_user_question" && tc.status === "running"
8521
+ );
8522
+ if (askCall) {
8523
+ return /* @__PURE__ */ jsx("div", { className: "px-1 py-0.5", children: /* @__PURE__ */ jsx(AskUserQuestionToolBlock, { tc: askCall }) });
8524
+ }
8525
+ const label2 = liveIterationLabel(message);
8526
+ return /* @__PURE__ */ jsx("div", { className: "px-1 py-0.5 text-sm", children: /* @__PURE__ */ jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: label2 }) });
8527
+ }
8528
+ const label = summary ?? deriveIterationLabel(message);
8529
+ if (!label) {
8530
+ return null;
8531
+ }
8532
+ const labelTone = summary ? "text-foreground/80" : "text-foreground/50";
8533
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
8534
+ /* @__PURE__ */ jsxs(
8535
+ "button",
8536
+ {
8537
+ type: "button",
8538
+ onClick: () => setOpen((prev) => !prev),
8539
+ "aria-expanded": open,
8540
+ className: "flex w-full items-center gap-2 rounded px-1 py-0.5 text-left text-sm hover:bg-muted/40",
8541
+ children: [
8542
+ /* @__PURE__ */ jsx("span", { className: cn("flex-1 truncate", labelTone), children: label }),
8543
+ /* @__PURE__ */ jsx(
8544
+ ChevronDown,
8545
+ {
8546
+ className: cn(
8547
+ "size-3 shrink-0 text-foreground/60 transition-transform",
8548
+ open && "rotate-180"
8549
+ ),
8550
+ "aria-hidden": true
8551
+ }
8552
+ )
8553
+ ]
8554
+ }
8555
+ ),
8556
+ open && /* @__PURE__ */ jsx(
8557
+ IterationExpanded,
8558
+ {
8559
+ message,
8560
+ sessionId,
8561
+ artifactFallbacks,
8562
+ onFileSelect
8563
+ }
8564
+ )
8565
+ ] });
8566
+ }
8567
+ function deriveFileArtifactsFromMessages(messages) {
8568
+ const seen = /* @__PURE__ */ new Set();
8569
+ const out = [];
8570
+ for (const msg of messages) {
8571
+ if (msg.role !== "assistant" || !msg.toolCalls) continue;
8572
+ for (const tc of msg.toolCalls) {
8573
+ const status = effectiveStatus(tc);
8574
+ if (status === "error" || status === "cancelled") continue;
8575
+ const args = parseArgs(tc.args);
8576
+ if (!args) continue;
8577
+ if (tc.toolName === "write_file" || tc.toolName === "patch") {
8578
+ const path = typeof args.path === "string" ? args.path : typeof args.file_path === "string" ? args.file_path : "";
8579
+ if (!path || seen.has(`file:${path}`)) continue;
8580
+ seen.add(`file:${path}`);
8581
+ out.push({
8582
+ kind: "file",
8583
+ label: path.split("/").pop() || path,
8584
+ ref: path
8585
+ });
8586
+ continue;
8587
+ }
8588
+ if (tc.toolName === "create_artifact") {
8589
+ const name = typeof args.name === "string" ? args.name : "";
8590
+ if (!name || seen.has(`artifact:${name}`)) continue;
8591
+ seen.add(`artifact:${name}`);
8592
+ out.push({
8593
+ kind: "artifact",
8594
+ label: name,
8595
+ ref: name
8596
+ });
8597
+ }
8598
+ }
8599
+ }
8600
+ return out;
8601
+ }
8602
+ function SimpleAssistantGroup({
8603
+ messages,
8604
+ lastGlobalIndex,
8605
+ totalMessages,
8606
+ isRunning,
8607
+ sessionId,
8608
+ artifactFallbacks,
8609
+ onFileSelect,
8610
+ onRetry
8611
+ }) {
8612
+ const assistantMessages = messages.filter((m) => m.role === "assistant");
8613
+ const tail = assistantMessages[assistantMessages.length - 1];
8614
+ const tailHasTools = !!(tail?.toolCalls && tail.toolCalls.length > 0);
8615
+ const tailIsTextOnly = !!tail && !tailHasTools;
8616
+ const finalText = tailIsTextOnly && tail.content ? tail.content : "";
8617
+ const isTailGroup = lastGlobalIndex === totalMessages - 1;
8618
+ const showThinkingShim = isTailGroup && isRunning && (!tail && messages.some((m) => m.role !== "assistant") || tailIsTextOnly && tail.status === "streaming" && !tail.content);
8619
+ const showErrorInfo = !!tail && tail.status === "error" && !!tail.errorInfo;
8620
+ const effectiveTurnSummary = (() => {
8621
+ const fromHarness = tail?.turnSummary;
8622
+ if (fromHarness && fromHarness.artifacts.length > 0) {
8623
+ return fromHarness;
8624
+ }
8625
+ const derived = deriveFileArtifactsFromMessages(messages);
8626
+ if (derived.length === 0) return fromHarness;
8627
+ return {
8628
+ turnId: fromHarness?.turnId ?? "",
8629
+ recap: fromHarness?.recap ?? "",
8630
+ artifacts: derived
8631
+ };
8632
+ })();
8633
+ return /* @__PURE__ */ jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsxs(MessageContent, { children: [
8634
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
8635
+ messages.map((message) => {
8636
+ if (message.role === "system") {
8637
+ return /* @__PURE__ */ jsx(
8638
+ OrphanSystemMarker,
8639
+ {
8640
+ message,
8641
+ sessionId,
8642
+ onRetry: void 0
8643
+ },
8644
+ message.id
8645
+ );
8646
+ }
8647
+ if (message === tail && tailIsTextOnly) return null;
8648
+ return /* @__PURE__ */ jsx(
8649
+ IterationGroup,
8650
+ {
8651
+ message,
8652
+ sessionId,
8653
+ artifactFallbacks,
8654
+ onFileSelect
8655
+ },
8656
+ message.id
8657
+ );
8658
+ }),
8659
+ showThinkingShim && /* @__PURE__ */ jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: "Working on it..." })
8660
+ ] }),
8661
+ finalText && /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
8662
+ /* @__PURE__ */ jsx(MessageResponse, { children: finalText }),
8663
+ tail?.status === "complete" && /* @__PURE__ */ jsx(TurnFeedback, { msg: tail })
8664
+ ] }),
8665
+ effectiveTurnSummary && /* @__PURE__ */ jsx(
8666
+ TurnSummaryCard,
8667
+ {
8668
+ summary: effectiveTurnSummary,
8669
+ sessionId,
8670
+ messages,
8671
+ onFileSelect
8672
+ }
8673
+ ),
8674
+ showErrorInfo && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(ErrorMessage, { errorInfo: tail.errorInfo, onRetry }) })
8675
+ ] }) });
8676
+ }
8677
+ function AssistantGroup({
8678
+ messages,
8679
+ lastGlobalIndex,
8680
+ totalMessages,
8681
+ isRunning,
8682
+ sessionId,
8683
+ artifactFallbacks,
8684
+ onFileSelect,
8685
+ onRetry,
8686
+ viewMode = "simple"
8687
+ }) {
8688
+ if (viewMode === "simple") {
8689
+ return /* @__PURE__ */ jsx(
8690
+ SimpleAssistantGroup,
8691
+ {
8692
+ messages,
8693
+ lastGlobalIndex,
8694
+ totalMessages,
8695
+ isRunning,
8696
+ sessionId,
8697
+ artifactFallbacks,
8698
+ onFileSelect,
8699
+ onRetry
8700
+ }
8701
+ );
8702
+ }
8703
+ let entries = [];
8704
+ for (let i = 0; i < messages.length; i++) {
8705
+ const isLast = i === messages.length - 1 && lastGlobalIndex === totalMessages - 1;
8706
+ entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
8707
+ }
8708
+ entries = groupBrowserActivityEntries(entries);
8709
+ entries = groupWebSearchEntries(entries);
8710
+ for (let i = entries.length - 1; i >= 0; i--) {
8711
+ const entry = entries[i];
8712
+ if (entry?.kind === "text") {
8713
+ entries[i] = { ...entry, isFinalTurnText: true };
8714
+ break;
8715
+ }
8716
+ }
8717
+ const isTailGroup = lastGlobalIndex === totalMessages - 1;
8718
+ const lastEntry = entries[entries.length - 1];
8719
+ const hasRunningTool = entries.some(
8720
+ (e) => e.kind === "tool" && e.tc.status === "running"
8721
+ );
8722
+ const tailMsg = messages[messages.length - 1];
8723
+ if (isTailGroup && isRunning && !hasRunningTool && lastEntry?.kind !== "thinking") {
8724
+ entries.push({ kind: "thinking", key: `${tailMsg.id}-tail-thinking` });
8725
+ }
8726
+ const tail = messages[messages.length - 1];
8727
+ const showErrorInfo = tail && tail.role === "assistant" && tail.status === "error" && !!tail.errorInfo;
8728
+ return /* @__PURE__ */ jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsxs(MessageContent, { children: [
8729
+ /* @__PURE__ */ jsx(Timeline, { defaultValue: 999, className: "gap-0", children: entries.map((entry, i) => /* @__PURE__ */ jsx(
8730
+ TimelineEntryItem,
8731
+ {
8732
+ entry,
8733
+ step: i + 1,
8734
+ sessionId,
8735
+ onFileSelect
8736
+ },
8737
+ entry.key
8738
+ )) }),
8739
+ showErrorInfo && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(ErrorMessage, { errorInfo: tail.errorInfo, onRetry }) })
8740
+ ] }) });
8741
+ }
8742
+ function RetryBanner({ indicator }) {
8743
+ const [open, setOpen] = useState(false);
8744
+ return /* @__PURE__ */ jsxs(
8745
+ "div",
8746
+ {
8747
+ role: "status",
8748
+ className: "flex flex-col gap-1 border-l-2 border-amber-500 bg-amber-500/5 px-3 py-2 text-xs",
8749
+ children: [
8750
+ /* @__PURE__ */ jsxs(
8751
+ "button",
8752
+ {
8753
+ type: "button",
8754
+ onClick: () => setOpen((prev) => !prev),
8755
+ className: "flex items-center gap-2 text-left text-amber-600 hover:text-amber-700",
8756
+ "aria-expanded": open,
8757
+ children: [
8758
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "size-3 shrink-0" }),
8759
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate font-medium", children: indicator.title }),
8760
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-foreground/60", children: [
8761
+ "attempt ",
8762
+ indicator.attempt
8763
+ ] }),
8764
+ indicator.detail && (open ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-3" }))
8765
+ ]
8766
+ }
8767
+ ),
8768
+ open && indicator.detail && /* @__PURE__ */ jsx("pre", { className: "mt-1 overflow-x-auto rounded-none bg-background p-2 font-mono text-[11px] whitespace-pre-wrap wrap-break-word text-foreground/60", children: indicator.detail })
8769
+ ]
8770
+ }
8771
+ );
8772
+ }
8773
+ function ChatThread({
8774
+ sessionId,
8775
+ messages,
8776
+ isRunning,
8777
+ isLoadingHistory = false,
8778
+ onSend,
8779
+ onStop,
8780
+ onFileSelect,
8781
+ disabled = false,
8782
+ disabledReason,
8783
+ tokenUsage,
8784
+ retryIndicator,
8785
+ onRetry,
8786
+ onComposerError,
8787
+ showBrowser = false,
8788
+ onToggleBrowser,
8789
+ showWorkspace = false,
8790
+ onToggleWorkspace,
8791
+ canShowBrowser = false,
8792
+ canShowWorkspace = false,
8793
+ viewMode = "simple",
8794
+ onViewModeChange
8795
+ }) {
8796
+ const groups = useMemo(() => groupMessages(messages), [messages]);
8797
+ const artifactFallbacks = useMemo(() => {
8798
+ const fallbacks = {};
8799
+ const createArtifactToolCallIds = [];
8800
+ let artifactIdx = 0;
8801
+ for (const msg of messages) {
8802
+ if (msg.role === "assistant" && msg.toolCalls) {
8803
+ for (const tc of msg.toolCalls) {
8804
+ if (tc.toolName === "create_artifact" && !tc.cancelled && tc.status !== "error") {
8805
+ createArtifactToolCallIds.push(tc.id);
8806
+ }
8045
8807
  }
8046
- ),
8047
- /* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
8048
- /* @__PURE__ */ jsx(
8049
- Button,
8050
- {
8051
- type: "button",
8052
- variant: "ghost",
8053
- size: "icon-xs",
8054
- onClick: () => setViewerScale(scale - 0.1),
8055
- disabled: scale <= 0.5,
8056
- "aria-label": "Zoom out PDF",
8057
- title: "Zoom out",
8058
- children: /* @__PURE__ */ jsx(MinusIcon, { className: "size-3.5" })
8808
+ } else if (msg.role === "system" && msg.systemKind === "artifact") {
8809
+ const tcId = createArtifactToolCallIds[artifactIdx];
8810
+ if (tcId) {
8811
+ const { name } = unpackArtifactMeta(msg.systemMeta, msg.content);
8812
+ if (name) fallbacks[tcId] = name;
8059
8813
  }
8060
- ),
8061
- /* @__PURE__ */ jsxs(
8062
- "button",
8814
+ artifactIdx += 1;
8815
+ }
8816
+ }
8817
+ return fallbacks;
8818
+ }, [messages]);
8819
+ const activeFailureId = useMemo(() => {
8820
+ if (messages.length === 0) return null;
8821
+ const tail = messages[messages.length - 1];
8822
+ if (tail.role === "system" && tail.systemKind === "error" && tail.errorInfo) {
8823
+ return tail.id;
8824
+ }
8825
+ if (tail.role === "assistant" && tail.status === "error" && tail.errorInfo) {
8826
+ return tail.id;
8827
+ }
8828
+ return null;
8829
+ }, [messages]);
8830
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col overflow-hidden bg-background text-sm", children: [
8831
+ /* @__PURE__ */ jsxs(Conversation, { className: "relative flex-1 min-h-0", children: [
8832
+ /* @__PURE__ */ jsx(ConversationContent, { className: "mx-auto w-full max-w-4xl", children: messages.length === 0 && isLoadingHistory ? /* @__PURE__ */ jsx(
8833
+ ConversationEmptyState,
8063
8834
  {
8064
- type: "button",
8065
- className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
8066
- onClick: () => {
8067
- const viewer = pdfViewerRef.current;
8068
- if (!viewer) return;
8069
- viewer.currentScaleValue = "page-width";
8070
- setScale(viewer.currentScale || 1);
8071
- },
8072
- "aria-label": "Fit PDF to width",
8073
- title: "Fit width",
8074
- children: [
8075
- Math.round(scale * 100),
8076
- "%"
8077
- ]
8835
+ icon: /* @__PURE__ */ jsx(MessageSquareIcon, { className: "size-8 opacity-40" }),
8836
+ title: "Loading conversation",
8837
+ description: "Fetching the session history."
8078
8838
  }
8079
- ),
8080
- /* @__PURE__ */ jsx(
8081
- Button,
8839
+ ) : messages.length === 0 && !disabled ? /* @__PURE__ */ jsx(
8840
+ ConversationEmptyState,
8082
8841
  {
8083
- type: "button",
8084
- variant: "ghost",
8085
- size: "icon-xs",
8086
- onClick: () => setViewerScale(scale + 0.1),
8087
- disabled: scale >= 3,
8088
- "aria-label": "Zoom in PDF",
8089
- title: "Zoom in",
8090
- children: /* @__PURE__ */ jsx(PlusIcon, { className: "size-3.5" })
8842
+ icon: /* @__PURE__ */ jsx(MessageSquareIcon, { className: "size-8 opacity-40" }),
8843
+ title: "Start a conversation",
8844
+ description: "Ask me anything \u2014 I can search, analyze, write code, and more."
8091
8845
  }
8092
- ),
8093
- /* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
8094
- /* @__PURE__ */ jsx(SearchIcon, { className: "size-3.5 text-muted-foreground" }),
8095
- /* @__PURE__ */ jsx(
8096
- Input,
8846
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
8847
+ groups.map((group) => {
8848
+ if (group.role === "user") {
8849
+ const msg = group.messages[0];
8850
+ return /* @__PURE__ */ jsx(
8851
+ ChatMessage,
8852
+ {
8853
+ message: msg,
8854
+ onFileSelect
8855
+ },
8856
+ msg.id
8857
+ );
8858
+ }
8859
+ if (group.role === "system") {
8860
+ const msg = group.messages[0];
8861
+ const groupRetry2 = msg.id === activeFailureId ? onRetry : void 0;
8862
+ return /* @__PURE__ */ jsx(
8863
+ OrphanSystemMarker,
8864
+ {
8865
+ message: msg,
8866
+ sessionId,
8867
+ onRetry: groupRetry2
8868
+ },
8869
+ msg.id
8870
+ );
8871
+ }
8872
+ const groupTail = group.messages[group.messages.length - 1];
8873
+ const groupRetry = groupTail.id === activeFailureId ? onRetry : void 0;
8874
+ return /* @__PURE__ */ jsx(
8875
+ AssistantGroup,
8876
+ {
8877
+ messages: group.messages,
8878
+ lastGlobalIndex: group.lastGlobalIndex,
8879
+ totalMessages: messages.length,
8880
+ isRunning,
8881
+ sessionId,
8882
+ artifactFallbacks,
8883
+ onFileSelect,
8884
+ onRetry: groupRetry,
8885
+ viewMode
8886
+ },
8887
+ group.messages[0].id
8888
+ );
8889
+ }),
8890
+ isRunning && messages.length > 0 && messages[messages.length - 1].role === "user" && /* @__PURE__ */ jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: "Working on it..." }) }) })
8891
+ ] }) }),
8892
+ /* @__PURE__ */ jsx(ConversationScrollButton, {})
8893
+ ] }),
8894
+ /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
8895
+ retryIndicator && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(RetryBanner, { indicator: retryIndicator }) }),
8896
+ disabled && disabledReason ? /* @__PURE__ */ jsx(
8897
+ "div",
8097
8898
  {
8098
- type: "search",
8099
- value: query,
8100
- onChange: (event) => setQuery(event.target.value),
8101
- onKeyDown: (event) => {
8102
- if (event.key === "Enter") runFind(event.shiftKey);
8103
- },
8104
- placeholder: "Find",
8105
- "aria-label": "Find in PDF",
8106
- className: "h-7 w-28 border-input px-1 text-xs"
8899
+ className: "rounded border border-line bg-muted/40 px-3 py-2 text-sm text-foreground/60",
8900
+ role: "status",
8901
+ children: disabledReason
8107
8902
  }
8108
- ),
8109
- /* @__PURE__ */ jsx(
8110
- Button,
8903
+ ) : /* @__PURE__ */ jsx(
8904
+ ChatComposer,
8111
8905
  {
8112
- type: "button",
8113
- variant: "ghost",
8114
- size: "xs",
8115
- onClick: () => runFind(false),
8116
- disabled: !query.trim(),
8117
- children: "Find"
8906
+ onSend,
8907
+ onStop,
8908
+ isRunning,
8909
+ disabled,
8910
+ disabledReason,
8911
+ tokenUsage,
8912
+ onComposerError,
8913
+ showBrowser,
8914
+ onToggleBrowser,
8915
+ showWorkspace,
8916
+ onToggleWorkspace,
8917
+ canShowBrowser,
8918
+ canShowWorkspace,
8919
+ viewMode,
8920
+ onViewModeChange
8118
8921
  }
8119
8922
  )
8120
- ] }),
8121
- error && /* @__PURE__ */ jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
8122
- /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
8123
- /* @__PURE__ */ jsx("span", { children: error })
8124
- ] }),
8125
- !error && /* @__PURE__ */ jsx("div", { className: "relative h-[70vh] min-h-[420px] flex-1 bg-muted/20", children: /* @__PURE__ */ jsx(
8923
+ ] })
8924
+ ] });
8925
+ }
8926
+ var noop = () => {
8927
+ };
8928
+ var FileTreeContext = createContext({
8929
+ expandedPaths: /* @__PURE__ */ new Set(),
8930
+ togglePath: noop
8931
+ });
8932
+ function FileTree({
8933
+ expanded: controlledExpanded,
8934
+ defaultExpanded = /* @__PURE__ */ new Set(),
8935
+ selectedPath,
8936
+ onSelect,
8937
+ onExpandedChange,
8938
+ className,
8939
+ children,
8940
+ ...props
8941
+ }) {
8942
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
8943
+ const expandedPaths = controlledExpanded ?? internalExpanded;
8944
+ const togglePath = useCallback(
8945
+ (path) => {
8946
+ const next = new Set(expandedPaths);
8947
+ if (next.has(path)) {
8948
+ next.delete(path);
8949
+ } else {
8950
+ next.add(path);
8951
+ }
8952
+ setInternalExpanded(next);
8953
+ onExpandedChange?.(next);
8954
+ },
8955
+ [expandedPaths, onExpandedChange]
8956
+ );
8957
+ const contextValue = useMemo(
8958
+ () => ({
8959
+ expandedPaths,
8960
+ onSelect,
8961
+ selectedPath: selectedPath ?? void 0,
8962
+ togglePath
8963
+ }),
8964
+ [expandedPaths, onSelect, selectedPath, togglePath]
8965
+ );
8966
+ return /* @__PURE__ */ jsx(FileTreeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
8967
+ "div",
8968
+ {
8969
+ className: cn("rounded-lg border bg-background text-sm", className),
8970
+ role: "tree",
8971
+ ...props,
8972
+ children: /* @__PURE__ */ jsx("div", { className: "p-2", children })
8973
+ }
8974
+ ) });
8975
+ }
8976
+ function FileTreeIcon({
8977
+ className,
8978
+ children,
8979
+ ...props
8980
+ }) {
8981
+ return /* @__PURE__ */ jsx("span", { className: cn("shrink-0", className), ...props, children });
8982
+ }
8983
+ function FileTreeName({
8984
+ className,
8985
+ children,
8986
+ ...props
8987
+ }) {
8988
+ return /* @__PURE__ */ jsx("span", { className: cn("truncate", className), ...props, children });
8989
+ }
8990
+ function FileTreeFolder({
8991
+ path,
8992
+ name,
8993
+ className,
8994
+ children,
8995
+ ...props
8996
+ }) {
8997
+ const { expandedPaths, togglePath, selectedPath, onSelect } = useContext(FileTreeContext);
8998
+ const isExpanded = expandedPaths.has(path);
8999
+ const isSelected = selectedPath === path;
9000
+ const handleOpenChange = useCallback(() => {
9001
+ togglePath(path);
9002
+ }, [togglePath, path]);
9003
+ const handleSelect = useCallback(() => {
9004
+ onSelect?.(path);
9005
+ }, [onSelect, path]);
9006
+ return /* @__PURE__ */ jsx(Collapsible, { onOpenChange: handleOpenChange, open: isExpanded, children: /* @__PURE__ */ jsxs("div", { className: cn("", className), role: "treeitem", tabIndex: 0, ...props, children: [
9007
+ /* @__PURE__ */ jsxs(
8126
9008
  "div",
8127
9009
  {
8128
- ref: containerRef,
8129
- "aria-label": `PDF viewer for ${fileName2}`,
8130
- className: "absolute inset-0 overflow-auto",
8131
- children: /* @__PURE__ */ jsx("div", { ref: viewerRef, className: "pdfViewer" })
9010
+ className: cn(
9011
+ "flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50",
9012
+ isSelected && "bg-muted"
9013
+ ),
9014
+ children: [
9015
+ /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
9016
+ "button",
9017
+ {
9018
+ className: "flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0",
9019
+ type: "button",
9020
+ "aria-label": isExpanded ? `Collapse ${name}` : `Expand ${name}`,
9021
+ children: /* @__PURE__ */ jsx(
9022
+ ChevronRightIcon,
9023
+ {
9024
+ className: cn(
9025
+ "size-4 shrink-0 text-muted-foreground transition-transform",
9026
+ isExpanded && "rotate-90"
9027
+ )
9028
+ }
9029
+ )
9030
+ }
9031
+ ) }),
9032
+ /* @__PURE__ */ jsxs(
9033
+ "button",
9034
+ {
9035
+ className: "flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left",
9036
+ onClick: handleSelect,
9037
+ type: "button",
9038
+ children: [
9039
+ /* @__PURE__ */ jsx(FileTreeIcon, { children: isExpanded ? /* @__PURE__ */ jsx(FolderOpenIcon, { className: "size-4 text-blue-500" }) : /* @__PURE__ */ jsx(FolderIcon, { className: "size-4 text-blue-500" }) }),
9040
+ /* @__PURE__ */ jsx(FileTreeName, { children: name })
9041
+ ]
9042
+ }
9043
+ )
9044
+ ]
8132
9045
  }
8133
- ) })
8134
- ] });
8135
- }
8136
- function decodeBase64(content) {
8137
- const binary = globalThis.atob(content);
8138
- const bytes = new Uint8Array(binary.length);
8139
- for (let index = 0; index < binary.length; index += 1) {
8140
- bytes[index] = binary.charCodeAt(index);
8141
- }
8142
- return bytes;
9046
+ ),
9047
+ /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "ml-4 border-l border-muted-foreground/50 pl-2", children }) })
9048
+ ] }) });
8143
9049
  }
8144
- function formatPdfPreviewError(error) {
8145
- if (error instanceof Error && (error.name === "InvalidCharacterError" || error.message === "Invalid character")) {
8146
- return "PDF preview data is not valid base64.";
8147
- }
8148
- return error instanceof Error ? error.message : "Failed to render PDF preview.";
9050
+ function FileTreeFile({
9051
+ path,
9052
+ name,
9053
+ icon,
9054
+ className,
9055
+ children,
9056
+ ...props
9057
+ }) {
9058
+ const { selectedPath, onSelect } = useContext(FileTreeContext);
9059
+ const isSelected = selectedPath === path;
9060
+ const handleClick = useCallback(() => {
9061
+ onSelect?.(path);
9062
+ }, [onSelect, path]);
9063
+ const handleKeyDown = useCallback(
9064
+ (event) => {
9065
+ if (event.key === "Enter" || event.key === " ") {
9066
+ onSelect?.(path);
9067
+ }
9068
+ },
9069
+ [onSelect, path]
9070
+ );
9071
+ return /* @__PURE__ */ jsx(
9072
+ "div",
9073
+ {
9074
+ className: cn(
9075
+ "group flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50",
9076
+ isSelected && "bg-muted",
9077
+ className
9078
+ ),
9079
+ onClick: handleClick,
9080
+ onKeyDown: handleKeyDown,
9081
+ role: "treeitem",
9082
+ tabIndex: 0,
9083
+ ...props,
9084
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
9085
+ /* @__PURE__ */ jsx("span", { className: "size-4 shrink-0" }),
9086
+ /* @__PURE__ */ jsx(FileTreeIcon, { children: icon ?? /* @__PURE__ */ jsx(FileIcon, { className: "size-4 text-muted-foreground" }) }),
9087
+ /* @__PURE__ */ jsx(FileTreeName, { children: name })
9088
+ ] })
9089
+ }
9090
+ );
8149
9091
  }
8150
9092
  var SKELETON_WIDTHS2 = [75, 60, 90, 65, 80, 70, 85, 55];
8151
9093
  var DEFAULT_WIDTH = 400;
@@ -8574,7 +9516,7 @@ function WorkspacePanel({
8574
9516
  }
8575
9517
  ),
8576
9518
  /* @__PURE__ */ jsx(
8577
- ConfirmDialog,
9519
+ ConfirmDialog2,
8578
9520
  {
8579
9521
  open: deleteTarget !== null,
8580
9522
  title: "Delete file?",
@@ -8588,7 +9530,7 @@ function WorkspacePanel({
8588
9530
  }
8589
9531
  );
8590
9532
  }
8591
- function ConfirmDialog({
9533
+ function ConfirmDialog2({
8592
9534
  open,
8593
9535
  title,
8594
9536
  description,
@@ -8664,7 +9606,9 @@ var AGENT_CHAT_LISTENED_EVENTS = [
8664
9606
  "skill.invoked",
8665
9607
  "policy.denied",
8666
9608
  "stream.timeout",
9609
+ "expert.delegation",
8667
9610
  "expert.result",
9611
+ "expert.failure",
8668
9612
  "expert.endorse",
8669
9613
  "expert.override",
8670
9614
  "user.feedback",
@@ -8674,7 +9618,9 @@ var AGENT_CHAT_LISTENED_EVENTS = [
8674
9618
  "browser.destroyed",
8675
9619
  "browser.control_granted",
8676
9620
  "browser.control_returned",
8677
- "clarify.response"
9621
+ "ask_user_question.response",
9622
+ "iteration.summary",
9623
+ "turn.summary"
8678
9624
  ];
8679
9625
 
8680
9626
  // src/runtime/reducer.ts
@@ -8699,7 +9645,8 @@ function createInitialAgentChatState(options = {}) {
8699
9645
  hadDeltas: false,
8700
9646
  terminal: false,
8701
9647
  workspaceRefreshKey: 0,
8702
- browser: null
9648
+ browser: null,
9649
+ viewMode: options.viewMode ?? "simple"
8703
9650
  };
8704
9651
  }
8705
9652
  function applyAgentChatEvent(state, event) {
@@ -8712,11 +9659,10 @@ function applyAgentChatEvent(state, event) {
8712
9659
  nextState = applyRetryIndicator(nextState, event);
8713
9660
  switch (event.type) {
8714
9661
  case "user.message":
8715
- return withMessages(nextState, applyUserMessage(
8716
- nextState.messages,
8717
- event.eventId,
8718
- event.data
8719
- ));
9662
+ return withMessages(
9663
+ { ...nextState, terminal: false, isRunning: true },
9664
+ applyUserMessage(nextState.messages, event.eventId, event.data)
9665
+ );
8720
9666
  case "skill.invoked":
8721
9667
  return withMessages(nextState, [
8722
9668
  ...nextState.messages,
@@ -8791,7 +9737,7 @@ function applyAgentChatEvent(state, event) {
8791
9737
  }
8792
9738
  case "harness.wake":
8793
9739
  case "llm.request":
8794
- return nextState.terminal ? nextState : { ...nextState, isRunning: true };
9740
+ return { ...nextState, terminal: false, isRunning: true };
8795
9741
  case "harness.crash":
8796
9742
  case "stream.timeout":
8797
9743
  return nextState;
@@ -8815,10 +9761,17 @@ function applyAgentChatEvent(state, event) {
8815
9761
  };
8816
9762
  }
8817
9763
  return nextState;
9764
+ case "expert.delegation":
9765
+ return applyExpertDelegation(nextState, event);
8818
9766
  case "expert.result":
8819
9767
  return withMessages(
8820
9768
  nextState,
8821
- applyExpertResult(nextState.messages, event.eventId)
9769
+ applyExpertResult(nextState.messages, event.eventId, event.data)
9770
+ );
9771
+ case "expert.failure":
9772
+ return withMessages(
9773
+ nextState,
9774
+ applyExpertFailure(nextState.messages, event.eventId, event.data)
8822
9775
  );
8823
9776
  case "expert.endorse":
8824
9777
  case "expert.override":
@@ -8831,15 +9784,19 @@ function applyAgentChatEvent(state, event) {
8831
9784
  nextState,
8832
9785
  applyUserFeedback(nextState.messages, event.data)
8833
9786
  );
8834
- case "clarify.response":
9787
+ case "ask_user_question.response":
8835
9788
  return withMessages(
8836
9789
  nextState,
8837
- applyClarifyResponse(nextState.messages, event.data)
9790
+ applyAskUserQuestionResponse(nextState.messages, event.data)
8838
9791
  );
8839
9792
  case "policy.denied":
8840
9793
  return applyPolicyDenied(nextState, event.data);
8841
9794
  case "session.start":
8842
9795
  return nextState;
9796
+ case "iteration.summary":
9797
+ return applyIterationSummary(nextState, event);
9798
+ case "turn.summary":
9799
+ return applyTurnSummary(nextState, event);
8843
9800
  }
8844
9801
  }
8845
9802
  function applyRetryIndicator(state, event) {
@@ -8972,6 +9929,7 @@ function browserMarker(event) {
8972
9929
  function applyLlmDelta(state, event) {
8973
9930
  const deltaContent = stringValue2(event.data.content);
8974
9931
  const deltaReasoning = stringValue2(event.data.reasoning);
9932
+ const { turnId, iterationIndex } = readTurnMeta(event.data);
8975
9933
  const messages = [...state.messages];
8976
9934
  const lastIdx = findLastAssistantIndex(messages);
8977
9935
  const lastMsg = lastIdx >= 0 ? messages[lastIdx] : null;
@@ -8986,7 +9944,9 @@ function applyLlmDelta(state, event) {
8986
9944
  messages[lastIdx] = {
8987
9945
  ...lastMsg,
8988
9946
  content: deltaContent ? lastMsg.content + deltaContent : lastMsg.content,
8989
- reasoning: deltaReasoning ? (lastMsg.reasoning ?? "") + deltaReasoning : lastMsg.reasoning
9947
+ reasoning: deltaReasoning ? (lastMsg.reasoning ?? "") + deltaReasoning : lastMsg.reasoning,
9948
+ turnId: turnId ?? lastMsg.turnId,
9949
+ iterationIndex: iterationIndex ?? lastMsg.iterationIndex
8990
9950
  };
8991
9951
  } else {
8992
9952
  messages.push({
@@ -8995,14 +9955,17 @@ function applyLlmDelta(state, event) {
8995
9955
  content: deltaContent,
8996
9956
  reasoning: deltaReasoning || void 0,
8997
9957
  createdAt: /* @__PURE__ */ new Date(),
8998
- status: "streaming"
9958
+ status: "streaming",
9959
+ turnId,
9960
+ iterationIndex
8999
9961
  });
9000
9962
  }
9001
9963
  return {
9002
9964
  ...state,
9003
9965
  messages,
9004
9966
  hadDeltas: state.hadDeltas || Boolean(deltaContent),
9005
- isRunning: state.terminal ? state.isRunning : true
9967
+ terminal: false,
9968
+ isRunning: true
9006
9969
  };
9007
9970
  }
9008
9971
  function applyLlmResponse(state, event) {
@@ -9012,6 +9975,7 @@ function applyLlmResponse(state, event) {
9012
9975
  const toolCalls = Array.isArray(message?.tool_calls) ? message?.tool_calls : [];
9013
9976
  const hasToolCalls = toolCalls.length > 0;
9014
9977
  const responseToolCallIds = toolCallIdsFromResponse(toolCalls);
9978
+ const { turnId, iterationIndex } = readTurnMeta(event.data);
9015
9979
  const idx = findLastAssistantIndex(messages);
9016
9980
  const prevAssistant = idx >= 0 ? messages[idx] : void 0;
9017
9981
  const prevHasTools = Boolean(prevAssistant?.toolCalls?.length);
@@ -9029,13 +9993,17 @@ function applyLlmResponse(state, event) {
9029
9993
  reasoning: (current.reasoning ?? "") + current.content,
9030
9994
  content: "",
9031
9995
  status: "streaming",
9032
- llmResponseEventId: event.eventId
9996
+ llmResponseEventId: event.eventId,
9997
+ turnId: turnId ?? current.turnId,
9998
+ iterationIndex: iterationIndex ?? current.iterationIndex
9033
9999
  };
9034
10000
  } else {
9035
10001
  messages[idx] = {
9036
10002
  ...current,
9037
10003
  status: "complete",
9038
- llmResponseEventId: event.eventId
10004
+ llmResponseEventId: event.eventId,
10005
+ turnId: turnId ?? current.turnId,
10006
+ iterationIndex: iterationIndex ?? current.iterationIndex
9039
10007
  };
9040
10008
  }
9041
10009
  } else if (matchesExistingToolTurn && idx >= 0) {
@@ -9044,7 +10012,9 @@ function applyLlmResponse(state, event) {
9044
10012
  ...current,
9045
10013
  reasoning: responseContent ? appendText(current.reasoning, responseContent) : current.reasoning,
9046
10014
  status: "streaming",
9047
- llmResponseEventId: event.eventId
10015
+ llmResponseEventId: event.eventId,
10016
+ turnId: turnId ?? current.turnId,
10017
+ iterationIndex: iterationIndex ?? current.iterationIndex
9048
10018
  };
9049
10019
  } else if (prevHasTools || !prevAssistant || hasUserAfter) {
9050
10020
  messages.push({
@@ -9054,7 +10024,9 @@ function applyLlmResponse(state, event) {
9054
10024
  reasoning: hasToolCalls && responseContent ? responseContent : void 0,
9055
10025
  createdAt: /* @__PURE__ */ new Date(),
9056
10026
  status: hasToolCalls ? "streaming" : "complete",
9057
- llmResponseEventId: event.eventId
10027
+ llmResponseEventId: event.eventId,
10028
+ turnId,
10029
+ iterationIndex
9058
10030
  });
9059
10031
  } else {
9060
10032
  const current = messages[idx];
@@ -9063,14 +10035,18 @@ function applyLlmResponse(state, event) {
9063
10035
  ...current,
9064
10036
  reasoning: (current.reasoning ?? "") + responseContent,
9065
10037
  status: "streaming",
9066
- llmResponseEventId: event.eventId
10038
+ llmResponseEventId: event.eventId,
10039
+ turnId: turnId ?? current.turnId,
10040
+ iterationIndex: iterationIndex ?? current.iterationIndex
9067
10041
  };
9068
10042
  } else {
9069
10043
  messages[idx] = {
9070
10044
  ...current,
9071
10045
  content: responseContent || current.content,
9072
10046
  status: hasToolCalls ? "streaming" : "complete",
9073
- llmResponseEventId: event.eventId
10047
+ llmResponseEventId: event.eventId,
10048
+ turnId: turnId ?? current.turnId,
10049
+ iterationIndex: iterationIndex ?? current.iterationIndex
9074
10050
  };
9075
10051
  }
9076
10052
  }
@@ -9103,6 +10079,7 @@ ${addition}`;
9103
10079
  }
9104
10080
  function applyLlmThinking(state, event) {
9105
10081
  const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
10082
+ const { turnId, iterationIndex } = readTurnMeta(event.data);
9106
10083
  const messages = [...state.messages];
9107
10084
  const idx = findLastAssistantIndex(messages);
9108
10085
  const prev = idx >= 0 ? messages[idx] : null;
@@ -9117,19 +10094,85 @@ function applyLlmThinking(state, event) {
9117
10094
  content: "",
9118
10095
  reasoning: reasoningText,
9119
10096
  createdAt: /* @__PURE__ */ new Date(),
9120
- status: "streaming"
10097
+ status: "streaming",
10098
+ turnId,
10099
+ iterationIndex
9121
10100
  });
9122
10101
  } else {
9123
10102
  messages[idx] = {
9124
10103
  ...prev,
9125
- reasoning: (prev.reasoning ?? "") + reasoningText
10104
+ reasoning: (prev.reasoning ?? "") + reasoningText,
10105
+ turnId: turnId ?? prev.turnId,
10106
+ iterationIndex: iterationIndex ?? prev.iterationIndex
9126
10107
  };
9127
10108
  }
9128
10109
  return {
9129
10110
  ...state,
9130
10111
  messages,
9131
- isRunning: state.terminal ? state.isRunning : true
10112
+ terminal: false,
10113
+ isRunning: true
10114
+ };
10115
+ }
10116
+ function applyIterationSummary(state, event) {
10117
+ const turnId = optionalStringValue(event.data.turn_id);
10118
+ const iterationIndex = optionalNumberValue(event.data.iteration_index);
10119
+ const summary = optionalStringValue(event.data.summary);
10120
+ if (!turnId || iterationIndex === null || !summary) return state;
10121
+ const idx = state.messages.findIndex(
10122
+ (m) => m.role === "assistant" && m.turnId === turnId && m.iterationIndex === iterationIndex
10123
+ );
10124
+ if (idx < 0) return state;
10125
+ const toolCallIds = Array.isArray(event.data.tool_call_ids) ? event.data.tool_call_ids.map((x) => String(x)) : [];
10126
+ const messages = [...state.messages];
10127
+ messages[idx] = {
10128
+ ...messages[idx],
10129
+ iterationSummary: {
10130
+ iterationIndex,
10131
+ summary,
10132
+ toolCallIds,
10133
+ startedAt: stringValue2(event.data.started_at),
10134
+ endedAt: stringValue2(event.data.ended_at)
10135
+ }
10136
+ };
10137
+ return { ...state, messages };
10138
+ }
10139
+ function applyTurnSummary(state, event) {
10140
+ const turnId = optionalStringValue(event.data.turn_id);
10141
+ if (!turnId) return state;
10142
+ const recap = stringValue2(event.data.recap);
10143
+ const artifacts = parseTurnArtifacts(event.data.artifacts);
10144
+ let idx = -1;
10145
+ for (let i = state.messages.length - 1; i >= 0; i--) {
10146
+ const message = state.messages[i];
10147
+ if (message.role === "assistant" && message.turnId === turnId) {
10148
+ idx = i;
10149
+ break;
10150
+ }
10151
+ }
10152
+ if (idx < 0) return state;
10153
+ const messages = [...state.messages];
10154
+ messages[idx] = {
10155
+ ...messages[idx],
10156
+ turnSummary: { turnId, recap, artifacts }
9132
10157
  };
10158
+ return { ...state, messages };
10159
+ }
10160
+ function parseTurnArtifacts(raw) {
10161
+ if (!Array.isArray(raw)) return [];
10162
+ const out = [];
10163
+ for (const item of raw) {
10164
+ if (!item || typeof item !== "object") continue;
10165
+ const obj = item;
10166
+ const kind = obj.kind;
10167
+ if (kind !== "file" && kind !== "artifact" && kind !== "url" && kind !== "command") {
10168
+ continue;
10169
+ }
10170
+ const label = typeof obj.label === "string" ? obj.label : "";
10171
+ const ref = typeof obj.ref === "string" ? obj.ref : "";
10172
+ if (!label || !ref) continue;
10173
+ out.push({ kind, label, ref });
10174
+ }
10175
+ return out;
9133
10176
  }
9134
10177
  function applyToolCall(state, event) {
9135
10178
  const messages = [...state.messages];
@@ -9166,7 +10209,8 @@ function applyToolCall(state, event) {
9166
10209
  return {
9167
10210
  ...state,
9168
10211
  messages,
9169
- isRunning: state.terminal ? state.isRunning : true
10212
+ terminal: false,
10213
+ isRunning: true
9170
10214
  };
9171
10215
  }
9172
10216
  function applyToolResult(messages, data) {
@@ -9244,16 +10288,84 @@ function buildErrorInfo(event) {
9244
10288
  retryable: Boolean(event.data.retryable)
9245
10289
  };
9246
10290
  }
9247
- function applyExpertResult(messages, eventId) {
10291
+ function applyExpertResult(messages, eventId, data) {
9248
10292
  const match = findLatestConsultExpertCall(messages);
9249
10293
  if (!match) return messages;
10294
+ const content = stringValue2(data.content);
9250
10295
  const next = [...messages];
9251
10296
  const msg = next[match.msgIdx];
9252
10297
  next[match.msgIdx] = {
9253
10298
  ...msg,
9254
- toolCalls: msg.toolCalls?.map(
9255
- (tc) => tc.id === match.toolId ? { ...tc, expertResultEventId: eventId } : tc
9256
- )
10299
+ toolCalls: msg.toolCalls?.map((tc) => {
10300
+ if (tc.id !== match.toolId) return tc;
10301
+ const updated = {
10302
+ ...tc,
10303
+ expertResultEventId: eventId,
10304
+ status: "complete"
10305
+ };
10306
+ if (tc.result === void 0 && content) {
10307
+ updated.result = JSON.stringify({ content });
10308
+ }
10309
+ return updated;
10310
+ })
10311
+ };
10312
+ return next;
10313
+ }
10314
+ function applyExpertDelegation(state, event) {
10315
+ if (findLatestConsultExpertCall(state.messages) !== null) {
10316
+ return state;
10317
+ }
10318
+ const expertName = stringValue2(event.data.expert);
10319
+ const task = stringValue2(event.data.task);
10320
+ if (!expertName) return state;
10321
+ const messages = [...state.messages];
10322
+ const assistantIdx = findLastAssistantIndex(messages);
10323
+ let assistant = assistantIdx >= 0 ? messages[assistantIdx] : null;
10324
+ const userAfterAssistant = assistantIdx >= 0 && hasUserAfterIndex(messages, assistantIdx);
10325
+ if (!assistant || assistant.status === "complete" || userAfterAssistant) {
10326
+ assistant = {
10327
+ id: `evt-${event.eventId}-expert-delegation`,
10328
+ role: "assistant",
10329
+ content: "",
10330
+ createdAt: /* @__PURE__ */ new Date(),
10331
+ status: "streaming"
10332
+ };
10333
+ messages.push(assistant);
10334
+ } else {
10335
+ assistant = { ...assistant };
10336
+ messages[assistantIdx] = assistant;
10337
+ }
10338
+ const toolCall = {
10339
+ id: `expert-delegation-${event.eventId}`,
10340
+ toolName: "consult_expert",
10341
+ args: JSON.stringify({ expert: expertName, question: task }),
10342
+ status: "running"
10343
+ };
10344
+ assistant.toolCalls = [...assistant.toolCalls ?? [], toolCall];
10345
+ return {
10346
+ ...state,
10347
+ messages,
10348
+ terminal: false,
10349
+ isRunning: true
10350
+ };
10351
+ }
10352
+ function applyExpertFailure(messages, eventId, data) {
10353
+ const match = findLatestConsultExpertCall(messages);
10354
+ if (!match) return messages;
10355
+ const errorMsg = stringValue2(data.error) || "Expert consultation failed.";
10356
+ const next = [...messages];
10357
+ const msg = next[match.msgIdx];
10358
+ next[match.msgIdx] = {
10359
+ ...msg,
10360
+ toolCalls: msg.toolCalls?.map((tc) => {
10361
+ if (tc.id !== match.toolId) return tc;
10362
+ return {
10363
+ ...tc,
10364
+ expertResultEventId: eventId,
10365
+ status: "error",
10366
+ result: tc.result ?? JSON.stringify({ error: errorMsg })
10367
+ };
10368
+ })
9257
10369
  };
9258
10370
  return next;
9259
10371
  }
@@ -9303,7 +10415,7 @@ function applyUserFeedback(messages, data) {
9303
10415
  }
9304
10416
  return messages;
9305
10417
  }
9306
- function applyClarifyResponse(messages, data) {
10418
+ function applyAskUserQuestionResponse(messages, data) {
9307
10419
  const targetToolId = stringValue2(data.tool_call_id);
9308
10420
  const responses = Array.isArray(data.responses) ? data.responses : void 0;
9309
10421
  if (!targetToolId || !responses) return messages;
@@ -9316,7 +10428,7 @@ function applyClarifyResponse(messages, data) {
9316
10428
  toolCalls: msg.toolCalls.map(
9317
10429
  (tc) => tc.id === targetToolId ? {
9318
10430
  ...tc,
9319
- clarifyAnswers: responses.map((response) => {
10431
+ askUserQuestionAnswers: responses.map((response) => {
9320
10432
  const row = objectValue(response) ?? {};
9321
10433
  return {
9322
10434
  question: stringValue2(row.question),
@@ -9398,8 +10510,39 @@ function stringValue2(value) {
9398
10510
  function numberValue(value) {
9399
10511
  return typeof value === "number" ? value : 0;
9400
10512
  }
10513
+ function optionalNumberValue(value) {
10514
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
10515
+ }
10516
+ function optionalStringValue(value) {
10517
+ return typeof value === "string" && value.length > 0 ? value : void 0;
10518
+ }
10519
+ function readTurnMeta(data) {
10520
+ const turnId = optionalStringValue(data.turn_id);
10521
+ const iterationIndex = optionalNumberValue(data.iteration_index);
10522
+ return {
10523
+ turnId,
10524
+ iterationIndex: iterationIndex === null ? void 0 : iterationIndex
10525
+ };
10526
+ }
9401
10527
 
9402
10528
  // src/runtime/use-agent-chat-runtime.ts
10529
+ var VIEW_MODE_KEY = "@invergent/agent-chat-react:viewMode";
10530
+ function readPersistedViewMode() {
10531
+ if (typeof window === "undefined") return null;
10532
+ try {
10533
+ const raw = window.localStorage.getItem(VIEW_MODE_KEY);
10534
+ if (raw === "simple" || raw === "expert") return raw;
10535
+ } catch {
10536
+ }
10537
+ return null;
10538
+ }
10539
+ function writePersistedViewMode(mode) {
10540
+ if (typeof window === "undefined") return;
10541
+ try {
10542
+ window.localStorage.setItem(VIEW_MODE_KEY, mode);
10543
+ } catch {
10544
+ }
10545
+ }
9403
10546
  function useAgentChatRuntime({
9404
10547
  adapter,
9405
10548
  agentId,
@@ -9407,7 +10550,13 @@ function useAgentChatRuntime({
9407
10550
  onSessionChange
9408
10551
  }) {
9409
10552
  const [state, setState] = useState(
9410
- () => createInitialAgentChatState({ isLoadingHistory: Boolean(sessionId) })
10553
+ () => createInitialAgentChatState({
10554
+ isLoadingHistory: Boolean(sessionId),
10555
+ // Seed from localStorage so the first paint matches the user's
10556
+ // last choice without flashing the default. The async adapter
10557
+ // load can still upgrade this later.
10558
+ viewMode: readPersistedViewMode() ?? "simple"
10559
+ })
9411
10560
  );
9412
10561
  const [session, setSession] = useState(null);
9413
10562
  const stateRef = useRef(state);
@@ -9703,6 +10852,41 @@ function useAgentChatRuntime({
9703
10852
  throw error;
9704
10853
  }
9705
10854
  }, [adapter, sessionId]);
10855
+ useEffect(() => {
10856
+ if (!adapter.getChatViewMode) {
10857
+ const cached = readPersistedViewMode();
10858
+ if (cached !== null) {
10859
+ setState(
10860
+ (prev) => prev.viewMode === cached ? prev : { ...prev, viewMode: cached }
10861
+ );
10862
+ }
10863
+ return;
10864
+ }
10865
+ let cancelled = false;
10866
+ void adapter.getChatViewMode().then((persisted) => {
10867
+ if (cancelled) return;
10868
+ if (persisted === "simple" || persisted === "expert") {
10869
+ setState(
10870
+ (prev) => prev.viewMode === persisted ? prev : { ...prev, viewMode: persisted }
10871
+ );
10872
+ }
10873
+ }).catch(() => void 0);
10874
+ return () => {
10875
+ cancelled = true;
10876
+ };
10877
+ }, [adapter]);
10878
+ const setViewMode = useCallback(
10879
+ (mode) => {
10880
+ setState(
10881
+ (prev) => prev.viewMode === mode ? prev : { ...prev, viewMode: mode }
10882
+ );
10883
+ writePersistedViewMode(mode);
10884
+ if (adapter.setChatViewMode) {
10885
+ void adapter.setChatViewMode(mode).catch(() => void 0);
10886
+ }
10887
+ },
10888
+ [adapter]
10889
+ );
9706
10890
  return {
9707
10891
  state,
9708
10892
  session,
@@ -9716,7 +10900,9 @@ function useAgentChatRuntime({
9716
10900
  stop,
9717
10901
  retry,
9718
10902
  markSending,
9719
- markSendError
10903
+ markSendError,
10904
+ viewMode: state.viewMode,
10905
+ setViewMode
9720
10906
  };
9721
10907
  }
9722
10908
  function parseEventData(data) {
@@ -9787,6 +10973,9 @@ function AgentChat({
9787
10973
  sessionId,
9788
10974
  onSessionChange
9789
10975
  });
10976
+ useEffect(() => {
10977
+ setShowWorkspace(runtime.viewMode === "expert");
10978
+ }, [runtime.viewMode]);
9790
10979
  const isReadOnlySession = isScheduledRunSession(runtime.session);
9791
10980
  const effectiveDisabled = disabled || isReadOnlySession;
9792
10981
  const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
@@ -9907,7 +11096,9 @@ function AgentChat({
9907
11096
  showWorkspace,
9908
11097
  onToggleWorkspace: handleToggleWorkspace,
9909
11098
  canShowBrowser: browserAvailable,
9910
- canShowWorkspace: workspaceAvailable
11099
+ canShowWorkspace: workspaceAvailable,
11100
+ viewMode: runtime.viewMode,
11101
+ onViewModeChange: runtime.setViewMode
9911
11102
  }
9912
11103
  )
9913
11104
  }
@@ -9943,7 +11134,8 @@ function AgentChat({
9943
11134
  {
9944
11135
  sessionId,
9945
11136
  state: browserState,
9946
- adapter
11137
+ adapter,
11138
+ onClose: () => setShowBrowser(false)
9947
11139
  }
9948
11140
  )
9949
11141
  }
@@ -10127,7 +11319,7 @@ function InputRequiredDetail({
10127
11319
  answer: answers[question.prompt]?.trim() ?? "",
10128
11320
  is_other: false
10129
11321
  }));
10130
- await adapter.submitClarifyResponse({
11322
+ await adapter.submitAskUserQuestionResponse({
10131
11323
  sessionId: item.sessionId,
10132
11324
  toolCallId,
10133
11325
  responses
@@ -10711,7 +11903,7 @@ function groupMissionTasksByBucket(tasks) {
10711
11903
  function deriveMissionWorkerActivityLabel(worker) {
10712
11904
  const kind = worker.latestEventKind;
10713
11905
  if (kind === "tool.call" && worker.latestEventSummary) {
10714
- return `tool: ${truncate2(worker.latestEventSummary, 80)}`;
11906
+ return `tool: ${truncate3(worker.latestEventSummary, 80)}`;
10715
11907
  }
10716
11908
  if (kind === "llm.response") {
10717
11909
  return "thinking";
@@ -10720,11 +11912,11 @@ function deriveMissionWorkerActivityLabel(worker) {
10720
11912
  return "received tool result";
10721
11913
  }
10722
11914
  if (kind && worker.latestEventSummary) {
10723
- return `${kind}: ${truncate2(worker.latestEventSummary, 60)}`;
11915
+ return `${kind}: ${truncate3(worker.latestEventSummary, 60)}`;
10724
11916
  }
10725
11917
  return worker.sessionStatus || "active";
10726
11918
  }
10727
- function truncate2(s, max) {
11919
+ function truncate3(s, max) {
10728
11920
  if (s.length <= max) return s;
10729
11921
  return `${s.slice(0, max - 1)}\u2026`;
10730
11922
  }
@@ -12092,7 +13284,8 @@ function pruneDeletedSessionNodes(nodes, deletedSessionId) {
12092
13284
  return nodes.filter((node) => !deletedIds.has(node.id));
12093
13285
  }
12094
13286
  function formatSessionTime(value) {
12095
- const date = new Date(value);
13287
+ const normalized = /[zZ]|[+-]\d{2}:?\d{2}$/.test(value) ? value : `${value}Z`;
13288
+ const date = new Date(normalized);
12096
13289
  if (Number.isNaN(date.getTime())) return "";
12097
13290
  return formatDistanceToNow(date, { addSuffix: true });
12098
13291
  }
@@ -12132,7 +13325,7 @@ function TreeNodeRow({
12132
13325
  const title = entry.title ?? fallbackSessionTitle(entry);
12133
13326
  const subtitle = [
12134
13327
  formatRunKind(entry.runKind),
12135
- entry.model ?? entry.agentType,
13328
+ entry.model,
12136
13329
  formatSessionTime(entry.updatedAt)
12137
13330
  ].filter(Boolean).join(" \xB7 ");
12138
13331
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -12148,7 +13341,7 @@ function TreeNodeRow({
12148
13341
  className: cn(
12149
13342
  "group flex items-center gap-2 w-full py-2 pr-2 text-left cursor-pointer transition-colors border-l-2",
12150
13343
  "min-h-11 md:min-h-0",
12151
- isActive ? "bg-line text-foreground border-l-primary" : "bg-transparent text-subtle hover:bg-input hover:text-foreground border-l-transparent"
13344
+ isActive ? "bg-line text-foreground border-l-primary" : "bg-transparent text-muted-foreground hover:bg-input hover:text-foreground border-l-transparent"
12152
13345
  ),
12153
13346
  style: { paddingLeft: `${depth * 12 + 12}px` },
12154
13347
  children: [