@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.cjs CHANGED
@@ -263,90 +263,6 @@ function Button({
263
263
  }
264
264
  );
265
265
  }
266
- function BrowserControlBar({
267
- sessionId,
268
- hasControl,
269
- adapter,
270
- onControlAcquired,
271
- onControlReleased
272
- }) {
273
- const [pending, setPending] = react.useState(false);
274
- const [error, setError] = react.useState(null);
275
- async function toggleControl() {
276
- if (!sessionId || pending) return;
277
- setPending(true);
278
- setError(null);
279
- try {
280
- if (hasControl) {
281
- await adapter.releaseBrowserControl(sessionId);
282
- onControlReleased?.();
283
- } else {
284
- await adapter.acquireBrowserControl(sessionId);
285
- onControlAcquired?.();
286
- }
287
- } catch (nextError) {
288
- setError(nextError.message);
289
- } finally {
290
- setPending(false);
291
- }
292
- }
293
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-11 items-center gap-2 border-t border-line bg-card px-3 py-2", children: [
294
- /* @__PURE__ */ jsxRuntime.jsxs(
295
- Button,
296
- {
297
- type: "button",
298
- size: "xs",
299
- variant: hasControl ? "secondary" : "default",
300
- disabled: !sessionId || pending,
301
- onClick: () => void toggleControl(),
302
- children: [
303
- hasControl ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcwIcon, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MousePointer2Icon, { className: "size-3", "aria-hidden": "true" }),
304
- hasControl ? "Return control" : "Take control"
305
- ]
306
- }
307
- ),
308
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
309
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1.5 rounded-full bg-red-500", "aria-hidden": "true" }),
310
- "Live"
311
- ] }),
312
- error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error })
313
- ] });
314
- }
315
- function BrowserLiveView({
316
- src,
317
- testId = "browser-iframe"
318
- }) {
319
- return /* @__PURE__ */ jsxRuntime.jsx(
320
- "iframe",
321
- {
322
- "data-testid": testId,
323
- title: "Browser live view",
324
- src,
325
- className: "h-full w-full border-0 bg-black",
326
- allow: "clipboard-read; clipboard-write"
327
- }
328
- );
329
- }
330
-
331
- // src/components/browser/browser-status-dot.tsx
332
- init_utils();
333
- function BrowserStatusDot({
334
- status
335
- }) {
336
- return /* @__PURE__ */ jsxRuntime.jsx(
337
- "span",
338
- {
339
- "aria-hidden": "true",
340
- className: cn(
341
- "inline-block size-2 shrink-0 rounded-full",
342
- status === "live" && "bg-emerald-500",
343
- status === "user-control" && "bg-amber-500",
344
- status === "provisioning" && "animate-pulse bg-sky-500",
345
- status === "closed" && "bg-muted-foreground"
346
- )
347
- }
348
- );
349
- }
350
266
 
351
267
  // src/components/ui/dialog.tsx
352
268
  init_utils();
@@ -479,6 +395,197 @@ function DialogDescription({
479
395
  }
480
396
  );
481
397
  }
398
+ function ConfirmDialog({
399
+ open,
400
+ title,
401
+ description,
402
+ confirmLabel = "Confirm",
403
+ confirmIcon,
404
+ variant = "destructive",
405
+ confirmDisabled = false,
406
+ onConfirm,
407
+ onCancel
408
+ }) {
409
+ const [loading, setLoading] = react.useState(false);
410
+ const handleConfirm = async () => {
411
+ setLoading(true);
412
+ try {
413
+ await onConfirm();
414
+ } finally {
415
+ setLoading(false);
416
+ }
417
+ };
418
+ return /* @__PURE__ */ jsxRuntime.jsx(
419
+ Dialog,
420
+ {
421
+ open,
422
+ onOpenChange: (next) => {
423
+ if (!next && !loading) onCancel();
424
+ },
425
+ children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "sm:max-w-md", children: [
426
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
427
+ /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: title }),
428
+ /* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { children: description })
429
+ ] }),
430
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
431
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", disabled: loading, onClick: onCancel, children: "Cancel" }),
432
+ /* @__PURE__ */ jsxRuntime.jsxs(
433
+ Button,
434
+ {
435
+ variant,
436
+ disabled: loading || confirmDisabled,
437
+ onClick: () => void handleConfirm(),
438
+ children: [
439
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "mr-1.5 size-3.5 animate-spin", "aria-hidden": "true" }) : confirmIcon,
440
+ confirmLabel
441
+ ]
442
+ }
443
+ )
444
+ ] })
445
+ ] })
446
+ }
447
+ );
448
+ }
449
+ function BrowserControlBar({
450
+ sessionId,
451
+ hasControl,
452
+ adapter,
453
+ onControlAcquired,
454
+ onControlReleased,
455
+ onClose
456
+ }) {
457
+ const [pending, setPending] = react.useState(false);
458
+ const [confirmOpen, setConfirmOpen] = react.useState(false);
459
+ const [error, setError] = react.useState(null);
460
+ async function toggleControl() {
461
+ if (!sessionId || pending) return;
462
+ setPending(true);
463
+ setError(null);
464
+ try {
465
+ if (hasControl) {
466
+ await adapter.releaseBrowserControl(sessionId);
467
+ onControlReleased?.();
468
+ } else {
469
+ await adapter.acquireBrowserControl(sessionId);
470
+ onControlAcquired?.();
471
+ }
472
+ } catch (nextError) {
473
+ setError(nextError.message);
474
+ } finally {
475
+ setPending(false);
476
+ }
477
+ }
478
+ async function handleConfirmClose() {
479
+ setError(null);
480
+ try {
481
+ if (hasControl) {
482
+ try {
483
+ await adapter.releaseBrowserControl(sessionId);
484
+ onControlReleased?.();
485
+ } catch (releaseError) {
486
+ console.error(
487
+ "Failed to release browser control before close",
488
+ releaseError
489
+ );
490
+ }
491
+ }
492
+ if (adapter.closeBrowserSession) {
493
+ await adapter.closeBrowserSession(sessionId);
494
+ }
495
+ setConfirmOpen(false);
496
+ onClose?.();
497
+ } catch (nextError) {
498
+ setError(nextError.message);
499
+ throw nextError;
500
+ }
501
+ }
502
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-11 items-center gap-2 border-t border-line bg-card px-3 py-2", children: [
503
+ /* @__PURE__ */ jsxRuntime.jsxs(
504
+ Button,
505
+ {
506
+ type: "button",
507
+ size: "xs",
508
+ variant: hasControl ? "secondary" : "default",
509
+ disabled: !sessionId || pending,
510
+ onClick: () => void toggleControl(),
511
+ children: [
512
+ hasControl ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcwIcon, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MousePointer2Icon, { className: "size-3", "aria-hidden": "true" }),
513
+ hasControl ? "Return control" : "Take control"
514
+ ]
515
+ }
516
+ ),
517
+ onClose && /* @__PURE__ */ jsxRuntime.jsxs(
518
+ Button,
519
+ {
520
+ type: "button",
521
+ size: "xs",
522
+ variant: "secondary",
523
+ disabled: !sessionId,
524
+ onClick: () => {
525
+ setError(null);
526
+ setConfirmOpen(true);
527
+ },
528
+ children: [
529
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-3", "aria-hidden": "true" }),
530
+ "Close"
531
+ ]
532
+ }
533
+ ),
534
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
535
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1.5 rounded-full bg-red-500", "aria-hidden": "true" }),
536
+ "Live"
537
+ ] }),
538
+ error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error }),
539
+ /* @__PURE__ */ jsxRuntime.jsx(
540
+ ConfirmDialog,
541
+ {
542
+ open: confirmOpen,
543
+ title: "Close browser session?",
544
+ 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.",
545
+ confirmLabel: "Close browser",
546
+ variant: "destructive",
547
+ confirmIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-3.5", "aria-hidden": "true" }),
548
+ onConfirm: handleConfirmClose,
549
+ onCancel: () => setConfirmOpen(false)
550
+ }
551
+ )
552
+ ] });
553
+ }
554
+ function BrowserLiveView({
555
+ src,
556
+ testId = "browser-iframe"
557
+ }) {
558
+ return /* @__PURE__ */ jsxRuntime.jsx(
559
+ "iframe",
560
+ {
561
+ "data-testid": testId,
562
+ title: "Browser live view",
563
+ src,
564
+ className: "h-full w-full border-0 bg-black",
565
+ allow: "clipboard-read; clipboard-write"
566
+ }
567
+ );
568
+ }
569
+
570
+ // src/components/browser/browser-status-dot.tsx
571
+ init_utils();
572
+ function BrowserStatusDot({
573
+ status
574
+ }) {
575
+ return /* @__PURE__ */ jsxRuntime.jsx(
576
+ "span",
577
+ {
578
+ "aria-hidden": "true",
579
+ className: cn(
580
+ "inline-block size-2 shrink-0 rounded-full",
581
+ status === "live" && "bg-emerald-500",
582
+ status === "user-control" && "bg-amber-500",
583
+ status === "provisioning" && "animate-pulse bg-sky-500",
584
+ status === "closed" && "bg-muted-foreground"
585
+ )
586
+ }
587
+ );
588
+ }
482
589
 
483
590
  // src/components/ui/tooltip.tsx
484
591
  init_utils();
@@ -528,7 +635,12 @@ function TooltipContent({
528
635
  }
529
636
  ) });
530
637
  }
531
- function BrowserPane({ sessionId, state, adapter }) {
638
+ function BrowserPane({
639
+ sessionId,
640
+ state,
641
+ adapter,
642
+ onClose
643
+ }) {
532
644
  const [fullscreenOpen, setFullscreenOpen] = react.useState(false);
533
645
  const [openFullscreenOnControl, setOpenFullscreenOnControl] = react.useState(false);
534
646
  const [localControlActive, setLocalControlActive] = react.useState(false);
@@ -588,14 +700,19 @@ function BrowserPane({ sessionId, state, adapter }) {
588
700
  const handleFullscreenOpenChange = react.useCallback(
589
701
  (open) => {
590
702
  setFullscreenOpen(open);
591
- if (open || !hasUserControl || !hasControlAdapter) return;
592
- setLocalControlActive(false);
593
- void adapter.releaseBrowserControl(sessionId).catch((error) => {
594
- console.error("Failed to release browser control", error);
595
- });
596
703
  },
597
- [adapter, hasControlAdapter, hasUserControl, sessionId]
704
+ []
598
705
  );
706
+ react.useEffect(() => {
707
+ if (!canUseLiveView || !hasControlAdapter) return;
708
+ const handle = window.setInterval(() => {
709
+ void adapter.acquireBrowserControl(sessionId).catch((error) => {
710
+ console.error("Failed to refresh browser control", error);
711
+ setLocalControlActive(false);
712
+ });
713
+ }, 25e3);
714
+ return () => window.clearInterval(handle);
715
+ }, [adapter, canUseLiveView, hasControlAdapter, sessionId]);
599
716
  react.useEffect(() => {
600
717
  if (!hasLiveView || canUseLiveView) {
601
718
  setPreviewLoading(false);
@@ -666,7 +783,8 @@ function BrowserPane({ sessionId, state, adapter }) {
666
783
  setLocalControlActive(true);
667
784
  setOpenFullscreenOnControl(true);
668
785
  },
669
- onControlReleased: () => setLocalControlActive(false)
786
+ onControlReleased: () => setLocalControlActive(false),
787
+ onClose
670
788
  }
671
789
  )
672
790
  ]
@@ -3696,7 +3814,7 @@ function SkillViewBlock({ tc }) {
3696
3814
  ] });
3697
3815
  }
3698
3816
 
3699
- // src/components/chat/tools/clarify-tool.tsx
3817
+ // src/components/chat/tools/ask-user-question-tool.tsx
3700
3818
  init_utils();
3701
3819
 
3702
3820
  // src/components/ui/input.tsx
@@ -3730,7 +3848,7 @@ function buildAnswer(q, sel) {
3730
3848
  if (!choice) return null;
3731
3849
  return { question: q.prompt, answer: choice.label, is_other: false };
3732
3850
  }
3733
- function ClarifyToolBlock({ tc }) {
3851
+ function AskUserQuestionToolBlock({ tc }) {
3734
3852
  const { adapter, sessionId } = useAgentChatAdapterContext();
3735
3853
  const args = react.useMemo(() => parseArgs(tc.args), [tc.args]);
3736
3854
  const questions = args?.questions ?? [];
@@ -3740,7 +3858,7 @@ function ClarifyToolBlock({ tc }) {
3740
3858
  );
3741
3859
  const [submitting, setSubmitting] = react.useState(false);
3742
3860
  const [error, setError] = react.useState(null);
3743
- const locked = tc.clarifyAnswers !== void 0 || tc.status !== "running";
3861
+ const locked = tc.askUserQuestionAnswers !== void 0 || tc.status !== "running";
3744
3862
  react.useEffect(() => {
3745
3863
  if (active >= questions.length && questions.length > 0) setActive(0);
3746
3864
  }, [active, questions.length]);
@@ -3768,7 +3886,7 @@ function ClarifyToolBlock({ tc }) {
3768
3886
  setError(null);
3769
3887
  setSubmitting(true);
3770
3888
  try {
3771
- await adapter.submitClarifyResponse({
3889
+ await adapter.submitAskUserQuestionResponse({
3772
3890
  sessionId,
3773
3891
  toolCallId: tc.id,
3774
3892
  responses: answers
@@ -3801,10 +3919,10 @@ function ClarifyToolBlock({ tc }) {
3801
3919
  return () => window.removeEventListener("keydown", handler);
3802
3920
  }, [handleCancel, handleSubmit, locked]);
3803
3921
  if (questions.length === 0) {
3804
- return /* @__PURE__ */ jsxRuntime.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" });
3922
+ return /* @__PURE__ */ jsxRuntime.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" });
3805
3923
  }
3806
3924
  if (locked) {
3807
- return /* @__PURE__ */ jsxRuntime.jsx(ClarifyLocked, { tc, questions });
3925
+ return /* @__PURE__ */ jsxRuntime.jsx(AskUserQuestionLocked, { tc, questions });
3808
3926
  }
3809
3927
  const current = questions[active];
3810
3928
  const currentSel = selections[active] ?? emptySelection();
@@ -3816,7 +3934,7 @@ function ClarifyToolBlock({ tc }) {
3816
3934
  "max-w-2xl"
3817
3935
  ),
3818
3936
  role: "group",
3819
- "aria-label": "Clarifying questions",
3937
+ "aria-label": "Questions for the user",
3820
3938
  children: [
3821
3939
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 pt-2", children: [
3822
3940
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3 overflow-x-auto text-sm", children: questions.map((_, i) => {
@@ -3980,11 +4098,11 @@ function Radio({ selected }) {
3980
4098
  }
3981
4099
  );
3982
4100
  }
3983
- function ClarifyLocked({
4101
+ function AskUserQuestionLocked({
3984
4102
  tc,
3985
4103
  questions
3986
4104
  }) {
3987
- const answers = tc.clarifyAnswers;
4105
+ const answers = tc.askUserQuestionAnswers;
3988
4106
  const byPrompt = new Map(
3989
4107
  answers?.map((a) => [a.question, a]) ?? []
3990
4108
  );
@@ -4399,8 +4517,8 @@ function ToolCallBlock({
4399
4517
  return /* @__PURE__ */ jsxRuntime.jsx(SkillViewBlock, { tc });
4400
4518
  case "skill_manage":
4401
4519
  return /* @__PURE__ */ jsxRuntime.jsx(SkillManageToolBlock, { tc });
4402
- case "clarify":
4403
- return /* @__PURE__ */ jsxRuntime.jsx(ClarifyToolBlock, { tc });
4520
+ case "ask_user_question":
4521
+ return /* @__PURE__ */ jsxRuntime.jsx(AskUserQuestionToolBlock, { tc });
4404
4522
  case "create_artifact":
4405
4523
  return /* @__PURE__ */ jsxRuntime.jsx(ArtifactToolBlock, { tc, resolvedName: resolvedArtifactName });
4406
4524
  case "delegate_task":
@@ -5965,7 +6083,9 @@ function ChatComposerInner({
5965
6083
  showWorkspace = false,
5966
6084
  onToggleWorkspace,
5967
6085
  canShowBrowser = false,
5968
- canShowWorkspace = false
6086
+ canShowWorkspace = false,
6087
+ viewMode = "simple",
6088
+ onViewModeChange
5969
6089
  }) {
5970
6090
  const { adapter } = useAgentChatAdapterContext();
5971
6091
  const { textInput, attachments } = usePromptInputController();
@@ -6272,6 +6392,36 @@ function ChatComposerInner({
6272
6392
  }
6273
6393
  )
6274
6394
  ] }),
6395
+ onViewModeChange && /* @__PURE__ */ jsxRuntime.jsxs(
6396
+ "div",
6397
+ {
6398
+ role: "group",
6399
+ "aria-label": "Chat view mode",
6400
+ className: "inline-flex overflow-hidden rounded-md border border-border",
6401
+ children: [
6402
+ /* @__PURE__ */ jsxRuntime.jsx(
6403
+ "button",
6404
+ {
6405
+ type: "button",
6406
+ "aria-pressed": viewMode === "simple",
6407
+ onClick: () => onViewModeChange("simple"),
6408
+ 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",
6409
+ children: "Simple"
6410
+ }
6411
+ ),
6412
+ /* @__PURE__ */ jsxRuntime.jsx(
6413
+ "button",
6414
+ {
6415
+ type: "button",
6416
+ "aria-pressed": viewMode === "expert",
6417
+ onClick: () => onViewModeChange("expert"),
6418
+ 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",
6419
+ children: "Expert"
6420
+ }
6421
+ )
6422
+ ]
6423
+ }
6424
+ ),
6275
6425
  canShowBrowser && onToggleBrowser && /* @__PURE__ */ jsxRuntime.jsx(
6276
6426
  PromptInputButton,
6277
6427
  {
@@ -6438,16 +6588,26 @@ function ChatComposerInner({
6438
6588
  CommandItem,
6439
6589
  {
6440
6590
  value: cmd.value,
6441
- keywords: [cmd.description],
6591
+ keywords: cmd.isExpert ? [cmd.description, "expert"] : [cmd.description],
6442
6592
  onSelect: () => handleCommandSelect(cmd.value),
6443
6593
  className: "grid grid-cols-[12rem_1fr] items-baseline gap-3 [&_svg]:hidden",
6444
6594
  children: [
6445
- /* @__PURE__ */ jsxRuntime.jsx(
6595
+ /* @__PURE__ */ jsxRuntime.jsxs(
6446
6596
  "span",
6447
6597
  {
6448
- className: "font-mono font-semibold text-foreground truncate",
6598
+ className: "inline-flex items-center gap-1.5 min-w-0 max-w-full",
6449
6599
  title: cmd.label,
6450
- children: cmd.label
6600
+ children: [
6601
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-semibold text-foreground truncate", children: cmd.label }),
6602
+ cmd.isExpert && /* @__PURE__ */ jsxRuntime.jsx(
6603
+ "span",
6604
+ {
6605
+ className: "shrink-0 rounded-sm bg-primary/10 px-1 text-[9px] font-semibold uppercase tracking-wider text-primary",
6606
+ "aria-label": "Expert specialist",
6607
+ children: "expert"
6608
+ }
6609
+ )
6610
+ ]
6451
6611
  }
6452
6612
  ),
6453
6613
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -7249,1207 +7409,1990 @@ var ErrorMessage = react.memo(function ErrorMessage2({
7249
7409
  }
7250
7410
  );
7251
7411
  });
7252
-
7253
- // src/components/chat/chat-thread.tsx
7254
7412
  init_utils();
7255
- function messageToEntries(msg, isLast, artifactFallbacks) {
7256
- if (msg.role === "system") {
7257
- if (msg.systemKind === "skill_invoked") {
7258
- return [{
7259
- kind: "skill_invoked",
7260
- key: msg.id,
7261
- skill: msg.systemMeta?.skill ?? msg.content,
7262
- stagedAt: msg.systemMeta?.staged_at ?? null
7263
- }];
7264
- }
7265
- if (msg.systemKind === "artifact") {
7266
- const { artifactId, name, kind, version } = unpackArtifactMeta(
7267
- msg.systemMeta,
7268
- msg.content
7269
- );
7270
- return [{
7271
- kind: "artifact",
7272
- key: msg.id,
7273
- artifactId,
7274
- name,
7275
- artifactKind: kind,
7276
- version
7277
- }];
7278
- }
7279
- return [];
7280
- }
7281
- const entries = [];
7282
- const hasToolCalls = !!(msg.toolCalls && msg.toolCalls.length > 0);
7283
- const hasContent = !!(msg.content && msg.content !== msg.reasoning);
7284
- const isStreaming = msg.status === "streaming" && isLast;
7285
- const effectiveReasoning = hasToolCalls && hasContent ? msg.reasoning ? msg.reasoning + "\n" + msg.content : msg.content : msg.reasoning;
7286
- const effectiveHasContent = hasContent && !hasToolCalls;
7287
- if (effectiveReasoning) {
7288
- entries.push({
7289
- kind: "reasoning",
7290
- key: `${msg.id}-reasoning`,
7291
- reasoning: effectiveReasoning,
7292
- isStreaming: isStreaming && !effectiveHasContent && !hasToolCalls
7293
- });
7294
- }
7295
- if (hasToolCalls) {
7296
- for (const tc of msg.toolCalls) {
7297
- if (tc.toolName === "create_artifact") {
7298
- const resolvedName = parseArgs(tc.args)?.name ?? artifactFallbacks[tc.id];
7299
- const status = effectiveStatus(tc);
7300
- if (!resolvedName && status !== "error") continue;
7301
- entries.push({
7302
- kind: "tool",
7303
- key: tc.id,
7304
- tc,
7305
- resolvedArtifactName: resolvedName
7306
- });
7307
- continue;
7308
- }
7309
- if (tc.toolName === "terminal" && tc.status !== "running") {
7310
- const parsed = parseTerminalResult(tc.result, tc.args);
7311
- if (parsed && (parsed.exit_code !== 0 || parsed.error)) continue;
7312
- }
7313
- entries.push({ kind: "tool", key: tc.id, tc });
7314
- }
7315
- }
7316
- if (effectiveHasContent) {
7317
- entries.push({
7318
- kind: "text",
7319
- key: `${msg.id}-text`,
7320
- content: msg.content,
7321
- isStreaming,
7322
- msg
7323
- });
7324
- }
7325
- const hasRunningTool = hasToolCalls && msg.toolCalls.some(
7326
- (tc) => tc.status === "running"
7327
- );
7328
- if (isStreaming && !effectiveHasContent && !hasRunningTool) {
7329
- entries.push({ kind: "thinking", key: `${msg.id}-thinking` });
7330
- }
7331
- return entries;
7332
- }
7333
- function groupBrowserActivityEntries(entries) {
7334
- const grouped = [];
7335
- let buffer = [];
7336
- const flush = () => {
7337
- if (buffer.length === 0) return;
7338
- grouped.push({
7339
- kind: "browser_activity",
7340
- key: `browser-activity-${buffer[0]?.id}-${buffer[buffer.length - 1]?.id}`,
7341
- calls: buffer
7342
- });
7343
- buffer = [];
7344
- };
7345
- for (const entry of entries) {
7346
- if (entry.kind === "tool" && isBrowserToolCall(entry.tc)) {
7347
- buffer.push(entry.tc);
7348
- continue;
7349
- }
7350
- flush();
7351
- grouped.push(entry);
7352
- }
7353
- flush();
7354
- return grouped;
7355
- }
7356
- function isBrowserToolCall(call) {
7357
- return call.toolName.startsWith("browser_");
7413
+
7414
+ // src/lib/format.ts
7415
+ function formatFileSize(bytes) {
7416
+ if (bytes < 1024) return `${bytes} B`;
7417
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
7418
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
7358
7419
  }
7359
- function groupWebSearchEntries(entries) {
7360
- const grouped = [];
7361
- let buffer = [];
7362
- const flush = () => {
7363
- if (buffer.length === 0) return;
7364
- if (buffer.length === 1) {
7365
- grouped.push({ kind: "tool", key: buffer[0].id, tc: buffer[0] });
7366
- } else {
7367
- grouped.push({
7368
- kind: "web_search_group",
7369
- key: `web-search-${buffer[0].id}-${buffer[buffer.length - 1].id}`,
7370
- calls: buffer
7371
- });
7372
- }
7373
- buffer = [];
7420
+ function getLanguageHint(path) {
7421
+ const name = path.split("/").pop() ?? path;
7422
+ const dot = name.lastIndexOf(".");
7423
+ if (dot < 0) return "plaintext";
7424
+ const ext = name.slice(dot).toLowerCase();
7425
+ const map = {
7426
+ ".py": "python",
7427
+ ".js": "javascript",
7428
+ ".ts": "typescript",
7429
+ ".tsx": "tsx",
7430
+ ".jsx": "jsx",
7431
+ ".json": "json",
7432
+ ".yaml": "yaml",
7433
+ ".yml": "yaml",
7434
+ ".toml": "toml",
7435
+ ".sql": "sql",
7436
+ ".sh": "bash",
7437
+ ".css": "css",
7438
+ ".html": "html",
7439
+ ".md": "markdown",
7440
+ ".rs": "rust",
7441
+ ".go": "go",
7442
+ ".java": "java",
7443
+ ".rb": "ruby",
7444
+ ".php": "php",
7445
+ ".c": "c",
7446
+ ".cpp": "cpp",
7447
+ ".h": "c",
7448
+ ".hpp": "cpp"
7374
7449
  };
7375
- for (const entry of entries) {
7376
- if (entry.kind === "tool" && entry.tc.toolName === "web_search") {
7377
- buffer.push(entry.tc);
7378
- continue;
7379
- }
7380
- flush();
7381
- grouped.push(entry);
7382
- }
7383
- flush();
7384
- return grouped;
7450
+ return map[ext] ?? "plaintext";
7385
7451
  }
7386
- function groupMessages(messages) {
7387
- const groups = [];
7388
- let pendingSystem = [];
7389
- const drainPendingAsOrphans = () => {
7390
- for (const { msg, index } of pendingSystem) {
7391
- groups.push({ role: "system", messages: [msg], lastGlobalIndex: index });
7392
- }
7393
- pendingSystem = [];
7394
- };
7395
- for (let i = 0; i < messages.length; i++) {
7396
- const msg = messages[i];
7397
- if (msg.role === "system") {
7398
- pendingSystem.push({ msg, index: i });
7399
- continue;
7400
- }
7401
- if (msg.role === "assistant") {
7402
- const prev = groups[groups.length - 1];
7403
- if (prev && prev.role === "assistant" && pendingSystem.length === 0) {
7404
- prev.messages.push(msg);
7405
- prev.lastGlobalIndex = i;
7406
- } else {
7407
- const folded = pendingSystem.map((p) => p.msg);
7408
- pendingSystem = [];
7409
- groups.push({
7410
- role: "assistant",
7411
- messages: [...folded, msg],
7412
- lastGlobalIndex: i
7413
- });
7414
- }
7415
- continue;
7452
+
7453
+ // src/components/ui/skeleton.tsx
7454
+ init_utils();
7455
+ function Skeleton({ className, ...props }) {
7456
+ return /* @__PURE__ */ jsxRuntime.jsx(
7457
+ "div",
7458
+ {
7459
+ className: cn("animate-pulse rounded-md bg-muted", className),
7460
+ ...props
7416
7461
  }
7417
- drainPendingAsOrphans();
7418
- groups.push({ role: "user", messages: [msg], lastGlobalIndex: i });
7419
- }
7420
- drainPendingAsOrphans();
7421
- return groups;
7422
- }
7423
- function unpackArtifactMeta(systemMeta, fallbackName) {
7424
- const meta = systemMeta ?? {};
7425
- return {
7426
- artifactId: meta.artifact_id ?? "",
7427
- name: meta.name ?? fallbackName,
7428
- kind: meta.kind ?? "markdown",
7429
- version: meta.version ?? 1
7430
- };
7462
+ );
7431
7463
  }
7432
- function OrphanSystemMarker({
7433
- message,
7434
- sessionId,
7435
- onRetry
7464
+ var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
7465
+ var PDF_WORKER_SRC = new URL(
7466
+ "pdfjs-dist/legacy/build/pdf.worker.mjs",
7467
+ (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))
7468
+ ).toString();
7469
+ function FileViewer({
7470
+ file,
7471
+ loading,
7472
+ error,
7473
+ downloadUrl,
7474
+ onDelete,
7475
+ onClose
7436
7476
  }) {
7437
- if (message.systemKind === "skill_invoked") {
7438
- const skill = message.systemMeta?.skill ?? message.content;
7439
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "my-2 flex items-center gap-2 px-4 text-xs text-muted-foreground ", children: [
7440
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-2 rounded-full bg-emerald-500" }),
7441
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
7442
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: "Skill" }),
7443
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground truncate", children: skill })
7444
- ] })
7445
- ] });
7446
- }
7447
- if (message.systemKind === "artifact" && sessionId) {
7448
- const unpacked = unpackArtifactMeta(message.systemMeta, message.content);
7449
- return /* @__PURE__ */ jsxRuntime.jsx(
7450
- ArtifactBlock,
7451
- {
7452
- sessionId,
7453
- artifactId: unpacked.artifactId,
7454
- name: unpacked.name,
7455
- kind: unpacked.kind,
7456
- version: unpacked.version
7457
- }
7458
- );
7459
- }
7460
- if (message.systemKind === "error" && message.errorInfo) {
7461
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto my-2 w-full max-w-4xl", children: /* @__PURE__ */ jsxRuntime.jsx(ErrorMessage, { errorInfo: message.errorInfo, onRetry }) });
7462
- }
7463
- return null;
7464
- }
7465
- function cancelledToolLabel(toolName) {
7466
- const map = {
7467
- terminal: "Command",
7468
- execute_code: "Execute Code",
7469
- read_file: "Read",
7470
- write_file: "Write",
7471
- patch: "Patch",
7472
- search_files: "Search Files",
7473
- list_files: "List Files",
7474
- web_search: "Web Search",
7475
- web_extract: "Web Fetch",
7476
- web_crawl: "Web Crawl",
7477
- session_search: "Session Search",
7478
- memory: "Memory",
7479
- todo: "Todo",
7480
- skills_list: "Skills",
7481
- skill_view: "Skill",
7482
- consult_expert: "Expert",
7483
- delegate_task: "Delegate",
7484
- clarify: "Clarify",
7485
- process: "Process",
7486
- create_artifact: "Artifact"
7487
- };
7488
- return map[toolName] ?? toolName;
7489
- }
7490
- function CancelledToolRow({ tc }) {
7491
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-sm", children: [
7492
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-muted-foreground", children: cancelledToolLabel(tc.toolName) }),
7493
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/70", children: "Cancelled" })
7494
- ] });
7495
- }
7496
- function TimelineEntryItem({
7497
- entry,
7498
- step,
7499
- sessionId,
7500
- onFileSelect
7501
- }) {
7502
- if (entry.kind === "reasoning") {
7503
- return /* @__PURE__ */ jsxRuntime.jsx(ReasoningEntry, { entry, step });
7504
- }
7505
- if (entry.kind === "tool") {
7506
- const rawStatus = effectiveStatus(entry.tc);
7507
- const hideArtifactFailure = rawStatus === "error" && entry.tc.toolName === "create_artifact";
7508
- const indicatorStatus = hideArtifactFailure ? "running" : rawStatus;
7509
- const failureSummary = rawStatus === "error" && !hideArtifactFailure ? toolErrorSummary(entry.tc.result) : "";
7510
- return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
7511
- /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
7512
- /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7513
- /* @__PURE__ */ jsxRuntime.jsx(
7514
- TimelineIndicator,
7515
- {
7516
- className: cn("size-2 border-none", statusColorClass(indicatorStatus))
7517
- }
7518
- )
7519
- ] }),
7520
- /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: entry.tc.cancelled ? /* @__PURE__ */ jsxRuntime.jsx(CancelledToolRow, { tc: entry.tc }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
7521
- /* @__PURE__ */ jsxRuntime.jsx(
7522
- ToolCallBlock,
7523
- {
7524
- tc: entry.tc,
7525
- resolvedArtifactName: entry.resolvedArtifactName,
7526
- onFileSelect
7527
- }
7528
- ),
7529
- failureSummary ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-full truncate text-xs text-destructive", title: failureSummary, children: failureSummary }) : null
7530
- ] }) })
7531
- ] });
7532
- }
7533
- if (entry.kind === "browser_activity") {
7534
- return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
7535
- /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
7536
- /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7537
- /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-amber-500" })
7538
- ] }),
7539
- /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx(BrowserActivityGroup, { calls: entry.calls }) })
7540
- ] });
7541
- }
7542
- if (entry.kind === "web_search_group") {
7543
- const allComplete = entry.calls.every((tc) => tc.status === "complete");
7544
- const anyRunning = entry.calls.some((tc) => tc.status === "running");
7545
- const indicatorClass = anyRunning ? "bg-emerald-500 animate-pulse" : allComplete ? "bg-emerald-500" : "bg-red-500";
7546
- return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
7547
- /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
7548
- /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7549
- /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: cn("size-2 border-none", indicatorClass) })
7550
- ] }),
7551
- /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx(WebSearchGroupBlock, { tcs: entry.calls }) })
7552
- ] });
7553
- }
7554
- if (entry.kind === "text") {
7555
- return /* @__PURE__ */ jsxRuntime.jsx(TextEntry, { entry, step });
7556
- }
7557
- if (entry.kind === "skill_invoked") {
7558
- return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
7559
- /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
7560
- /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7561
- /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-emerald-500" })
7562
- ] }),
7563
- /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 py-1 text-sm ", children: [
7564
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: "Skill" }),
7565
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground truncate", children: entry.skill }),
7566
- entry.stagedAt && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground/70 ", children: [
7567
- "staged at ",
7568
- entry.stagedAt
7477
+ const visible = loading || file !== null || error !== null;
7478
+ if (!visible) return null;
7479
+ const fileName2 = file?.path.split("/").pop() ?? "File";
7480
+ const lang = file ? getLanguageHint(file.path) : "";
7481
+ const isPdf = file?.mime_type === "application/pdf" || file?.path.toLowerCase().endsWith(".pdf") || false;
7482
+ const isImage = file?.encoding === "base64" && !isPdf;
7483
+ const HeaderIcon = isImage ? lucideReact.ImageIcon : lucideReact.FileTextIcon;
7484
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 flex-col border-t border-line", children: [
7485
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
7486
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
7487
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
7488
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs font-medium", children: fileName2 }),
7489
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "truncate text-[10px] text-muted-foreground", children: [
7490
+ file?.path ?? "Loading...",
7491
+ file && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-1.5", children: [
7492
+ formatFileSize(file.size),
7493
+ file.truncated && " (truncated)"
7494
+ ] })
7569
7495
  ] })
7570
- ] }) })
7571
- ] });
7572
- }
7573
- if (entry.kind === "artifact") {
7574
- return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
7575
- /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
7576
- /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7577
- /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-sky-500" })
7578
7496
  ] }),
7579
- /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: sessionId ? /* @__PURE__ */ jsxRuntime.jsx(
7580
- ArtifactBlock,
7497
+ downloadUrl && /* @__PURE__ */ jsxRuntime.jsx(
7498
+ "a",
7581
7499
  {
7582
- sessionId,
7583
- artifactId: entry.artifactId,
7584
- name: entry.name,
7585
- kind: entry.artifactKind,
7586
- version: entry.version
7500
+ href: downloadUrl,
7501
+ download: fileName2,
7502
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
7503
+ "aria-label": `Download ${fileName2}`,
7504
+ title: "Download",
7505
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-3.5" })
7587
7506
  }
7588
- ) : null })
7589
- ] });
7590
- }
7591
- return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
7592
- /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
7593
- /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7594
- /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-primary animate-pulse" })
7595
- ] }),
7596
- /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: "Working on it..." }) })
7597
- ] });
7598
- }
7599
- function ReasoningEntry({
7600
- entry,
7601
- step
7602
- }) {
7603
- const reasoning = useSmoothStream(entry.reasoning, entry.isStreaming);
7604
- return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
7605
- /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
7606
- /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7607
- /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2" })
7507
+ ),
7508
+ onDelete && /* @__PURE__ */ jsxRuntime.jsx(
7509
+ "button",
7510
+ {
7511
+ type: "button",
7512
+ onClick: onDelete,
7513
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
7514
+ "aria-label": `Delete ${fileName2}`,
7515
+ title: "Delete",
7516
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrashIcon, { className: "size-3.5" })
7517
+ }
7518
+ ),
7519
+ /* @__PURE__ */ jsxRuntime.jsx(
7520
+ "button",
7521
+ {
7522
+ type: "button",
7523
+ onClick: onClose,
7524
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
7525
+ "aria-label": "Close file",
7526
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-3.5" })
7527
+ }
7528
+ )
7608
7529
  ] }),
7609
- /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(Reasoning, { isStreaming: entry.isStreaming, children: [
7610
- /* @__PURE__ */ jsxRuntime.jsx(ReasoningTrigger, {}),
7611
- /* @__PURE__ */ jsxRuntime.jsx(ReasoningContent, { children: reasoning })
7612
- ] }) })
7530
+ /* @__PURE__ */ jsxRuntime.jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [
7531
+ loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5 p-3", children: Array.from({ length: 10 }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
7532
+ Skeleton,
7533
+ {
7534
+ className: "h-3.5 rounded",
7535
+ style: {
7536
+ width: `${SKELETON_WIDTHS[index % SKELETON_WIDTHS.length]}%`
7537
+ }
7538
+ },
7539
+ index
7540
+ )) }),
7541
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
7542
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
7543
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
7544
+ ] }),
7545
+ file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsxRuntime.jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
7546
+ file && isPdf && file.encoding === "base64" && /* @__PURE__ */ jsxRuntime.jsx(PdfPreview, { file, fileName: fileName2 }),
7547
+ file && isImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
7548
+ "img",
7549
+ {
7550
+ src: `data:${file.mime_type ?? "image/png"};base64,${file.content}`,
7551
+ alt: fileName2,
7552
+ className: "max-h-[60vh] max-w-full rounded object-contain"
7553
+ }
7554
+ ) }),
7555
+ file && !isImage && !isPdf && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-[11px] leading-relaxed text-foreground", children: /* @__PURE__ */ jsxRuntime.jsx("code", { "data-language": lang, children: file.content }) })
7556
+ ] })
7613
7557
  ] });
7614
7558
  }
7615
- function TextEntry({
7616
- entry,
7617
- step
7618
- }) {
7619
- const content = useSmoothStream(entry.content, entry.isStreaming);
7620
- return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
7621
- /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
7622
- /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7623
- /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-foreground/40" })
7624
- ] }),
7625
- /* @__PURE__ */ jsxRuntime.jsxs(TimelineContent, { children: [
7626
- /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { children: content }),
7627
- entry.isFinalTurnText && entry.msg.status === "complete" && /* @__PURE__ */ jsxRuntime.jsx(TurnFeedback, { msg: entry.msg })
7628
- ] })
7559
+ function FileViewerError({ message }) {
7560
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
7561
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
7562
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: message })
7629
7563
  ] });
7630
7564
  }
7631
- function AssistantGroup({
7632
- messages,
7633
- lastGlobalIndex,
7634
- totalMessages,
7635
- isRunning,
7636
- sessionId,
7637
- artifactFallbacks,
7638
- onFileSelect,
7639
- onRetry
7565
+ function PdfPreview({
7566
+ file,
7567
+ fileName: fileName2
7640
7568
  }) {
7641
- let entries = [];
7642
- for (let i = 0; i < messages.length; i++) {
7643
- const isLast = i === messages.length - 1 && lastGlobalIndex === totalMessages - 1;
7644
- entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
7645
- }
7646
- entries = groupBrowserActivityEntries(entries);
7647
- entries = groupWebSearchEntries(entries);
7648
- for (let i = entries.length - 1; i >= 0; i--) {
7649
- const entry = entries[i];
7650
- if (entry?.kind === "text") {
7651
- entries[i] = { ...entry, isFinalTurnText: true };
7652
- break;
7653
- }
7654
- }
7655
- const isTailGroup = lastGlobalIndex === totalMessages - 1;
7656
- const lastEntry = entries[entries.length - 1];
7657
- const hasRunningTool = entries.some(
7658
- (e) => e.kind === "tool" && e.tc.status === "running"
7659
- );
7660
- const tailMsg = messages[messages.length - 1];
7661
- if (isTailGroup && isRunning && !hasRunningTool && lastEntry?.kind !== "thinking") {
7662
- entries.push({ kind: "thinking", key: `${tailMsg.id}-tail-thinking` });
7663
- }
7664
- const tail = messages[messages.length - 1];
7665
- const showErrorInfo = tail && tail.role === "assistant" && tail.status === "error" && !!tail.errorInfo;
7666
- return /* @__PURE__ */ jsxRuntime.jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsxRuntime.jsxs(MessageContent, { children: [
7667
- /* @__PURE__ */ jsxRuntime.jsx(Timeline, { defaultValue: 999, className: "gap-0", children: entries.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsx(
7668
- TimelineEntryItem,
7669
- {
7670
- entry,
7671
- step: i + 1,
7672
- sessionId,
7673
- onFileSelect
7674
- },
7675
- entry.key
7676
- )) }),
7677
- showErrorInfo && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsx(ErrorMessage, { errorInfo: tail.errorInfo, onRetry }) })
7678
- ] }) });
7679
- }
7680
- function RetryBanner({ indicator }) {
7681
- const [open, setOpen] = react.useState(false);
7682
- return /* @__PURE__ */ jsxRuntime.jsxs(
7683
- "div",
7684
- {
7685
- role: "status",
7686
- className: "flex flex-col gap-1 border-l-2 border-amber-500 bg-amber-500/5 px-3 py-2 text-xs",
7687
- children: [
7688
- /* @__PURE__ */ jsxRuntime.jsxs(
7689
- "button",
7690
- {
7691
- type: "button",
7692
- onClick: () => setOpen((prev) => !prev),
7693
- className: "flex items-center gap-2 text-left text-amber-600 hover:text-amber-700",
7694
- "aria-expanded": open,
7695
- children: [
7696
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "size-3 shrink-0" }),
7697
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate font-medium", children: indicator.title }),
7698
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
7699
- "attempt ",
7700
- indicator.attempt
7701
- ] }),
7702
- indicator.detail && (open ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "size-3" }))
7703
- ]
7704
- }
7705
- ),
7706
- open && indicator.detail && /* @__PURE__ */ jsxRuntime.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 })
7707
- ]
7708
- }
7709
- );
7710
- }
7711
- function ChatThread({
7712
- sessionId,
7713
- messages,
7714
- isRunning,
7715
- isLoadingHistory = false,
7716
- onSend,
7717
- onStop,
7718
- onFileSelect,
7719
- disabled = false,
7720
- disabledReason,
7721
- tokenUsage,
7722
- retryIndicator,
7723
- onRetry,
7724
- onComposerError,
7725
- showBrowser = false,
7726
- onToggleBrowser,
7727
- showWorkspace = false,
7728
- onToggleWorkspace,
7729
- canShowBrowser = false,
7730
- canShowWorkspace = false
7731
- }) {
7732
- const groups = react.useMemo(() => groupMessages(messages), [messages]);
7733
- const artifactFallbacks = react.useMemo(() => {
7734
- const fallbacks = {};
7735
- const createArtifactToolCallIds = [];
7736
- let artifactIdx = 0;
7737
- for (const msg of messages) {
7738
- if (msg.role === "assistant" && msg.toolCalls) {
7739
- for (const tc of msg.toolCalls) {
7740
- if (tc.toolName === "create_artifact" && !tc.cancelled && tc.status !== "error") {
7741
- createArtifactToolCallIds.push(tc.id);
7742
- }
7569
+ const containerRef = react.useRef(null);
7570
+ const viewerRef = react.useRef(null);
7571
+ const pdfViewerRef = react.useRef(null);
7572
+ const eventBusRef = react.useRef(null);
7573
+ const findControllerRef = react.useRef(null);
7574
+ const [error, setError] = react.useState(null);
7575
+ const [pageNumber, setPageNumber] = react.useState(1);
7576
+ const [pagesCount, setPagesCount] = react.useState(0);
7577
+ const [scale, setScale] = react.useState(1);
7578
+ const [query, setQuery] = react.useState("");
7579
+ react.useEffect(() => {
7580
+ let cancelled = false;
7581
+ let loadingTask = null;
7582
+ let pdfDocument = null;
7583
+ const renderPdf = async () => {
7584
+ setError(null);
7585
+ const container = containerRef.current;
7586
+ const viewerElement = viewerRef.current;
7587
+ if (!container || !viewerElement) return;
7588
+ const pdfBytes = decodeBase64(file.content);
7589
+ const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
7590
+ pdfjs.GlobalWorkerOptions.workerSrc = PDF_WORKER_SRC;
7591
+ globalThis.pdfjsLib = pdfjs;
7592
+ const pdfViewerModule = await import('pdfjs-dist/web/pdf_viewer.mjs');
7593
+ loadingTask = pdfjs.getDocument({ data: pdfBytes });
7594
+ const pdf = await loadingTask.promise;
7595
+ pdfDocument = pdf;
7596
+ if (cancelled) return;
7597
+ const eventBus = new pdfViewerModule.EventBus();
7598
+ const linkService = new pdfViewerModule.PDFLinkService({ eventBus });
7599
+ const findController = new pdfViewerModule.PDFFindController({
7600
+ eventBus,
7601
+ linkService
7602
+ });
7603
+ const pdfViewer = new pdfViewerModule.PDFViewer({
7604
+ container,
7605
+ viewer: viewerElement,
7606
+ eventBus,
7607
+ linkService,
7608
+ findController,
7609
+ removePageBorders: true
7610
+ });
7611
+ linkService.setViewer(pdfViewer);
7612
+ linkService.setDocument(pdf);
7613
+ findController.setDocument(pdf);
7614
+ const onPagesInit = () => {
7615
+ pdfViewer.currentScaleValue = "page-width";
7616
+ setScale(pdfViewer.currentScale || 1);
7617
+ setPagesCount(pdfViewer.pagesCount);
7618
+ setPageNumber(pdfViewer.currentPageNumber);
7619
+ };
7620
+ const onPageChanging = (event) => {
7621
+ if (typeof event.pageNumber === "number") {
7622
+ setPageNumber(event.pageNumber);
7743
7623
  }
7744
- } else if (msg.role === "system" && msg.systemKind === "artifact") {
7745
- const tcId = createArtifactToolCallIds[artifactIdx];
7746
- if (tcId) {
7747
- const { name } = unpackArtifactMeta(msg.systemMeta, msg.content);
7748
- if (name) fallbacks[tcId] = name;
7624
+ };
7625
+ const onScaleChanging = (event) => {
7626
+ if (typeof event.scale === "number") {
7627
+ setScale(event.scale);
7749
7628
  }
7750
- artifactIdx += 1;
7629
+ };
7630
+ eventBus.on("pagesinit", onPagesInit);
7631
+ eventBus.on("pagechanging", onPageChanging);
7632
+ eventBus.on("scalechanging", onScaleChanging);
7633
+ pdfViewerRef.current = pdfViewer;
7634
+ eventBusRef.current = eventBus;
7635
+ findControllerRef.current = findController;
7636
+ setPagesCount(pdf.numPages);
7637
+ pdfViewer.setDocument(pdf);
7638
+ };
7639
+ void renderPdf().catch((nextError) => {
7640
+ if (cancelled) return;
7641
+ if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
7642
+ return;
7751
7643
  }
7752
- }
7753
- return fallbacks;
7754
- }, [messages]);
7755
- const activeFailureId = react.useMemo(() => {
7756
- if (messages.length === 0) return null;
7757
- const tail = messages[messages.length - 1];
7758
- if (tail.role === "system" && tail.systemKind === "error" && tail.errorInfo) {
7759
- return tail.id;
7760
- }
7761
- if (tail.role === "assistant" && tail.status === "error" && tail.errorInfo) {
7762
- return tail.id;
7763
- }
7764
- return null;
7765
- }, [messages]);
7766
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col overflow-hidden bg-background text-sm", children: [
7767
- /* @__PURE__ */ jsxRuntime.jsxs(Conversation, { className: "relative flex-1 min-h-0", children: [
7768
- /* @__PURE__ */ jsxRuntime.jsx(ConversationContent, { className: "mx-auto w-full max-w-4xl", children: messages.length === 0 && isLoadingHistory ? /* @__PURE__ */ jsxRuntime.jsx(
7769
- ConversationEmptyState,
7644
+ setError(formatPdfPreviewError(nextError));
7645
+ });
7646
+ return () => {
7647
+ cancelled = true;
7648
+ loadingTask?.destroy();
7649
+ pdfDocument?.destroy();
7650
+ pdfViewerRef.current?.cleanup();
7651
+ pdfViewerRef.current = null;
7652
+ eventBusRef.current = null;
7653
+ findControllerRef.current = null;
7654
+ };
7655
+ }, [file.content]);
7656
+ const setViewerPage = (nextPage) => {
7657
+ const viewer = pdfViewerRef.current;
7658
+ if (!viewer) return;
7659
+ const clampedPage = Math.min(
7660
+ Math.max(1, nextPage),
7661
+ Math.max(1, viewer.pagesCount)
7662
+ );
7663
+ viewer.currentPageNumber = clampedPage;
7664
+ setPageNumber(clampedPage);
7665
+ };
7666
+ const setViewerScale = (nextScale) => {
7667
+ const viewer = pdfViewerRef.current;
7668
+ if (!viewer) return;
7669
+ const clampedScale = Math.min(3, Math.max(0.5, nextScale));
7670
+ viewer.currentScale = clampedScale;
7671
+ setScale(clampedScale);
7672
+ };
7673
+ const runFind = (findPrevious = false) => {
7674
+ const eventBus = eventBusRef.current;
7675
+ if (!eventBus || !query.trim()) return;
7676
+ eventBus.dispatch("find", {
7677
+ source: eventBus,
7678
+ type: "again",
7679
+ query,
7680
+ phraseSearch: true,
7681
+ caseSensitive: false,
7682
+ entireWord: false,
7683
+ highlightAll: true,
7684
+ findPrevious,
7685
+ matchDiacritics: true
7686
+ });
7687
+ };
7688
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-full flex-col", children: [
7689
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
7690
+ /* @__PURE__ */ jsxRuntime.jsx(
7691
+ Button,
7770
7692
  {
7771
- icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquareIcon, { className: "size-8 opacity-40" }),
7772
- title: "Loading conversation",
7773
- description: "Fetching the session history."
7693
+ type: "button",
7694
+ variant: "ghost",
7695
+ size: "icon-xs",
7696
+ onClick: () => setViewerPage(pageNumber - 1),
7697
+ disabled: pageNumber <= 1,
7698
+ "aria-label": "Previous PDF page",
7699
+ title: "Previous page",
7700
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeftIcon, { className: "size-3.5" })
7774
7701
  }
7775
- ) : messages.length === 0 && !disabled ? /* @__PURE__ */ jsxRuntime.jsx(
7776
- ConversationEmptyState,
7702
+ ),
7703
+ /* @__PURE__ */ jsxRuntime.jsx(
7704
+ Input,
7777
7705
  {
7778
- icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquareIcon, { className: "size-8 opacity-40" }),
7779
- title: "Start a conversation",
7780
- description: "Ask me anything \u2014 I can search, analyze, write code, and more."
7706
+ type: "number",
7707
+ min: 1,
7708
+ max: pagesCount || 1,
7709
+ value: pageNumber,
7710
+ onChange: (event) => setViewerPage(Number(event.target.value)),
7711
+ "aria-label": "PDF page number",
7712
+ className: "h-7 w-12 border-input px-1 text-center text-xs"
7781
7713
  }
7782
- ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7783
- groups.map((group) => {
7784
- if (group.role === "user") {
7785
- const msg = group.messages[0];
7786
- return /* @__PURE__ */ jsxRuntime.jsx(
7787
- ChatMessage,
7788
- {
7789
- message: msg,
7790
- onFileSelect
7791
- },
7792
- msg.id
7793
- );
7794
- }
7795
- if (group.role === "system") {
7796
- const msg = group.messages[0];
7797
- const groupRetry2 = msg.id === activeFailureId ? onRetry : void 0;
7798
- return /* @__PURE__ */ jsxRuntime.jsx(
7799
- OrphanSystemMarker,
7800
- {
7801
- message: msg,
7802
- sessionId,
7803
- onRetry: groupRetry2
7804
- },
7805
- msg.id
7806
- );
7807
- }
7808
- const groupTail = group.messages[group.messages.length - 1];
7809
- const groupRetry = groupTail.id === activeFailureId ? onRetry : void 0;
7810
- return /* @__PURE__ */ jsxRuntime.jsx(
7811
- AssistantGroup,
7812
- {
7813
- messages: group.messages,
7814
- lastGlobalIndex: group.lastGlobalIndex,
7815
- totalMessages: messages.length,
7816
- isRunning,
7817
- sessionId,
7818
- artifactFallbacks,
7819
- onFileSelect,
7820
- onRetry: groupRetry
7821
- },
7822
- group.messages[0].id
7823
- );
7824
- }),
7825
- isRunning && messages.length > 0 && messages[messages.length - 1].role === "user" && /* @__PURE__ */ jsxRuntime.jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsxRuntime.jsx(MessageContent, { children: /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: "Working on it..." }) }) })
7826
- ] }) }),
7827
- /* @__PURE__ */ jsxRuntime.jsx(ConversationScrollButton, {})
7828
- ] }),
7829
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
7830
- retryIndicator && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(RetryBanner, { indicator: retryIndicator }) }),
7831
- disabled && disabledReason ? /* @__PURE__ */ jsxRuntime.jsx(
7832
- "div",
7714
+ ),
7715
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
7716
+ "/ ",
7717
+ pagesCount || "-"
7718
+ ] }),
7719
+ /* @__PURE__ */ jsxRuntime.jsx(
7720
+ Button,
7833
7721
  {
7834
- className: "rounded border border-line bg-muted/40 px-3 py-2 text-sm text-muted-foreground",
7835
- role: "status",
7836
- children: disabledReason
7722
+ type: "button",
7723
+ variant: "ghost",
7724
+ size: "icon-xs",
7725
+ onClick: () => setViewerPage(pageNumber + 1),
7726
+ disabled: pagesCount > 0 && pageNumber >= pagesCount,
7727
+ "aria-label": "Next PDF page",
7728
+ title: "Next page",
7729
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "size-3.5" })
7837
7730
  }
7838
- ) : /* @__PURE__ */ jsxRuntime.jsx(
7839
- ChatComposer,
7731
+ ),
7732
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
7733
+ /* @__PURE__ */ jsxRuntime.jsx(
7734
+ Button,
7840
7735
  {
7841
- onSend,
7842
- onStop,
7843
- isRunning,
7844
- disabled,
7845
- disabledReason,
7846
- tokenUsage,
7847
- onComposerError,
7848
- showBrowser,
7849
- onToggleBrowser,
7850
- showWorkspace,
7851
- onToggleWorkspace,
7852
- canShowBrowser,
7853
- canShowWorkspace
7736
+ type: "button",
7737
+ variant: "ghost",
7738
+ size: "icon-xs",
7739
+ onClick: () => setViewerScale(scale - 0.1),
7740
+ disabled: scale <= 0.5,
7741
+ "aria-label": "Zoom out PDF",
7742
+ title: "Zoom out",
7743
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MinusIcon, { className: "size-3.5" })
7744
+ }
7745
+ ),
7746
+ /* @__PURE__ */ jsxRuntime.jsxs(
7747
+ "button",
7748
+ {
7749
+ type: "button",
7750
+ className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
7751
+ onClick: () => {
7752
+ const viewer = pdfViewerRef.current;
7753
+ if (!viewer) return;
7754
+ viewer.currentScaleValue = "page-width";
7755
+ setScale(viewer.currentScale || 1);
7756
+ },
7757
+ "aria-label": "Fit PDF to width",
7758
+ title: "Fit width",
7759
+ children: [
7760
+ Math.round(scale * 100),
7761
+ "%"
7762
+ ]
7763
+ }
7764
+ ),
7765
+ /* @__PURE__ */ jsxRuntime.jsx(
7766
+ Button,
7767
+ {
7768
+ type: "button",
7769
+ variant: "ghost",
7770
+ size: "icon-xs",
7771
+ onClick: () => setViewerScale(scale + 0.1),
7772
+ disabled: scale >= 3,
7773
+ "aria-label": "Zoom in PDF",
7774
+ title: "Zoom in",
7775
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlusIcon, { className: "size-3.5" })
7776
+ }
7777
+ ),
7778
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
7779
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SearchIcon, { className: "size-3.5 text-muted-foreground" }),
7780
+ /* @__PURE__ */ jsxRuntime.jsx(
7781
+ Input,
7782
+ {
7783
+ type: "search",
7784
+ value: query,
7785
+ onChange: (event) => setQuery(event.target.value),
7786
+ onKeyDown: (event) => {
7787
+ if (event.key === "Enter") runFind(event.shiftKey);
7788
+ },
7789
+ placeholder: "Find",
7790
+ "aria-label": "Find in PDF",
7791
+ className: "h-7 w-28 border-input px-1 text-xs"
7792
+ }
7793
+ ),
7794
+ /* @__PURE__ */ jsxRuntime.jsx(
7795
+ Button,
7796
+ {
7797
+ type: "button",
7798
+ variant: "ghost",
7799
+ size: "xs",
7800
+ onClick: () => runFind(false),
7801
+ disabled: !query.trim(),
7802
+ children: "Find"
7854
7803
  }
7855
7804
  )
7856
- ] })
7805
+ ] }),
7806
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
7807
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
7808
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
7809
+ ] }),
7810
+ !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-[70vh] min-h-[420px] flex-1 bg-muted/20", children: /* @__PURE__ */ jsxRuntime.jsx(
7811
+ "div",
7812
+ {
7813
+ ref: containerRef,
7814
+ "aria-label": `PDF viewer for ${fileName2}`,
7815
+ className: "absolute inset-0 overflow-auto",
7816
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: viewerRef, className: "pdfViewer" })
7817
+ }
7818
+ ) })
7857
7819
  ] });
7858
7820
  }
7859
-
7860
- // src/components/ai-elements/file-tree.tsx
7861
- init_utils();
7862
- var noop = () => {
7821
+ function decodeBase64(content) {
7822
+ const binary = globalThis.atob(content);
7823
+ const bytes = new Uint8Array(binary.length);
7824
+ for (let index = 0; index < binary.length; index += 1) {
7825
+ bytes[index] = binary.charCodeAt(index);
7826
+ }
7827
+ return bytes;
7828
+ }
7829
+ function formatPdfPreviewError(error) {
7830
+ if (error instanceof Error && (error.name === "InvalidCharacterError" || error.message === "Invalid character")) {
7831
+ return "PDF preview data is not valid base64.";
7832
+ }
7833
+ return error instanceof Error ? error.message : "Failed to render PDF preview.";
7834
+ }
7835
+ var _EXTENSION_KIND = {
7836
+ // Documents
7837
+ pdf: { icon: lucideReact.FileTextIcon, typeLabel: "PDF" },
7838
+ doc: { icon: lucideReact.FileTextIcon, typeLabel: "Word document" },
7839
+ docx: { icon: lucideReact.FileTextIcon, typeLabel: "Word document" },
7840
+ odt: { icon: lucideReact.FileTextIcon, typeLabel: "OpenDocument text" },
7841
+ rtf: { icon: lucideReact.FileTextIcon, typeLabel: "Rich text" },
7842
+ txt: { icon: lucideReact.FileTextIcon, typeLabel: "Text" },
7843
+ md: { icon: lucideReact.FileTextIcon, typeLabel: "Markdown" },
7844
+ // Spreadsheets
7845
+ xls: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "Spreadsheet" },
7846
+ xlsx: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "Spreadsheet" },
7847
+ ods: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "OpenDocument spreadsheet" },
7848
+ csv: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "CSV" },
7849
+ tsv: { icon: lucideReact.FileSpreadsheetIcon, typeLabel: "TSV" },
7850
+ // Presentations / charts
7851
+ ppt: { icon: lucideReact.FileChartLineIcon, typeLabel: "Presentation" },
7852
+ pptx: { icon: lucideReact.FileChartLineIcon, typeLabel: "Presentation" },
7853
+ // Images
7854
+ png: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
7855
+ jpg: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
7856
+ jpeg: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
7857
+ gif: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
7858
+ svg: { icon: lucideReact.FileImageIcon, typeLabel: "SVG image" },
7859
+ webp: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
7860
+ bmp: { icon: lucideReact.FileImageIcon, typeLabel: "Image" },
7861
+ // Audio / video
7862
+ mp3: { icon: lucideReact.FileAudioIcon, typeLabel: "Audio" },
7863
+ wav: { icon: lucideReact.FileAudioIcon, typeLabel: "Audio" },
7864
+ ogg: { icon: lucideReact.FileAudioIcon, typeLabel: "Audio" },
7865
+ mp4: { icon: lucideReact.FileVideoIcon, typeLabel: "Video" },
7866
+ mov: { icon: lucideReact.FileVideoIcon, typeLabel: "Video" },
7867
+ webm: { icon: lucideReact.FileVideoIcon, typeLabel: "Video" },
7868
+ // Archives
7869
+ zip: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
7870
+ tar: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
7871
+ gz: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
7872
+ bz2: { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
7873
+ "7z": { icon: lucideReact.FileArchiveIcon, typeLabel: "Archive" },
7874
+ // Code
7875
+ py: { icon: lucideReact.FileCodeIcon, typeLabel: "Python" },
7876
+ js: { icon: lucideReact.FileCodeIcon, typeLabel: "JavaScript" },
7877
+ ts: { icon: lucideReact.FileCodeIcon, typeLabel: "TypeScript" },
7878
+ tsx: { icon: lucideReact.FileCodeIcon, typeLabel: "TypeScript" },
7879
+ jsx: { icon: lucideReact.FileCodeIcon, typeLabel: "JavaScript" },
7880
+ json: { icon: lucideReact.FileCodeIcon, typeLabel: "JSON" },
7881
+ yaml: { icon: lucideReact.FileCodeIcon, typeLabel: "YAML" },
7882
+ yml: { icon: lucideReact.FileCodeIcon, typeLabel: "YAML" },
7883
+ toml: { icon: lucideReact.FileCodeIcon, typeLabel: "TOML" },
7884
+ sh: { icon: lucideReact.CodeIcon, typeLabel: "Shell" },
7885
+ rs: { icon: lucideReact.FileCodeIcon, typeLabel: "Rust" },
7886
+ go: { icon: lucideReact.FileCodeIcon, typeLabel: "Go" },
7887
+ rb: { icon: lucideReact.FileCodeIcon, typeLabel: "Ruby" },
7888
+ java: { icon: lucideReact.FileCodeIcon, typeLabel: "Java" },
7889
+ cpp: { icon: lucideReact.FileCodeIcon, typeLabel: "C++" },
7890
+ c: { icon: lucideReact.FileCodeIcon, typeLabel: "C" },
7891
+ html: { icon: lucideReact.FileCodeIcon, typeLabel: "HTML" },
7892
+ css: { icon: lucideReact.FileCodeIcon, typeLabel: "CSS" },
7893
+ scss: { icon: lucideReact.FileCodeIcon, typeLabel: "Sass" },
7894
+ sql: { icon: lucideReact.FileCodeIcon, typeLabel: "SQL" }
7863
7895
  };
7864
- var FileTreeContext = react.createContext({
7865
- expandedPaths: /* @__PURE__ */ new Set(),
7866
- togglePath: noop
7867
- });
7868
- function FileTree({
7869
- expanded: controlledExpanded,
7870
- defaultExpanded = /* @__PURE__ */ new Set(),
7871
- selectedPath,
7872
- onSelect,
7873
- onExpandedChange,
7874
- className,
7875
- children,
7876
- ...props
7877
- }) {
7878
- const [internalExpanded, setInternalExpanded] = react.useState(defaultExpanded);
7879
- const expandedPaths = controlledExpanded ?? internalExpanded;
7880
- const togglePath = react.useCallback(
7881
- (path) => {
7882
- const next = new Set(expandedPaths);
7883
- if (next.has(path)) {
7884
- next.delete(path);
7885
- } else {
7886
- next.add(path);
7887
- }
7888
- setInternalExpanded(next);
7889
- onExpandedChange?.(next);
7890
- },
7891
- [expandedPaths, onExpandedChange]
7892
- );
7893
- const contextValue = react.useMemo(
7894
- () => ({
7895
- expandedPaths,
7896
- onSelect,
7897
- selectedPath: selectedPath ?? void 0,
7898
- togglePath
7899
- }),
7900
- [expandedPaths, onSelect, selectedPath, togglePath]
7901
- );
7902
- return /* @__PURE__ */ jsxRuntime.jsx(FileTreeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
7903
- "div",
7904
- {
7905
- className: cn("rounded-lg border bg-background text-sm", className),
7906
- role: "tree",
7907
- ...props,
7908
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2", children })
7909
- }
7910
- ) });
7896
+ function _extension(path) {
7897
+ const idx = path.lastIndexOf(".");
7898
+ if (idx < 0 || idx === path.length - 1) return "";
7899
+ return path.slice(idx + 1).toLowerCase();
7911
7900
  }
7912
- function FileTreeIcon({
7913
- className,
7914
- children,
7915
- ...props
7916
- }) {
7917
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("shrink-0", className), ...props, children });
7901
+ function _fileKindFor(path) {
7902
+ const ext = _extension(path);
7903
+ return _EXTENSION_KIND[ext] ?? { icon: lucideReact.FileIcon, typeLabel: "File" };
7918
7904
  }
7919
- function FileTreeName({
7920
- className,
7921
- children,
7922
- ...props
7923
- }) {
7924
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("truncate", className), ...props, children });
7905
+ function _basename(path) {
7906
+ const idx = path.lastIndexOf("/");
7907
+ return idx >= 0 ? path.slice(idx + 1) : path;
7925
7908
  }
7926
- function FileTreeFolder({
7909
+ function WorkspaceFileCard({
7910
+ sessionId,
7927
7911
  path,
7928
- name,
7929
- className,
7930
- children,
7931
- ...props
7912
+ label
7932
7913
  }) {
7933
- const { expandedPaths, togglePath, selectedPath, onSelect } = react.useContext(FileTreeContext);
7934
- const isExpanded = expandedPaths.has(path);
7935
- const isSelected = selectedPath === path;
7936
- const handleOpenChange = react.useCallback(() => {
7937
- togglePath(path);
7938
- }, [togglePath, path]);
7939
- const handleSelect = react.useCallback(() => {
7940
- onSelect?.(path);
7941
- }, [onSelect, path]);
7942
- return /* @__PURE__ */ jsxRuntime.jsx(Collapsible, { onOpenChange: handleOpenChange, open: isExpanded, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("", className), role: "treeitem", tabIndex: 0, ...props, children: [
7914
+ const { adapter } = useAgentChatAdapterContext();
7915
+ const kind = _fileKindFor(path);
7916
+ const Icon = kind.icon;
7917
+ const downloadUrl = adapter.getWorkspaceDownloadUrl({ sessionId, path });
7918
+ const downloadName = _basename(path) || label;
7919
+ const [previewOpen, setPreviewOpen] = react.useState(false);
7920
+ const handleCardClick = react.useCallback(() => {
7921
+ setPreviewOpen(true);
7922
+ }, []);
7923
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7943
7924
  /* @__PURE__ */ jsxRuntime.jsxs(
7944
7925
  "div",
7945
7926
  {
7927
+ role: "button",
7928
+ tabIndex: 0,
7929
+ onClick: handleCardClick,
7930
+ onKeyDown: (e) => {
7931
+ if (e.key === "Enter" || e.key === " ") {
7932
+ e.preventDefault();
7933
+ handleCardClick();
7934
+ }
7935
+ },
7946
7936
  className: cn(
7947
- "flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50",
7948
- isSelected && "bg-muted"
7937
+ "group/file flex items-center gap-3 rounded-lg border border-border",
7938
+ "bg-muted/20 px-3 py-2 text-left transition-colors",
7939
+ "cursor-pointer hover:bg-muted/40"
7949
7940
  ),
7950
7941
  children: [
7951
- /* @__PURE__ */ jsxRuntime.jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
7952
- "button",
7953
- {
7954
- className: "flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0",
7955
- type: "button",
7956
- "aria-label": isExpanded ? `Collapse ${name}` : `Expand ${name}`,
7957
- children: /* @__PURE__ */ jsxRuntime.jsx(
7958
- lucideReact.ChevronRightIcon,
7959
- {
7960
- className: cn(
7961
- "size-4 shrink-0 text-muted-foreground transition-transform",
7962
- isExpanded && "rotate-90"
7963
- )
7964
- }
7965
- )
7966
- }
7967
- ) }),
7942
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-10 shrink-0 items-center justify-center rounded-md border border-border bg-background", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-5 text-muted-foreground", "aria-hidden": true }) }),
7943
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
7944
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-sm font-medium text-foreground", children: label }),
7945
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs text-muted-foreground", children: kind.typeLabel })
7946
+ ] }),
7968
7947
  /* @__PURE__ */ jsxRuntime.jsxs(
7969
- "button",
7948
+ "a",
7970
7949
  {
7971
- className: "flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left",
7972
- onClick: handleSelect,
7973
- type: "button",
7950
+ href: downloadUrl,
7951
+ download: downloadName,
7952
+ onClick: (e) => e.stopPropagation(),
7953
+ className: cn(
7954
+ "shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border",
7955
+ "bg-background px-2.5 py-1.5 text-xs font-medium text-foreground",
7956
+ "transition-colors hover:bg-muted"
7957
+ ),
7958
+ "aria-label": `Download ${downloadName}`,
7974
7959
  children: [
7975
- /* @__PURE__ */ jsxRuntime.jsx(FileTreeIcon, { children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpenIcon, { className: "size-4 text-blue-500" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderIcon, { className: "size-4 text-blue-500" }) }),
7976
- /* @__PURE__ */ jsxRuntime.jsx(FileTreeName, { children: name })
7960
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-3.5", "aria-hidden": true }),
7961
+ "Download"
7977
7962
  ]
7978
7963
  }
7979
7964
  )
7980
7965
  ]
7981
7966
  }
7982
7967
  ),
7983
- /* @__PURE__ */ jsxRuntime.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-4 border-l border-muted-foreground/50 pl-2", children }) })
7984
- ] }) });
7968
+ /* @__PURE__ */ jsxRuntime.jsx(
7969
+ WorkspaceFilePreviewDialog,
7970
+ {
7971
+ open: previewOpen,
7972
+ onOpenChange: setPreviewOpen,
7973
+ sessionId,
7974
+ path,
7975
+ downloadUrl
7976
+ }
7977
+ )
7978
+ ] });
7985
7979
  }
7986
- function FileTreeFile({
7980
+ function WorkspaceFilePreviewDialog({
7981
+ open,
7982
+ onOpenChange,
7983
+ sessionId,
7987
7984
  path,
7988
- name,
7989
- icon,
7990
- className,
7991
- children,
7992
- ...props
7985
+ downloadUrl
7993
7986
  }) {
7994
- const { selectedPath, onSelect } = react.useContext(FileTreeContext);
7995
- const isSelected = selectedPath === path;
7996
- const handleClick = react.useCallback(() => {
7997
- onSelect?.(path);
7998
- }, [onSelect, path]);
7999
- const handleKeyDown = react.useCallback(
8000
- (event) => {
8001
- if (event.key === "Enter" || event.key === " ") {
8002
- onSelect?.(path);
7987
+ const { adapter } = useAgentChatAdapterContext();
7988
+ const [file, setFile] = react.useState(null);
7989
+ const [loading, setLoading] = react.useState(false);
7990
+ const [error, setError] = react.useState(null);
7991
+ react.useEffect(() => {
7992
+ if (!open) return;
7993
+ let cancelled = false;
7994
+ setLoading(true);
7995
+ setError(null);
7996
+ setFile(null);
7997
+ adapter.getWorkspaceFile({ sessionId, path }).then((result) => {
7998
+ if (cancelled) return;
7999
+ setFile(result);
8000
+ setLoading(false);
8001
+ }).catch((err) => {
8002
+ if (cancelled) return;
8003
+ const message = err instanceof Error ? err.message : "Failed to load file";
8004
+ setError(message);
8005
+ setLoading(false);
8006
+ });
8007
+ return () => {
8008
+ cancelled = true;
8009
+ };
8010
+ }, [adapter, sessionId, path, open]);
8011
+ return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "flex h-[80vh] w-full max-w-3xl flex-col gap-0 overflow-hidden p-0", children: [
8012
+ /* @__PURE__ */ jsxRuntime.jsx(DialogHeader, { className: "border-b border-line px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { className: "truncate text-sm", children: _basename(path) }) }),
8013
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsxRuntime.jsx(
8014
+ FileViewer,
8015
+ {
8016
+ file,
8017
+ loading,
8018
+ error,
8019
+ downloadUrl,
8020
+ onDelete: null,
8021
+ onClose: () => onOpenChange(false)
8003
8022
  }
8004
- },
8005
- [onSelect, path]
8006
- );
8007
- return /* @__PURE__ */ jsxRuntime.jsx(
8008
- "div",
8009
- {
8010
- className: cn(
8011
- "group flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50",
8012
- isSelected && "bg-muted",
8013
- className
8014
- ),
8015
- onClick: handleClick,
8016
- onKeyDown: handleKeyDown,
8017
- role: "treeitem",
8018
- tabIndex: 0,
8019
- ...props,
8020
- children: children ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8021
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-4 shrink-0" }),
8022
- /* @__PURE__ */ jsxRuntime.jsx(FileTreeIcon, { children: icon ?? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileIcon, { className: "size-4 text-muted-foreground" }) }),
8023
- /* @__PURE__ */ jsxRuntime.jsx(FileTreeName, { children: name })
8024
- ] })
8025
- }
8026
- );
8023
+ ) })
8024
+ ] }) });
8027
8025
  }
8028
-
8029
- // src/components/ui/skeleton.tsx
8030
- init_utils();
8031
- function Skeleton({ className, ...props }) {
8032
- return /* @__PURE__ */ jsxRuntime.jsx(
8033
- "div",
8026
+ function resolveArtifactRef(ref, messages) {
8027
+ for (const msg of messages) {
8028
+ if (msg.role !== "system" || msg.systemKind !== "artifact") continue;
8029
+ const meta = msg.systemMeta ?? {};
8030
+ if (meta.artifact_id !== ref) continue;
8031
+ const kind = meta.kind ?? "markdown";
8032
+ const version = typeof meta.version === "number" ? meta.version : 1;
8033
+ const name = typeof meta.name === "string" ? meta.name : "";
8034
+ return { artifactId: ref, name, kind, version };
8035
+ }
8036
+ return null;
8037
+ }
8038
+ function TurnSummaryCard({
8039
+ summary,
8040
+ sessionId,
8041
+ messages,
8042
+ onFileSelect,
8043
+ onCommandSelect
8044
+ }) {
8045
+ const hasRecap = summary.recap.trim().length > 0;
8046
+ const hasArtifacts = summary.artifacts.length > 0;
8047
+ if (!hasRecap && !hasArtifacts) return null;
8048
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 rounded border border-border bg-muted/20 px-3 py-2 text-sm", children: [
8049
+ hasRecap && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 whitespace-pre-wrap text-foreground", children: summary.recap }),
8050
+ hasArtifacts && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: summary.artifacts.map((artifact, i) => {
8051
+ const key = `${artifact.kind}:${artifact.ref}:${i}`;
8052
+ if (artifact.kind === "file" && sessionId) {
8053
+ return /* @__PURE__ */ jsxRuntime.jsx(
8054
+ WorkspaceFileCard,
8055
+ {
8056
+ sessionId,
8057
+ path: artifact.ref,
8058
+ label: artifact.label
8059
+ },
8060
+ key
8061
+ );
8062
+ }
8063
+ if (artifact.kind === "artifact") {
8064
+ const resolved = sessionId ? resolveArtifactRef(artifact.ref, messages) : null;
8065
+ if (resolved && sessionId) {
8066
+ return /* @__PURE__ */ jsxRuntime.jsx(
8067
+ ArtifactBlock,
8068
+ {
8069
+ sessionId,
8070
+ artifactId: resolved.artifactId,
8071
+ name: resolved.name || artifact.label,
8072
+ kind: resolved.kind,
8073
+ version: resolved.version
8074
+ },
8075
+ key
8076
+ );
8077
+ }
8078
+ }
8079
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8080
+ "div",
8081
+ {
8082
+ className: "flex items-baseline gap-2 text-sm",
8083
+ children: [
8084
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", "aria-hidden": true, children: "\u2022" }),
8085
+ /* @__PURE__ */ jsxRuntime.jsx(
8086
+ ArtifactRow,
8087
+ {
8088
+ artifact,
8089
+ sessionId,
8090
+ messages,
8091
+ onFileSelect,
8092
+ onCommandSelect
8093
+ }
8094
+ )
8095
+ ]
8096
+ },
8097
+ key
8098
+ );
8099
+ }) })
8100
+ ] });
8101
+ }
8102
+ function ArtifactRow({
8103
+ artifact,
8104
+ sessionId,
8105
+ messages,
8106
+ onFileSelect,
8107
+ onCommandSelect
8108
+ }) {
8109
+ if (artifact.kind === "file") {
8110
+ if (!onFileSelect) {
8111
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
8112
+ }
8113
+ return /* @__PURE__ */ jsxRuntime.jsx(
8114
+ "button",
8115
+ {
8116
+ type: "button",
8117
+ onClick: () => onFileSelect(artifact.ref),
8118
+ className: "truncate cursor-pointer text-primary hover:underline",
8119
+ children: artifact.label
8120
+ }
8121
+ );
8122
+ }
8123
+ if (artifact.kind === "url") {
8124
+ return /* @__PURE__ */ jsxRuntime.jsx(
8125
+ "a",
8126
+ {
8127
+ href: artifact.ref,
8128
+ target: "_blank",
8129
+ rel: "noopener noreferrer",
8130
+ className: "truncate text-primary hover:underline",
8131
+ children: artifact.label
8132
+ }
8133
+ );
8134
+ }
8135
+ if (artifact.kind === "command") {
8136
+ if (!onCommandSelect) {
8137
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
8138
+ }
8139
+ return /* @__PURE__ */ jsxRuntime.jsx(
8140
+ "button",
8141
+ {
8142
+ type: "button",
8143
+ onClick: () => onCommandSelect(artifact.ref),
8144
+ className: "truncate text-left cursor-pointer text-primary hover:underline",
8145
+ children: artifact.label
8146
+ }
8147
+ );
8148
+ }
8149
+ const resolved = sessionId ? resolveArtifactRef(artifact.ref, messages) : null;
8150
+ if (!resolved) {
8151
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
8152
+ }
8153
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
8154
+ ArtifactBlock,
8034
8155
  {
8035
- className: cn("animate-pulse rounded-md bg-muted", className),
8036
- ...props
8156
+ sessionId,
8157
+ artifactId: resolved.artifactId,
8158
+ name: resolved.name || artifact.label,
8159
+ kind: resolved.kind,
8160
+ version: resolved.version
8037
8161
  }
8038
- );
8162
+ ) });
8039
8163
  }
8040
8164
 
8041
- // src/components/workspace/workspace-panel.tsx
8165
+ // src/components/chat/chat-thread.tsx
8042
8166
  init_utils();
8043
-
8044
- // src/lib/format.ts
8045
- function formatFileSize(bytes) {
8046
- if (bytes < 1024) return `${bytes} B`;
8047
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
8048
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
8049
- }
8050
- function getLanguageHint(path) {
8051
- const name = path.split("/").pop() ?? path;
8052
- const dot = name.lastIndexOf(".");
8053
- if (dot < 0) return "plaintext";
8054
- const ext = name.slice(dot).toLowerCase();
8055
- const map = {
8056
- ".py": "python",
8057
- ".js": "javascript",
8058
- ".ts": "typescript",
8059
- ".tsx": "tsx",
8060
- ".jsx": "jsx",
8061
- ".json": "json",
8062
- ".yaml": "yaml",
8063
- ".yml": "yaml",
8064
- ".toml": "toml",
8065
- ".sql": "sql",
8066
- ".sh": "bash",
8067
- ".css": "css",
8068
- ".html": "html",
8069
- ".md": "markdown",
8070
- ".rs": "rust",
8071
- ".go": "go",
8072
- ".java": "java",
8073
- ".rb": "ruby",
8074
- ".php": "php",
8075
- ".c": "c",
8076
- ".cpp": "cpp",
8077
- ".h": "c",
8078
- ".hpp": "cpp"
8079
- };
8080
- return map[ext] ?? "plaintext";
8081
- }
8082
- var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
8083
- var PDF_WORKER_SRC = new URL(
8084
- "pdfjs-dist/legacy/build/pdf.worker.mjs",
8085
- (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))
8086
- ).toString();
8087
- function FileViewer({
8088
- file,
8089
- loading,
8090
- error,
8091
- downloadUrl,
8092
- onDelete,
8093
- onClose
8094
- }) {
8095
- const visible = loading || file !== null || error !== null;
8096
- if (!visible) return null;
8097
- const fileName2 = file?.path.split("/").pop() ?? "File";
8098
- const lang = file ? getLanguageHint(file.path) : "";
8099
- const isPdf = file?.mime_type === "application/pdf" || file?.path.toLowerCase().endsWith(".pdf") || false;
8100
- const isImage = file?.encoding === "base64" && !isPdf;
8101
- const HeaderIcon = isImage ? lucideReact.ImageIcon : lucideReact.FileTextIcon;
8102
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 flex-col border-t border-line", children: [
8103
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
8104
- /* @__PURE__ */ jsxRuntime.jsx(HeaderIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
8105
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
8106
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs font-medium", children: fileName2 }),
8107
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "truncate text-[10px] text-muted-foreground", children: [
8108
- file?.path ?? "Loading...",
8109
- file && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-1.5", children: [
8110
- formatFileSize(file.size),
8111
- file.truncated && " (truncated)"
8112
- ] })
8167
+ function messageToEntries(msg, isLast, artifactFallbacks) {
8168
+ if (msg.role === "system") {
8169
+ if (msg.systemKind === "skill_invoked") {
8170
+ return [{
8171
+ kind: "skill_invoked",
8172
+ key: msg.id,
8173
+ skill: msg.systemMeta?.skill ?? msg.content,
8174
+ stagedAt: msg.systemMeta?.staged_at ?? null
8175
+ }];
8176
+ }
8177
+ if (msg.systemKind === "artifact") {
8178
+ const { artifactId, name, kind, version } = unpackArtifactMeta(
8179
+ msg.systemMeta,
8180
+ msg.content
8181
+ );
8182
+ return [{
8183
+ kind: "artifact",
8184
+ key: msg.id,
8185
+ artifactId,
8186
+ name,
8187
+ artifactKind: kind,
8188
+ version
8189
+ }];
8190
+ }
8191
+ return [];
8192
+ }
8193
+ const entries = [];
8194
+ const hasToolCalls = !!(msg.toolCalls && msg.toolCalls.length > 0);
8195
+ const hasContent = !!(msg.content && msg.content !== msg.reasoning);
8196
+ const isStreaming = msg.status === "streaming" && isLast;
8197
+ const effectiveReasoning = hasToolCalls && hasContent ? msg.reasoning ? msg.reasoning + "\n" + msg.content : msg.content : msg.reasoning;
8198
+ const effectiveHasContent = hasContent && !hasToolCalls;
8199
+ if (effectiveReasoning) {
8200
+ entries.push({
8201
+ kind: "reasoning",
8202
+ key: `${msg.id}-reasoning`,
8203
+ reasoning: effectiveReasoning,
8204
+ isStreaming: isStreaming && !effectiveHasContent && !hasToolCalls
8205
+ });
8206
+ }
8207
+ if (hasToolCalls) {
8208
+ for (const tc of msg.toolCalls) {
8209
+ if (tc.toolName === "create_artifact") {
8210
+ const resolvedName = parseArgs(tc.args)?.name ?? artifactFallbacks[tc.id];
8211
+ const status = effectiveStatus(tc);
8212
+ if (!resolvedName && status !== "error") continue;
8213
+ entries.push({
8214
+ kind: "tool",
8215
+ key: tc.id,
8216
+ tc,
8217
+ resolvedArtifactName: resolvedName
8218
+ });
8219
+ continue;
8220
+ }
8221
+ if (tc.toolName === "terminal" && tc.status !== "running") {
8222
+ const parsed = parseTerminalResult(tc.result, tc.args);
8223
+ if (parsed && (parsed.exit_code !== 0 || parsed.error)) continue;
8224
+ }
8225
+ entries.push({ kind: "tool", key: tc.id, tc });
8226
+ }
8227
+ }
8228
+ if (effectiveHasContent) {
8229
+ entries.push({
8230
+ kind: "text",
8231
+ key: `${msg.id}-text`,
8232
+ content: msg.content,
8233
+ isStreaming,
8234
+ msg
8235
+ });
8236
+ }
8237
+ const hasRunningTool = hasToolCalls && msg.toolCalls.some(
8238
+ (tc) => tc.status === "running"
8239
+ );
8240
+ if (isStreaming && !effectiveHasContent && !hasRunningTool) {
8241
+ entries.push({ kind: "thinking", key: `${msg.id}-thinking` });
8242
+ }
8243
+ return entries;
8244
+ }
8245
+ function groupBrowserActivityEntries(entries) {
8246
+ const grouped = [];
8247
+ let buffer = [];
8248
+ const flush = () => {
8249
+ if (buffer.length === 0) return;
8250
+ grouped.push({
8251
+ kind: "browser_activity",
8252
+ key: `browser-activity-${buffer[0]?.id}-${buffer[buffer.length - 1]?.id}`,
8253
+ calls: buffer
8254
+ });
8255
+ buffer = [];
8256
+ };
8257
+ for (const entry of entries) {
8258
+ if (entry.kind === "tool" && isBrowserToolCall(entry.tc)) {
8259
+ buffer.push(entry.tc);
8260
+ continue;
8261
+ }
8262
+ flush();
8263
+ grouped.push(entry);
8264
+ }
8265
+ flush();
8266
+ return grouped;
8267
+ }
8268
+ function isBrowserToolCall(call) {
8269
+ return call.toolName.startsWith("browser_");
8270
+ }
8271
+ function groupWebSearchEntries(entries) {
8272
+ const grouped = [];
8273
+ let buffer = [];
8274
+ const flush = () => {
8275
+ if (buffer.length === 0) return;
8276
+ if (buffer.length === 1) {
8277
+ grouped.push({ kind: "tool", key: buffer[0].id, tc: buffer[0] });
8278
+ } else {
8279
+ grouped.push({
8280
+ kind: "web_search_group",
8281
+ key: `web-search-${buffer[0].id}-${buffer[buffer.length - 1].id}`,
8282
+ calls: buffer
8283
+ });
8284
+ }
8285
+ buffer = [];
8286
+ };
8287
+ for (const entry of entries) {
8288
+ if (entry.kind === "tool" && entry.tc.toolName === "web_search") {
8289
+ buffer.push(entry.tc);
8290
+ continue;
8291
+ }
8292
+ flush();
8293
+ grouped.push(entry);
8294
+ }
8295
+ flush();
8296
+ return grouped;
8297
+ }
8298
+ function groupMessages(messages) {
8299
+ const groups = [];
8300
+ let pendingSystem = [];
8301
+ const drainPendingAsOrphans = () => {
8302
+ for (const { msg, index } of pendingSystem) {
8303
+ groups.push({ role: "system", messages: [msg], lastGlobalIndex: index });
8304
+ }
8305
+ pendingSystem = [];
8306
+ };
8307
+ for (let i = 0; i < messages.length; i++) {
8308
+ const msg = messages[i];
8309
+ if (msg.role === "system") {
8310
+ pendingSystem.push({ msg, index: i });
8311
+ continue;
8312
+ }
8313
+ if (msg.role === "assistant") {
8314
+ const prev = groups[groups.length - 1];
8315
+ if (prev && prev.role === "assistant" && pendingSystem.length === 0) {
8316
+ prev.messages.push(msg);
8317
+ prev.lastGlobalIndex = i;
8318
+ } else {
8319
+ const folded = pendingSystem.map((p) => p.msg);
8320
+ pendingSystem = [];
8321
+ groups.push({
8322
+ role: "assistant",
8323
+ messages: [...folded, msg],
8324
+ lastGlobalIndex: i
8325
+ });
8326
+ }
8327
+ continue;
8328
+ }
8329
+ drainPendingAsOrphans();
8330
+ groups.push({ role: "user", messages: [msg], lastGlobalIndex: i });
8331
+ }
8332
+ drainPendingAsOrphans();
8333
+ return groups;
8334
+ }
8335
+ function unpackArtifactMeta(systemMeta, fallbackName) {
8336
+ const meta = systemMeta ?? {};
8337
+ return {
8338
+ artifactId: meta.artifact_id ?? "",
8339
+ name: meta.name ?? fallbackName,
8340
+ kind: meta.kind ?? "markdown",
8341
+ version: meta.version ?? 1
8342
+ };
8343
+ }
8344
+ function OrphanSystemMarker({
8345
+ message,
8346
+ sessionId,
8347
+ onRetry
8348
+ }) {
8349
+ if (message.systemKind === "skill_invoked") {
8350
+ const skill = message.systemMeta?.skill ?? message.content;
8351
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "my-2 flex items-center gap-2 px-4 text-xs text-foreground/60 ", children: [
8352
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-2 rounded-full bg-emerald-500" }),
8353
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
8354
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: "Skill" }),
8355
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground/60 truncate", children: skill })
8356
+ ] })
8357
+ ] });
8358
+ }
8359
+ if (message.systemKind === "artifact" && sessionId) {
8360
+ const unpacked = unpackArtifactMeta(message.systemMeta, message.content);
8361
+ return /* @__PURE__ */ jsxRuntime.jsx(
8362
+ ArtifactBlock,
8363
+ {
8364
+ sessionId,
8365
+ artifactId: unpacked.artifactId,
8366
+ name: unpacked.name,
8367
+ kind: unpacked.kind,
8368
+ version: unpacked.version
8369
+ }
8370
+ );
8371
+ }
8372
+ if (message.systemKind === "error" && message.errorInfo) {
8373
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto my-2 w-full max-w-4xl", children: /* @__PURE__ */ jsxRuntime.jsx(ErrorMessage, { errorInfo: message.errorInfo, onRetry }) });
8374
+ }
8375
+ return null;
8376
+ }
8377
+ function cancelledToolLabel(toolName) {
8378
+ const map = {
8379
+ terminal: "Command",
8380
+ execute_code: "Execute Code",
8381
+ read_file: "Read",
8382
+ write_file: "Write",
8383
+ patch: "Patch",
8384
+ search_files: "Search Files",
8385
+ list_files: "List Files",
8386
+ web_search: "Web Search",
8387
+ web_extract: "Web Fetch",
8388
+ web_crawl: "Web Crawl",
8389
+ session_search: "Session Search",
8390
+ memory: "Memory",
8391
+ todo: "Todo",
8392
+ skills_list: "Skills",
8393
+ skill_view: "Skill",
8394
+ consult_expert: "Expert",
8395
+ delegate_task: "Delegate",
8396
+ ask_user_question: "Ask User Question",
8397
+ process: "Process",
8398
+ create_artifact: "Artifact"
8399
+ };
8400
+ return map[toolName] ?? toolName;
8401
+ }
8402
+ function CancelledToolRow({ tc }) {
8403
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-sm", children: [
8404
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground/60", children: cancelledToolLabel(tc.toolName) }),
8405
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground/60/70", children: "Cancelled" })
8406
+ ] });
8407
+ }
8408
+ function TimelineEntryItem({
8409
+ entry,
8410
+ step,
8411
+ sessionId,
8412
+ onFileSelect
8413
+ }) {
8414
+ if (entry.kind === "reasoning") {
8415
+ return /* @__PURE__ */ jsxRuntime.jsx(ReasoningEntry, { entry, step });
8416
+ }
8417
+ if (entry.kind === "tool") {
8418
+ const rawStatus = effectiveStatus(entry.tc);
8419
+ const hideArtifactFailure = rawStatus === "error" && entry.tc.toolName === "create_artifact";
8420
+ const indicatorStatus = hideArtifactFailure ? "running" : rawStatus;
8421
+ const failureSummary = rawStatus === "error" && !hideArtifactFailure ? toolErrorSummary(entry.tc.result) : "";
8422
+ return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
8423
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
8424
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8425
+ /* @__PURE__ */ jsxRuntime.jsx(
8426
+ TimelineIndicator,
8427
+ {
8428
+ className: cn("size-2 border-none", statusColorClass(indicatorStatus))
8429
+ }
8430
+ )
8431
+ ] }),
8432
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: entry.tc.cancelled ? /* @__PURE__ */ jsxRuntime.jsx(CancelledToolRow, { tc: entry.tc }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
8433
+ /* @__PURE__ */ jsxRuntime.jsx(
8434
+ ToolCallBlock,
8435
+ {
8436
+ tc: entry.tc,
8437
+ resolvedArtifactName: entry.resolvedArtifactName,
8438
+ onFileSelect
8439
+ }
8440
+ ),
8441
+ failureSummary ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-full truncate text-xs text-destructive", title: failureSummary, children: failureSummary }) : null
8442
+ ] }) })
8443
+ ] });
8444
+ }
8445
+ if (entry.kind === "browser_activity") {
8446
+ return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
8447
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
8448
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8449
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-amber-500" })
8450
+ ] }),
8451
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx(BrowserActivityGroup, { calls: entry.calls }) })
8452
+ ] });
8453
+ }
8454
+ if (entry.kind === "web_search_group") {
8455
+ const allComplete = entry.calls.every((tc) => tc.status === "complete");
8456
+ const anyRunning = entry.calls.some((tc) => tc.status === "running");
8457
+ const indicatorClass = anyRunning ? "bg-emerald-500 animate-pulse" : allComplete ? "bg-emerald-500" : "bg-red-500";
8458
+ return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
8459
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
8460
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8461
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: cn("size-2 border-none", indicatorClass) })
8462
+ ] }),
8463
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx(WebSearchGroupBlock, { tcs: entry.calls }) })
8464
+ ] });
8465
+ }
8466
+ if (entry.kind === "text") {
8467
+ return /* @__PURE__ */ jsxRuntime.jsx(TextEntry, { entry, step });
8468
+ }
8469
+ if (entry.kind === "skill_invoked") {
8470
+ return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
8471
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
8472
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8473
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-emerald-500" })
8474
+ ] }),
8475
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 py-1 text-sm ", children: [
8476
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: "Skill" }),
8477
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground/60 truncate", children: entry.skill }),
8478
+ entry.stagedAt && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-foreground/60/70 ", children: [
8479
+ "staged at ",
8480
+ entry.stagedAt
8113
8481
  ] })
8482
+ ] }) })
8483
+ ] });
8484
+ }
8485
+ if (entry.kind === "artifact") {
8486
+ return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
8487
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
8488
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8489
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-sky-500" })
8114
8490
  ] }),
8115
- downloadUrl && /* @__PURE__ */ jsxRuntime.jsx(
8116
- "a",
8117
- {
8118
- href: downloadUrl,
8119
- download: fileName2,
8120
- className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
8121
- "aria-label": `Download ${fileName2}`,
8122
- title: "Download",
8123
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-3.5" })
8124
- }
8125
- ),
8126
- onDelete && /* @__PURE__ */ jsxRuntime.jsx(
8127
- "button",
8128
- {
8129
- type: "button",
8130
- onClick: onDelete,
8131
- className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
8132
- "aria-label": `Delete ${fileName2}`,
8133
- title: "Delete",
8134
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrashIcon, { className: "size-3.5" })
8135
- }
8136
- ),
8137
- /* @__PURE__ */ jsxRuntime.jsx(
8138
- "button",
8491
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: sessionId ? /* @__PURE__ */ jsxRuntime.jsx(
8492
+ ArtifactBlock,
8139
8493
  {
8140
- type: "button",
8141
- onClick: onClose,
8142
- className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
8143
- "aria-label": "Close file",
8144
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-3.5" })
8494
+ sessionId,
8495
+ artifactId: entry.artifactId,
8496
+ name: entry.name,
8497
+ kind: entry.artifactKind,
8498
+ version: entry.version
8145
8499
  }
8146
- )
8500
+ ) : null })
8501
+ ] });
8502
+ }
8503
+ return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
8504
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
8505
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8506
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-primary animate-pulse" })
8147
8507
  ] }),
8148
- /* @__PURE__ */ jsxRuntime.jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [
8149
- loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5 p-3", children: Array.from({ length: 10 }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
8150
- Skeleton,
8151
- {
8152
- className: "h-3.5 rounded",
8153
- style: {
8154
- width: `${SKELETON_WIDTHS[index % SKELETON_WIDTHS.length]}%`
8155
- }
8156
- },
8157
- index
8158
- )) }),
8159
- error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
8160
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
8161
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
8162
- ] }),
8163
- file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsxRuntime.jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
8164
- file && isPdf && file.encoding === "base64" && /* @__PURE__ */ jsxRuntime.jsx(PdfPreview, { file, fileName: fileName2 }),
8165
- file && isImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
8166
- "img",
8167
- {
8168
- src: `data:${file.mime_type ?? "image/png"};base64,${file.content}`,
8169
- alt: fileName2,
8170
- className: "max-h-[60vh] max-w-full rounded object-contain"
8171
- }
8172
- ) }),
8173
- file && !isImage && !isPdf && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-[11px] leading-relaxed text-foreground", children: /* @__PURE__ */ jsxRuntime.jsx("code", { "data-language": lang, children: file.content }) })
8508
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: "Working on it..." }) })
8509
+ ] });
8510
+ }
8511
+ function ReasoningEntry({
8512
+ entry,
8513
+ step
8514
+ }) {
8515
+ const reasoning = useSmoothStream(entry.reasoning, entry.isStreaming);
8516
+ return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
8517
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
8518
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8519
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2" })
8520
+ ] }),
8521
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(Reasoning, { isStreaming: entry.isStreaming, children: [
8522
+ /* @__PURE__ */ jsxRuntime.jsx(ReasoningTrigger, {}),
8523
+ /* @__PURE__ */ jsxRuntime.jsx(ReasoningContent, { children: reasoning })
8524
+ ] }) })
8525
+ ] });
8526
+ }
8527
+ function TextEntry({
8528
+ entry,
8529
+ step
8530
+ }) {
8531
+ const content = useSmoothStream(entry.content, entry.isStreaming);
8532
+ return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
8533
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
8534
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8535
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-foreground/40" })
8536
+ ] }),
8537
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineContent, { children: [
8538
+ /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { children: content }),
8539
+ entry.isFinalTurnText && entry.msg.status === "complete" && /* @__PURE__ */ jsxRuntime.jsx(TurnFeedback, { msg: entry.msg })
8174
8540
  ] })
8175
8541
  ] });
8176
8542
  }
8177
- function FileViewerError({ message }) {
8178
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
8179
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
8180
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: message })
8543
+ function deriveIterationLabel(message) {
8544
+ const calls = visibleToolCalls(message);
8545
+ if (calls.length === 0) {
8546
+ return message.reasoning ? "Thought through the problem" : null;
8547
+ }
8548
+ if (calls.length === 1) {
8549
+ const tc = calls[0];
8550
+ return deriveSingleToolLabel(tc);
8551
+ }
8552
+ const firstName = calls[0].toolName;
8553
+ const allSame = calls.every((tc) => tc.toolName === firstName);
8554
+ if (allSame) {
8555
+ const human = cancelledToolLabel(firstName);
8556
+ return `${human} \xD7 ${calls.length}`;
8557
+ }
8558
+ return `Used ${calls.length} tools`;
8559
+ }
8560
+ function deriveSingleToolLabel(tc) {
8561
+ const name = cancelledToolLabel(tc.toolName);
8562
+ if (_HEADER_HIDES_DETAIL.has(tc.toolName)) {
8563
+ return _HEADER_GENERIC_VERB[tc.toolName] ?? name;
8564
+ }
8565
+ const detail = extractToolDetail(tc);
8566
+ return detail ? `${name} \xB7 ${detail}` : name;
8567
+ }
8568
+ var _HEADER_HIDES_DETAIL = /* @__PURE__ */ new Set([
8569
+ "terminal",
8570
+ "execute_code",
8571
+ "memory"
8572
+ ]);
8573
+ var _HEADER_GENERIC_VERB = {
8574
+ terminal: "Ran a command",
8575
+ execute_code: "Executed code",
8576
+ memory: "Updated memory"
8577
+ };
8578
+ function extractToolDetail(tc) {
8579
+ const args = parseArgs(tc.args);
8580
+ if (!args) return null;
8581
+ const stringArg = (key) => {
8582
+ const v = args[key];
8583
+ return typeof v === "string" && v.length > 0 ? v : null;
8584
+ };
8585
+ switch (tc.toolName) {
8586
+ case "read_file":
8587
+ case "write_file":
8588
+ case "patch":
8589
+ case "list_files":
8590
+ case "search_files": {
8591
+ const path = stringArg("path") ?? stringArg("file_path");
8592
+ return path ? lastPathSegment(path) : null;
8593
+ }
8594
+ case "terminal": {
8595
+ const cmd = stringArg("command");
8596
+ return cmd ? truncate2(cmd, 40) : null;
8597
+ }
8598
+ case "execute_code": {
8599
+ const code3 = stringArg("code");
8600
+ return code3 ? truncate2(code3.split("\n")[0] ?? "", 40) : null;
8601
+ }
8602
+ case "web_search":
8603
+ case "web_crawl":
8604
+ return stringArg("query");
8605
+ case "web_extract":
8606
+ return stringArg("url");
8607
+ case "skill_view":
8608
+ case "skill_manage":
8609
+ return stringArg("name") ?? stringArg("skill");
8610
+ case "create_artifact":
8611
+ case "consult_expert":
8612
+ case "delegate_task":
8613
+ return stringArg("name") ?? stringArg("task") ?? stringArg("title");
8614
+ case "memory":
8615
+ return stringArg("action") ?? stringArg("key");
8616
+ default:
8617
+ return null;
8618
+ }
8619
+ }
8620
+ function lastPathSegment(path) {
8621
+ const cleaned = path.replace(/\/+$/, "");
8622
+ const idx = cleaned.lastIndexOf("/");
8623
+ return idx >= 0 ? cleaned.slice(idx + 1) : cleaned;
8624
+ }
8625
+ function truncate2(s, max) {
8626
+ return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
8627
+ }
8628
+ var _SIMPLE_MODE_HIDDEN_TOOLS = /* @__PURE__ */ new Set([
8629
+ "list_files",
8630
+ "search_files",
8631
+ "session_search",
8632
+ "skills_list",
8633
+ "process",
8634
+ "memory",
8635
+ // Shell commands and code execution are infrastructure plumbing the
8636
+ // user doesn't need to see when the goal is to know *what* the
8637
+ // agent accomplished, not *how*. Expert mode still has the full
8638
+ // command + output block.
8639
+ "terminal",
8640
+ "execute_code"
8641
+ ]);
8642
+ function _isHiddenSimpleTool(tc) {
8643
+ if (_SIMPLE_MODE_HIDDEN_TOOLS.has(tc.toolName)) return true;
8644
+ if (tc.toolName.startsWith("browser_")) return true;
8645
+ return false;
8646
+ }
8647
+ function visibleToolCalls(message) {
8648
+ return (message.toolCalls ?? []).filter((tc) => {
8649
+ if (_isHiddenSimpleTool(tc)) return false;
8650
+ const status = effectiveStatus(tc);
8651
+ if (status === "error" || status === "cancelled") return false;
8652
+ return true;
8653
+ });
8654
+ }
8655
+ function isIterationLive(message) {
8656
+ const calls = message.toolCalls ?? [];
8657
+ if (calls.length > 0) {
8658
+ return calls.some((tc) => tc.status === "running");
8659
+ }
8660
+ return message.status === "streaming";
8661
+ }
8662
+ function liveIterationLabel(message) {
8663
+ const running = (message.toolCalls ?? []).filter(
8664
+ (tc) => tc.status === "running" && !_isHiddenSimpleTool(tc)
8665
+ );
8666
+ if (running.length === 0) return "Thinking\u2026";
8667
+ if (running.length === 1) {
8668
+ const tc = running[0];
8669
+ const name = cancelledToolLabel(tc.toolName);
8670
+ const detail = extractToolDetail(tc);
8671
+ return detail ? `Running ${name} \xB7 ${detail}\u2026` : `Running ${name}\u2026`;
8672
+ }
8673
+ const firstName = running[0].toolName;
8674
+ const allSame = running.every((tc) => tc.toolName === firstName);
8675
+ if (allSame) {
8676
+ return `Running ${cancelledToolLabel(firstName)} \xD7 ${running.length}\u2026`;
8677
+ }
8678
+ return `Running ${running.length} tools\u2026`;
8679
+ }
8680
+ var _TOOL_ROW_ICON = {
8681
+ read_file: lucideReact.FileTextIcon,
8682
+ write_file: lucideReact.FileTextIcon,
8683
+ patch: lucideReact.FileEditIcon,
8684
+ list_files: lucideReact.ListIcon,
8685
+ search_files: lucideReact.SearchIcon,
8686
+ terminal: lucideReact.TerminalIcon,
8687
+ execute_code: lucideReact.TerminalIcon,
8688
+ web_search: lucideReact.GlobeIcon,
8689
+ web_extract: lucideReact.GlobeIcon,
8690
+ web_crawl: lucideReact.GlobeIcon,
8691
+ session_search: lucideReact.SearchIcon,
8692
+ skill_view: lucideReact.BookOpenIcon,
8693
+ skills_list: lucideReact.BookOpenIcon,
8694
+ skill_manage: lucideReact.PenLineIcon,
8695
+ consult_expert: lucideReact.SparklesIcon,
8696
+ delegate_task: lucideReact.ArrowRight,
8697
+ create_artifact: lucideReact.FileTextIcon,
8698
+ memory: lucideReact.PenLineIcon,
8699
+ todo: lucideReact.ListIcon
8700
+ };
8701
+ function _toolRowIcon(toolName) {
8702
+ return _TOOL_ROW_ICON[toolName] ?? lucideReact.WrenchIcon;
8703
+ }
8704
+ function _toolRowLabel(tc) {
8705
+ const detail = extractToolDetail(tc);
8706
+ switch (tc.toolName) {
8707
+ case "read_file":
8708
+ return detail ? `Read ${detail}` : "Read a file";
8709
+ case "write_file":
8710
+ return detail ? `Wrote ${detail}` : "Wrote a file";
8711
+ case "patch":
8712
+ return detail ? `Edited ${detail}` : "Edited a file";
8713
+ case "list_files":
8714
+ return detail ? `Listed ${detail}` : "Listed files";
8715
+ case "search_files":
8716
+ return detail ? `Searched files for "${detail}"` : "Searched files";
8717
+ case "terminal":
8718
+ return "Ran a command";
8719
+ case "execute_code":
8720
+ return "Executed code";
8721
+ case "web_search":
8722
+ return detail ? `Searched the web for "${detail}"` : "Searched the web";
8723
+ case "web_crawl":
8724
+ return detail ? `Crawled "${detail}"` : "Crawled the web";
8725
+ case "web_extract":
8726
+ return detail ? `Fetched ${detail}` : "Fetched a page";
8727
+ case "session_search":
8728
+ return detail ? `Searched session for "${detail}"` : "Searched session";
8729
+ case "skill_view":
8730
+ return detail ? `Read the ${detail} skill` : "Read a skill";
8731
+ case "skills_list":
8732
+ return "Listed available skills";
8733
+ case "skill_manage":
8734
+ return detail ? `Updated skill ${detail}` : "Managed skills";
8735
+ case "consult_expert":
8736
+ return detail ? `Consulted ${detail} expert` : "Consulted an expert";
8737
+ case "delegate_task":
8738
+ return detail ? `Delegated: ${detail}` : "Delegated a task";
8739
+ case "create_artifact":
8740
+ return detail ? `Created artifact "${detail}"` : "Created an artifact";
8741
+ case "memory":
8742
+ return detail ? `Memory ${detail}` : "Updated memory";
8743
+ case "todo":
8744
+ return detail ? `Todo ${detail}` : "Updated todo list";
8745
+ default:
8746
+ return cancelledToolLabel(tc.toolName);
8747
+ }
8748
+ }
8749
+ function SimpleDetailRow({ icon: Icon, children }) {
8750
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3 text-sm", children: [
8751
+ /* @__PURE__ */ jsxRuntime.jsx(
8752
+ Icon,
8753
+ {
8754
+ className: "mt-0.5 size-4 shrink-0 text-foreground/60",
8755
+ "aria-hidden": true
8756
+ }
8757
+ ),
8758
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1 leading-relaxed text-foreground/60", children })
8759
+ ] });
8760
+ }
8761
+ function ClampedReasoning({
8762
+ text,
8763
+ previewParagraphs = 2
8764
+ }) {
8765
+ const paragraphs = splitReasoningParagraphs(text);
8766
+ const [expanded, setExpanded] = react.useState(false);
8767
+ const hasMore = paragraphs.length > previewParagraphs;
8768
+ const visible = expanded || !hasMore ? paragraphs : paragraphs.slice(0, previewParagraphs);
8769
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
8770
+ visible.map((paragraph, i) => /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap", children: paragraph }, i)),
8771
+ hasMore && /* @__PURE__ */ jsxRuntime.jsx(
8772
+ "button",
8773
+ {
8774
+ type: "button",
8775
+ onClick: () => setExpanded((prev) => !prev),
8776
+ className: "text-xs font-medium text-foreground hover:text-foreground/70 cursor-pointer",
8777
+ children: expanded ? "Show less" : "Show more..."
8778
+ }
8779
+ )
8780
+ ] });
8781
+ }
8782
+ function splitReasoningParagraphs(text) {
8783
+ const trimmed = text.trim();
8784
+ if (!trimmed) return [];
8785
+ const blankSplit = trimmed.split(/\n\s*\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
8786
+ if (blankSplit.length > 1) return blankSplit;
8787
+ return trimmed.split(/\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
8788
+ }
8789
+ function IterationExpanded({
8790
+ message
8791
+ }) {
8792
+ const calls = visibleToolCalls(message);
8793
+ const reasoning = (message.reasoning ?? "").trim();
8794
+ const anyVisibleRunning = calls.some((tc) => tc.status === "running");
8795
+ const showDone = !anyVisibleRunning && (calls.length > 0 || !!reasoning);
8796
+ if (!reasoning && calls.length === 0) return null;
8797
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-2 space-y-3 border-l border-border/40 pl-4 py-1", children: [
8798
+ reasoning && /* @__PURE__ */ jsxRuntime.jsx(SimpleDetailRow, { icon: lucideReact.ClockIcon, children: /* @__PURE__ */ jsxRuntime.jsx(ClampedReasoning, { text: reasoning }) }),
8799
+ calls.map((tc) => {
8800
+ if (tc.toolName === "todo") {
8801
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pr-1", children: /* @__PURE__ */ jsxRuntime.jsx(TodoToolBlock, { tc }) }, tc.id);
8802
+ }
8803
+ return /* @__PURE__ */ jsxRuntime.jsx(SimpleDetailRow, { icon: _toolRowIcon(tc.toolName), children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: _toolRowLabel(tc) }) }, tc.id);
8804
+ }),
8805
+ showDone && /* @__PURE__ */ jsxRuntime.jsx(SimpleDetailRow, { icon: lucideReact.CircleCheckIcon, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground", children: "Done" }) })
8806
+ ] });
8807
+ }
8808
+ function IterationGroup({
8809
+ message,
8810
+ sessionId,
8811
+ artifactFallbacks,
8812
+ onFileSelect
8813
+ }) {
8814
+ const summary = message.iterationSummary?.summary;
8815
+ const [open, setOpen] = react.useState(false);
8816
+ if (isIterationLive(message)) {
8817
+ const askCall = (message.toolCalls ?? []).find(
8818
+ (tc) => tc.toolName === "ask_user_question" && tc.status === "running"
8819
+ );
8820
+ if (askCall) {
8821
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 py-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(AskUserQuestionToolBlock, { tc: askCall }) });
8822
+ }
8823
+ const label2 = liveIterationLabel(message);
8824
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 py-0.5 text-sm", children: /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: label2 }) });
8825
+ }
8826
+ const label = summary ?? deriveIterationLabel(message);
8827
+ if (!label) {
8828
+ return null;
8829
+ }
8830
+ const labelTone = summary ? "text-foreground/80" : "text-foreground/50";
8831
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
8832
+ /* @__PURE__ */ jsxRuntime.jsxs(
8833
+ "button",
8834
+ {
8835
+ type: "button",
8836
+ onClick: () => setOpen((prev) => !prev),
8837
+ "aria-expanded": open,
8838
+ className: "flex w-full items-center gap-2 rounded px-1 py-0.5 text-left text-sm hover:bg-muted/40",
8839
+ children: [
8840
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-1 truncate", labelTone), children: label }),
8841
+ /* @__PURE__ */ jsxRuntime.jsx(
8842
+ lucideReact.ChevronDown,
8843
+ {
8844
+ className: cn(
8845
+ "size-3 shrink-0 text-foreground/60 transition-transform",
8846
+ open && "rotate-180"
8847
+ ),
8848
+ "aria-hidden": true
8849
+ }
8850
+ )
8851
+ ]
8852
+ }
8853
+ ),
8854
+ open && /* @__PURE__ */ jsxRuntime.jsx(
8855
+ IterationExpanded,
8856
+ {
8857
+ message,
8858
+ sessionId,
8859
+ artifactFallbacks,
8860
+ onFileSelect
8861
+ }
8862
+ )
8181
8863
  ] });
8182
8864
  }
8183
- function PdfPreview({
8184
- file,
8185
- fileName: fileName2
8865
+ function deriveFileArtifactsFromMessages(messages) {
8866
+ const seen = /* @__PURE__ */ new Set();
8867
+ const out = [];
8868
+ for (const msg of messages) {
8869
+ if (msg.role !== "assistant" || !msg.toolCalls) continue;
8870
+ for (const tc of msg.toolCalls) {
8871
+ const status = effectiveStatus(tc);
8872
+ if (status === "error" || status === "cancelled") continue;
8873
+ const args = parseArgs(tc.args);
8874
+ if (!args) continue;
8875
+ if (tc.toolName === "write_file" || tc.toolName === "patch") {
8876
+ const path = typeof args.path === "string" ? args.path : typeof args.file_path === "string" ? args.file_path : "";
8877
+ if (!path || seen.has(`file:${path}`)) continue;
8878
+ seen.add(`file:${path}`);
8879
+ out.push({
8880
+ kind: "file",
8881
+ label: path.split("/").pop() || path,
8882
+ ref: path
8883
+ });
8884
+ continue;
8885
+ }
8886
+ if (tc.toolName === "create_artifact") {
8887
+ const name = typeof args.name === "string" ? args.name : "";
8888
+ if (!name || seen.has(`artifact:${name}`)) continue;
8889
+ seen.add(`artifact:${name}`);
8890
+ out.push({
8891
+ kind: "artifact",
8892
+ label: name,
8893
+ ref: name
8894
+ });
8895
+ }
8896
+ }
8897
+ }
8898
+ return out;
8899
+ }
8900
+ function SimpleAssistantGroup({
8901
+ messages,
8902
+ lastGlobalIndex,
8903
+ totalMessages,
8904
+ isRunning,
8905
+ sessionId,
8906
+ artifactFallbacks,
8907
+ onFileSelect,
8908
+ onRetry
8909
+ }) {
8910
+ const assistantMessages = messages.filter((m) => m.role === "assistant");
8911
+ const tail = assistantMessages[assistantMessages.length - 1];
8912
+ const tailHasTools = !!(tail?.toolCalls && tail.toolCalls.length > 0);
8913
+ const tailIsTextOnly = !!tail && !tailHasTools;
8914
+ const finalText = tailIsTextOnly && tail.content ? tail.content : "";
8915
+ const isTailGroup = lastGlobalIndex === totalMessages - 1;
8916
+ const showThinkingShim = isTailGroup && isRunning && (!tail && messages.some((m) => m.role !== "assistant") || tailIsTextOnly && tail.status === "streaming" && !tail.content);
8917
+ const showErrorInfo = !!tail && tail.status === "error" && !!tail.errorInfo;
8918
+ const effectiveTurnSummary = (() => {
8919
+ const fromHarness = tail?.turnSummary;
8920
+ if (fromHarness && fromHarness.artifacts.length > 0) {
8921
+ return fromHarness;
8922
+ }
8923
+ const derived = deriveFileArtifactsFromMessages(messages);
8924
+ if (derived.length === 0) return fromHarness;
8925
+ return {
8926
+ turnId: fromHarness?.turnId ?? "",
8927
+ recap: fromHarness?.recap ?? "",
8928
+ artifacts: derived
8929
+ };
8930
+ })();
8931
+ return /* @__PURE__ */ jsxRuntime.jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsxRuntime.jsxs(MessageContent, { children: [
8932
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
8933
+ messages.map((message) => {
8934
+ if (message.role === "system") {
8935
+ return /* @__PURE__ */ jsxRuntime.jsx(
8936
+ OrphanSystemMarker,
8937
+ {
8938
+ message,
8939
+ sessionId,
8940
+ onRetry: void 0
8941
+ },
8942
+ message.id
8943
+ );
8944
+ }
8945
+ if (message === tail && tailIsTextOnly) return null;
8946
+ return /* @__PURE__ */ jsxRuntime.jsx(
8947
+ IterationGroup,
8948
+ {
8949
+ message,
8950
+ sessionId,
8951
+ artifactFallbacks,
8952
+ onFileSelect
8953
+ },
8954
+ message.id
8955
+ );
8956
+ }),
8957
+ showThinkingShim && /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: "Working on it..." })
8958
+ ] }),
8959
+ finalText && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3", children: [
8960
+ /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { children: finalText }),
8961
+ tail?.status === "complete" && /* @__PURE__ */ jsxRuntime.jsx(TurnFeedback, { msg: tail })
8962
+ ] }),
8963
+ effectiveTurnSummary && /* @__PURE__ */ jsxRuntime.jsx(
8964
+ TurnSummaryCard,
8965
+ {
8966
+ summary: effectiveTurnSummary,
8967
+ sessionId,
8968
+ messages,
8969
+ onFileSelect
8970
+ }
8971
+ ),
8972
+ showErrorInfo && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsx(ErrorMessage, { errorInfo: tail.errorInfo, onRetry }) })
8973
+ ] }) });
8974
+ }
8975
+ function AssistantGroup({
8976
+ messages,
8977
+ lastGlobalIndex,
8978
+ totalMessages,
8979
+ isRunning,
8980
+ sessionId,
8981
+ artifactFallbacks,
8982
+ onFileSelect,
8983
+ onRetry,
8984
+ viewMode = "simple"
8186
8985
  }) {
8187
- const containerRef = react.useRef(null);
8188
- const viewerRef = react.useRef(null);
8189
- const pdfViewerRef = react.useRef(null);
8190
- const eventBusRef = react.useRef(null);
8191
- const findControllerRef = react.useRef(null);
8192
- const [error, setError] = react.useState(null);
8193
- const [pageNumber, setPageNumber] = react.useState(1);
8194
- const [pagesCount, setPagesCount] = react.useState(0);
8195
- const [scale, setScale] = react.useState(1);
8196
- const [query, setQuery] = react.useState("");
8197
- react.useEffect(() => {
8198
- let cancelled = false;
8199
- let loadingTask = null;
8200
- let pdfDocument = null;
8201
- const renderPdf = async () => {
8202
- setError(null);
8203
- const container = containerRef.current;
8204
- const viewerElement = viewerRef.current;
8205
- if (!container || !viewerElement) return;
8206
- const pdfBytes = decodeBase64(file.content);
8207
- const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
8208
- pdfjs.GlobalWorkerOptions.workerSrc = PDF_WORKER_SRC;
8209
- globalThis.pdfjsLib = pdfjs;
8210
- const pdfViewerModule = await import('pdfjs-dist/web/pdf_viewer.mjs');
8211
- loadingTask = pdfjs.getDocument({ data: pdfBytes });
8212
- const pdf = await loadingTask.promise;
8213
- pdfDocument = pdf;
8214
- if (cancelled) return;
8215
- const eventBus = new pdfViewerModule.EventBus();
8216
- const linkService = new pdfViewerModule.PDFLinkService({ eventBus });
8217
- const findController = new pdfViewerModule.PDFFindController({
8218
- eventBus,
8219
- linkService
8220
- });
8221
- const pdfViewer = new pdfViewerModule.PDFViewer({
8222
- container,
8223
- viewer: viewerElement,
8224
- eventBus,
8225
- linkService,
8226
- findController,
8227
- removePageBorders: true
8228
- });
8229
- linkService.setViewer(pdfViewer);
8230
- linkService.setDocument(pdf);
8231
- findController.setDocument(pdf);
8232
- const onPagesInit = () => {
8233
- pdfViewer.currentScaleValue = "page-width";
8234
- setScale(pdfViewer.currentScale || 1);
8235
- setPagesCount(pdfViewer.pagesCount);
8236
- setPageNumber(pdfViewer.currentPageNumber);
8237
- };
8238
- const onPageChanging = (event) => {
8239
- if (typeof event.pageNumber === "number") {
8240
- setPageNumber(event.pageNumber);
8241
- }
8242
- };
8243
- const onScaleChanging = (event) => {
8244
- if (typeof event.scale === "number") {
8245
- setScale(event.scale);
8246
- }
8247
- };
8248
- eventBus.on("pagesinit", onPagesInit);
8249
- eventBus.on("pagechanging", onPageChanging);
8250
- eventBus.on("scalechanging", onScaleChanging);
8251
- pdfViewerRef.current = pdfViewer;
8252
- eventBusRef.current = eventBus;
8253
- findControllerRef.current = findController;
8254
- setPagesCount(pdf.numPages);
8255
- pdfViewer.setDocument(pdf);
8256
- };
8257
- void renderPdf().catch((nextError) => {
8258
- if (cancelled) return;
8259
- if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
8260
- return;
8986
+ if (viewMode === "simple") {
8987
+ return /* @__PURE__ */ jsxRuntime.jsx(
8988
+ SimpleAssistantGroup,
8989
+ {
8990
+ messages,
8991
+ lastGlobalIndex,
8992
+ totalMessages,
8993
+ isRunning,
8994
+ sessionId,
8995
+ artifactFallbacks,
8996
+ onFileSelect,
8997
+ onRetry
8261
8998
  }
8262
- setError(formatPdfPreviewError(nextError));
8263
- });
8264
- return () => {
8265
- cancelled = true;
8266
- loadingTask?.destroy();
8267
- pdfDocument?.destroy();
8268
- pdfViewerRef.current?.cleanup();
8269
- pdfViewerRef.current = null;
8270
- eventBusRef.current = null;
8271
- findControllerRef.current = null;
8272
- };
8273
- }, [file.content]);
8274
- const setViewerPage = (nextPage) => {
8275
- const viewer = pdfViewerRef.current;
8276
- if (!viewer) return;
8277
- const clampedPage = Math.min(
8278
- Math.max(1, nextPage),
8279
- Math.max(1, viewer.pagesCount)
8280
8999
  );
8281
- viewer.currentPageNumber = clampedPage;
8282
- setPageNumber(clampedPage);
8283
- };
8284
- const setViewerScale = (nextScale) => {
8285
- const viewer = pdfViewerRef.current;
8286
- if (!viewer) return;
8287
- const clampedScale = Math.min(3, Math.max(0.5, nextScale));
8288
- viewer.currentScale = clampedScale;
8289
- setScale(clampedScale);
8290
- };
8291
- const runFind = (findPrevious = false) => {
8292
- const eventBus = eventBusRef.current;
8293
- if (!eventBus || !query.trim()) return;
8294
- eventBus.dispatch("find", {
8295
- source: eventBus,
8296
- type: "again",
8297
- query,
8298
- phraseSearch: true,
8299
- caseSensitive: false,
8300
- entireWord: false,
8301
- highlightAll: true,
8302
- findPrevious,
8303
- matchDiacritics: true
8304
- });
8305
- };
8306
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-full flex-col", children: [
8307
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
8308
- /* @__PURE__ */ jsxRuntime.jsx(
8309
- Button,
8310
- {
8311
- type: "button",
8312
- variant: "ghost",
8313
- size: "icon-xs",
8314
- onClick: () => setViewerPage(pageNumber - 1),
8315
- disabled: pageNumber <= 1,
8316
- "aria-label": "Previous PDF page",
8317
- title: "Previous page",
8318
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeftIcon, { className: "size-3.5" })
8319
- }
8320
- ),
8321
- /* @__PURE__ */ jsxRuntime.jsx(
8322
- Input,
8323
- {
8324
- type: "number",
8325
- min: 1,
8326
- max: pagesCount || 1,
8327
- value: pageNumber,
8328
- onChange: (event) => setViewerPage(Number(event.target.value)),
8329
- "aria-label": "PDF page number",
8330
- className: "h-7 w-12 border-input px-1 text-center text-xs"
8331
- }
8332
- ),
8333
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
8334
- "/ ",
8335
- pagesCount || "-"
8336
- ] }),
8337
- /* @__PURE__ */ jsxRuntime.jsx(
8338
- Button,
8339
- {
8340
- type: "button",
8341
- variant: "ghost",
8342
- size: "icon-xs",
8343
- onClick: () => setViewerPage(pageNumber + 1),
8344
- disabled: pagesCount > 0 && pageNumber >= pagesCount,
8345
- "aria-label": "Next PDF page",
8346
- title: "Next page",
8347
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "size-3.5" })
9000
+ }
9001
+ let entries = [];
9002
+ for (let i = 0; i < messages.length; i++) {
9003
+ const isLast = i === messages.length - 1 && lastGlobalIndex === totalMessages - 1;
9004
+ entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
9005
+ }
9006
+ entries = groupBrowserActivityEntries(entries);
9007
+ entries = groupWebSearchEntries(entries);
9008
+ for (let i = entries.length - 1; i >= 0; i--) {
9009
+ const entry = entries[i];
9010
+ if (entry?.kind === "text") {
9011
+ entries[i] = { ...entry, isFinalTurnText: true };
9012
+ break;
9013
+ }
9014
+ }
9015
+ const isTailGroup = lastGlobalIndex === totalMessages - 1;
9016
+ const lastEntry = entries[entries.length - 1];
9017
+ const hasRunningTool = entries.some(
9018
+ (e) => e.kind === "tool" && e.tc.status === "running"
9019
+ );
9020
+ const tailMsg = messages[messages.length - 1];
9021
+ if (isTailGroup && isRunning && !hasRunningTool && lastEntry?.kind !== "thinking") {
9022
+ entries.push({ kind: "thinking", key: `${tailMsg.id}-tail-thinking` });
9023
+ }
9024
+ const tail = messages[messages.length - 1];
9025
+ const showErrorInfo = tail && tail.role === "assistant" && tail.status === "error" && !!tail.errorInfo;
9026
+ return /* @__PURE__ */ jsxRuntime.jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsxRuntime.jsxs(MessageContent, { children: [
9027
+ /* @__PURE__ */ jsxRuntime.jsx(Timeline, { defaultValue: 999, className: "gap-0", children: entries.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsx(
9028
+ TimelineEntryItem,
9029
+ {
9030
+ entry,
9031
+ step: i + 1,
9032
+ sessionId,
9033
+ onFileSelect
9034
+ },
9035
+ entry.key
9036
+ )) }),
9037
+ showErrorInfo && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsx(ErrorMessage, { errorInfo: tail.errorInfo, onRetry }) })
9038
+ ] }) });
9039
+ }
9040
+ function RetryBanner({ indicator }) {
9041
+ const [open, setOpen] = react.useState(false);
9042
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9043
+ "div",
9044
+ {
9045
+ role: "status",
9046
+ className: "flex flex-col gap-1 border-l-2 border-amber-500 bg-amber-500/5 px-3 py-2 text-xs",
9047
+ children: [
9048
+ /* @__PURE__ */ jsxRuntime.jsxs(
9049
+ "button",
9050
+ {
9051
+ type: "button",
9052
+ onClick: () => setOpen((prev) => !prev),
9053
+ className: "flex items-center gap-2 text-left text-amber-600 hover:text-amber-700",
9054
+ "aria-expanded": open,
9055
+ children: [
9056
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "size-3 shrink-0" }),
9057
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate font-medium", children: indicator.title }),
9058
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-foreground/60", children: [
9059
+ "attempt ",
9060
+ indicator.attempt
9061
+ ] }),
9062
+ indicator.detail && (open ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "size-3" }))
9063
+ ]
9064
+ }
9065
+ ),
9066
+ open && indicator.detail && /* @__PURE__ */ jsxRuntime.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 })
9067
+ ]
9068
+ }
9069
+ );
9070
+ }
9071
+ function ChatThread({
9072
+ sessionId,
9073
+ messages,
9074
+ isRunning,
9075
+ isLoadingHistory = false,
9076
+ onSend,
9077
+ onStop,
9078
+ onFileSelect,
9079
+ disabled = false,
9080
+ disabledReason,
9081
+ tokenUsage,
9082
+ retryIndicator,
9083
+ onRetry,
9084
+ onComposerError,
9085
+ showBrowser = false,
9086
+ onToggleBrowser,
9087
+ showWorkspace = false,
9088
+ onToggleWorkspace,
9089
+ canShowBrowser = false,
9090
+ canShowWorkspace = false,
9091
+ viewMode = "simple",
9092
+ onViewModeChange
9093
+ }) {
9094
+ const groups = react.useMemo(() => groupMessages(messages), [messages]);
9095
+ const artifactFallbacks = react.useMemo(() => {
9096
+ const fallbacks = {};
9097
+ const createArtifactToolCallIds = [];
9098
+ let artifactIdx = 0;
9099
+ for (const msg of messages) {
9100
+ if (msg.role === "assistant" && msg.toolCalls) {
9101
+ for (const tc of msg.toolCalls) {
9102
+ if (tc.toolName === "create_artifact" && !tc.cancelled && tc.status !== "error") {
9103
+ createArtifactToolCallIds.push(tc.id);
9104
+ }
8348
9105
  }
8349
- ),
8350
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
8351
- /* @__PURE__ */ jsxRuntime.jsx(
8352
- Button,
8353
- {
8354
- type: "button",
8355
- variant: "ghost",
8356
- size: "icon-xs",
8357
- onClick: () => setViewerScale(scale - 0.1),
8358
- disabled: scale <= 0.5,
8359
- "aria-label": "Zoom out PDF",
8360
- title: "Zoom out",
8361
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MinusIcon, { className: "size-3.5" })
9106
+ } else if (msg.role === "system" && msg.systemKind === "artifact") {
9107
+ const tcId = createArtifactToolCallIds[artifactIdx];
9108
+ if (tcId) {
9109
+ const { name } = unpackArtifactMeta(msg.systemMeta, msg.content);
9110
+ if (name) fallbacks[tcId] = name;
8362
9111
  }
8363
- ),
8364
- /* @__PURE__ */ jsxRuntime.jsxs(
8365
- "button",
9112
+ artifactIdx += 1;
9113
+ }
9114
+ }
9115
+ return fallbacks;
9116
+ }, [messages]);
9117
+ const activeFailureId = react.useMemo(() => {
9118
+ if (messages.length === 0) return null;
9119
+ const tail = messages[messages.length - 1];
9120
+ if (tail.role === "system" && tail.systemKind === "error" && tail.errorInfo) {
9121
+ return tail.id;
9122
+ }
9123
+ if (tail.role === "assistant" && tail.status === "error" && tail.errorInfo) {
9124
+ return tail.id;
9125
+ }
9126
+ return null;
9127
+ }, [messages]);
9128
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col overflow-hidden bg-background text-sm", children: [
9129
+ /* @__PURE__ */ jsxRuntime.jsxs(Conversation, { className: "relative flex-1 min-h-0", children: [
9130
+ /* @__PURE__ */ jsxRuntime.jsx(ConversationContent, { className: "mx-auto w-full max-w-4xl", children: messages.length === 0 && isLoadingHistory ? /* @__PURE__ */ jsxRuntime.jsx(
9131
+ ConversationEmptyState,
8366
9132
  {
8367
- type: "button",
8368
- className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
8369
- onClick: () => {
8370
- const viewer = pdfViewerRef.current;
8371
- if (!viewer) return;
8372
- viewer.currentScaleValue = "page-width";
8373
- setScale(viewer.currentScale || 1);
8374
- },
8375
- "aria-label": "Fit PDF to width",
8376
- title: "Fit width",
8377
- children: [
8378
- Math.round(scale * 100),
8379
- "%"
8380
- ]
9133
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquareIcon, { className: "size-8 opacity-40" }),
9134
+ title: "Loading conversation",
9135
+ description: "Fetching the session history."
8381
9136
  }
8382
- ),
8383
- /* @__PURE__ */ jsxRuntime.jsx(
8384
- Button,
9137
+ ) : messages.length === 0 && !disabled ? /* @__PURE__ */ jsxRuntime.jsx(
9138
+ ConversationEmptyState,
8385
9139
  {
8386
- type: "button",
8387
- variant: "ghost",
8388
- size: "icon-xs",
8389
- onClick: () => setViewerScale(scale + 0.1),
8390
- disabled: scale >= 3,
8391
- "aria-label": "Zoom in PDF",
8392
- title: "Zoom in",
8393
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlusIcon, { className: "size-3.5" })
9140
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquareIcon, { className: "size-8 opacity-40" }),
9141
+ title: "Start a conversation",
9142
+ description: "Ask me anything \u2014 I can search, analyze, write code, and more."
8394
9143
  }
8395
- ),
8396
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
8397
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SearchIcon, { className: "size-3.5 text-muted-foreground" }),
8398
- /* @__PURE__ */ jsxRuntime.jsx(
8399
- Input,
9144
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9145
+ groups.map((group) => {
9146
+ if (group.role === "user") {
9147
+ const msg = group.messages[0];
9148
+ return /* @__PURE__ */ jsxRuntime.jsx(
9149
+ ChatMessage,
9150
+ {
9151
+ message: msg,
9152
+ onFileSelect
9153
+ },
9154
+ msg.id
9155
+ );
9156
+ }
9157
+ if (group.role === "system") {
9158
+ const msg = group.messages[0];
9159
+ const groupRetry2 = msg.id === activeFailureId ? onRetry : void 0;
9160
+ return /* @__PURE__ */ jsxRuntime.jsx(
9161
+ OrphanSystemMarker,
9162
+ {
9163
+ message: msg,
9164
+ sessionId,
9165
+ onRetry: groupRetry2
9166
+ },
9167
+ msg.id
9168
+ );
9169
+ }
9170
+ const groupTail = group.messages[group.messages.length - 1];
9171
+ const groupRetry = groupTail.id === activeFailureId ? onRetry : void 0;
9172
+ return /* @__PURE__ */ jsxRuntime.jsx(
9173
+ AssistantGroup,
9174
+ {
9175
+ messages: group.messages,
9176
+ lastGlobalIndex: group.lastGlobalIndex,
9177
+ totalMessages: messages.length,
9178
+ isRunning,
9179
+ sessionId,
9180
+ artifactFallbacks,
9181
+ onFileSelect,
9182
+ onRetry: groupRetry,
9183
+ viewMode
9184
+ },
9185
+ group.messages[0].id
9186
+ );
9187
+ }),
9188
+ isRunning && messages.length > 0 && messages[messages.length - 1].role === "user" && /* @__PURE__ */ jsxRuntime.jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsxRuntime.jsx(MessageContent, { children: /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: "Working on it..." }) }) })
9189
+ ] }) }),
9190
+ /* @__PURE__ */ jsxRuntime.jsx(ConversationScrollButton, {})
9191
+ ] }),
9192
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
9193
+ retryIndicator && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(RetryBanner, { indicator: retryIndicator }) }),
9194
+ disabled && disabledReason ? /* @__PURE__ */ jsxRuntime.jsx(
9195
+ "div",
8400
9196
  {
8401
- type: "search",
8402
- value: query,
8403
- onChange: (event) => setQuery(event.target.value),
8404
- onKeyDown: (event) => {
8405
- if (event.key === "Enter") runFind(event.shiftKey);
8406
- },
8407
- placeholder: "Find",
8408
- "aria-label": "Find in PDF",
8409
- className: "h-7 w-28 border-input px-1 text-xs"
9197
+ className: "rounded border border-line bg-muted/40 px-3 py-2 text-sm text-foreground/60",
9198
+ role: "status",
9199
+ children: disabledReason
8410
9200
  }
8411
- ),
8412
- /* @__PURE__ */ jsxRuntime.jsx(
8413
- Button,
9201
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
9202
+ ChatComposer,
8414
9203
  {
8415
- type: "button",
8416
- variant: "ghost",
8417
- size: "xs",
8418
- onClick: () => runFind(false),
8419
- disabled: !query.trim(),
8420
- children: "Find"
9204
+ onSend,
9205
+ onStop,
9206
+ isRunning,
9207
+ disabled,
9208
+ disabledReason,
9209
+ tokenUsage,
9210
+ onComposerError,
9211
+ showBrowser,
9212
+ onToggleBrowser,
9213
+ showWorkspace,
9214
+ onToggleWorkspace,
9215
+ canShowBrowser,
9216
+ canShowWorkspace,
9217
+ viewMode,
9218
+ onViewModeChange
8421
9219
  }
8422
9220
  )
8423
- ] }),
8424
- error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
8425
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
8426
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
8427
- ] }),
8428
- !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-[70vh] min-h-[420px] flex-1 bg-muted/20", children: /* @__PURE__ */ jsxRuntime.jsx(
9221
+ ] })
9222
+ ] });
9223
+ }
9224
+
9225
+ // src/components/ai-elements/file-tree.tsx
9226
+ init_utils();
9227
+ var noop = () => {
9228
+ };
9229
+ var FileTreeContext = react.createContext({
9230
+ expandedPaths: /* @__PURE__ */ new Set(),
9231
+ togglePath: noop
9232
+ });
9233
+ function FileTree({
9234
+ expanded: controlledExpanded,
9235
+ defaultExpanded = /* @__PURE__ */ new Set(),
9236
+ selectedPath,
9237
+ onSelect,
9238
+ onExpandedChange,
9239
+ className,
9240
+ children,
9241
+ ...props
9242
+ }) {
9243
+ const [internalExpanded, setInternalExpanded] = react.useState(defaultExpanded);
9244
+ const expandedPaths = controlledExpanded ?? internalExpanded;
9245
+ const togglePath = react.useCallback(
9246
+ (path) => {
9247
+ const next = new Set(expandedPaths);
9248
+ if (next.has(path)) {
9249
+ next.delete(path);
9250
+ } else {
9251
+ next.add(path);
9252
+ }
9253
+ setInternalExpanded(next);
9254
+ onExpandedChange?.(next);
9255
+ },
9256
+ [expandedPaths, onExpandedChange]
9257
+ );
9258
+ const contextValue = react.useMemo(
9259
+ () => ({
9260
+ expandedPaths,
9261
+ onSelect,
9262
+ selectedPath: selectedPath ?? void 0,
9263
+ togglePath
9264
+ }),
9265
+ [expandedPaths, onSelect, selectedPath, togglePath]
9266
+ );
9267
+ return /* @__PURE__ */ jsxRuntime.jsx(FileTreeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
9268
+ "div",
9269
+ {
9270
+ className: cn("rounded-lg border bg-background text-sm", className),
9271
+ role: "tree",
9272
+ ...props,
9273
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2", children })
9274
+ }
9275
+ ) });
9276
+ }
9277
+ function FileTreeIcon({
9278
+ className,
9279
+ children,
9280
+ ...props
9281
+ }) {
9282
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("shrink-0", className), ...props, children });
9283
+ }
9284
+ function FileTreeName({
9285
+ className,
9286
+ children,
9287
+ ...props
9288
+ }) {
9289
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("truncate", className), ...props, children });
9290
+ }
9291
+ function FileTreeFolder({
9292
+ path,
9293
+ name,
9294
+ className,
9295
+ children,
9296
+ ...props
9297
+ }) {
9298
+ const { expandedPaths, togglePath, selectedPath, onSelect } = react.useContext(FileTreeContext);
9299
+ const isExpanded = expandedPaths.has(path);
9300
+ const isSelected = selectedPath === path;
9301
+ const handleOpenChange = react.useCallback(() => {
9302
+ togglePath(path);
9303
+ }, [togglePath, path]);
9304
+ const handleSelect = react.useCallback(() => {
9305
+ onSelect?.(path);
9306
+ }, [onSelect, path]);
9307
+ return /* @__PURE__ */ jsxRuntime.jsx(Collapsible, { onOpenChange: handleOpenChange, open: isExpanded, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("", className), role: "treeitem", tabIndex: 0, ...props, children: [
9308
+ /* @__PURE__ */ jsxRuntime.jsxs(
8429
9309
  "div",
8430
9310
  {
8431
- ref: containerRef,
8432
- "aria-label": `PDF viewer for ${fileName2}`,
8433
- className: "absolute inset-0 overflow-auto",
8434
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: viewerRef, className: "pdfViewer" })
9311
+ className: cn(
9312
+ "flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50",
9313
+ isSelected && "bg-muted"
9314
+ ),
9315
+ children: [
9316
+ /* @__PURE__ */ jsxRuntime.jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
9317
+ "button",
9318
+ {
9319
+ className: "flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0",
9320
+ type: "button",
9321
+ "aria-label": isExpanded ? `Collapse ${name}` : `Expand ${name}`,
9322
+ children: /* @__PURE__ */ jsxRuntime.jsx(
9323
+ lucideReact.ChevronRightIcon,
9324
+ {
9325
+ className: cn(
9326
+ "size-4 shrink-0 text-muted-foreground transition-transform",
9327
+ isExpanded && "rotate-90"
9328
+ )
9329
+ }
9330
+ )
9331
+ }
9332
+ ) }),
9333
+ /* @__PURE__ */ jsxRuntime.jsxs(
9334
+ "button",
9335
+ {
9336
+ className: "flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left",
9337
+ onClick: handleSelect,
9338
+ type: "button",
9339
+ children: [
9340
+ /* @__PURE__ */ jsxRuntime.jsx(FileTreeIcon, { children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpenIcon, { className: "size-4 text-blue-500" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderIcon, { className: "size-4 text-blue-500" }) }),
9341
+ /* @__PURE__ */ jsxRuntime.jsx(FileTreeName, { children: name })
9342
+ ]
9343
+ }
9344
+ )
9345
+ ]
8435
9346
  }
8436
- ) })
8437
- ] });
8438
- }
8439
- function decodeBase64(content) {
8440
- const binary = globalThis.atob(content);
8441
- const bytes = new Uint8Array(binary.length);
8442
- for (let index = 0; index < binary.length; index += 1) {
8443
- bytes[index] = binary.charCodeAt(index);
8444
- }
8445
- return bytes;
9347
+ ),
9348
+ /* @__PURE__ */ jsxRuntime.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-4 border-l border-muted-foreground/50 pl-2", children }) })
9349
+ ] }) });
8446
9350
  }
8447
- function formatPdfPreviewError(error) {
8448
- if (error instanceof Error && (error.name === "InvalidCharacterError" || error.message === "Invalid character")) {
8449
- return "PDF preview data is not valid base64.";
8450
- }
8451
- return error instanceof Error ? error.message : "Failed to render PDF preview.";
9351
+ function FileTreeFile({
9352
+ path,
9353
+ name,
9354
+ icon,
9355
+ className,
9356
+ children,
9357
+ ...props
9358
+ }) {
9359
+ const { selectedPath, onSelect } = react.useContext(FileTreeContext);
9360
+ const isSelected = selectedPath === path;
9361
+ const handleClick = react.useCallback(() => {
9362
+ onSelect?.(path);
9363
+ }, [onSelect, path]);
9364
+ const handleKeyDown = react.useCallback(
9365
+ (event) => {
9366
+ if (event.key === "Enter" || event.key === " ") {
9367
+ onSelect?.(path);
9368
+ }
9369
+ },
9370
+ [onSelect, path]
9371
+ );
9372
+ return /* @__PURE__ */ jsxRuntime.jsx(
9373
+ "div",
9374
+ {
9375
+ className: cn(
9376
+ "group flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50",
9377
+ isSelected && "bg-muted",
9378
+ className
9379
+ ),
9380
+ onClick: handleClick,
9381
+ onKeyDown: handleKeyDown,
9382
+ role: "treeitem",
9383
+ tabIndex: 0,
9384
+ ...props,
9385
+ children: children ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9386
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-4 shrink-0" }),
9387
+ /* @__PURE__ */ jsxRuntime.jsx(FileTreeIcon, { children: icon ?? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileIcon, { className: "size-4 text-muted-foreground" }) }),
9388
+ /* @__PURE__ */ jsxRuntime.jsx(FileTreeName, { children: name })
9389
+ ] })
9390
+ }
9391
+ );
8452
9392
  }
9393
+
9394
+ // src/components/workspace/workspace-panel.tsx
9395
+ init_utils();
8453
9396
  var SKELETON_WIDTHS2 = [75, 60, 90, 65, 80, 70, 85, 55];
8454
9397
  var DEFAULT_WIDTH = 400;
8455
9398
  var MIN_WIDTH = 300;
@@ -8877,7 +9820,7 @@ function WorkspacePanel({
8877
9820
  }
8878
9821
  ),
8879
9822
  /* @__PURE__ */ jsxRuntime.jsx(
8880
- ConfirmDialog,
9823
+ ConfirmDialog2,
8881
9824
  {
8882
9825
  open: deleteTarget !== null,
8883
9826
  title: "Delete file?",
@@ -8891,7 +9834,7 @@ function WorkspacePanel({
8891
9834
  }
8892
9835
  );
8893
9836
  }
8894
- function ConfirmDialog({
9837
+ function ConfirmDialog2({
8895
9838
  open,
8896
9839
  title,
8897
9840
  description,
@@ -8970,7 +9913,9 @@ var AGENT_CHAT_LISTENED_EVENTS = [
8970
9913
  "skill.invoked",
8971
9914
  "policy.denied",
8972
9915
  "stream.timeout",
9916
+ "expert.delegation",
8973
9917
  "expert.result",
9918
+ "expert.failure",
8974
9919
  "expert.endorse",
8975
9920
  "expert.override",
8976
9921
  "user.feedback",
@@ -8980,7 +9925,9 @@ var AGENT_CHAT_LISTENED_EVENTS = [
8980
9925
  "browser.destroyed",
8981
9926
  "browser.control_granted",
8982
9927
  "browser.control_returned",
8983
- "clarify.response"
9928
+ "ask_user_question.response",
9929
+ "iteration.summary",
9930
+ "turn.summary"
8984
9931
  ];
8985
9932
 
8986
9933
  // src/runtime/reducer.ts
@@ -9005,7 +9952,8 @@ function createInitialAgentChatState(options = {}) {
9005
9952
  hadDeltas: false,
9006
9953
  terminal: false,
9007
9954
  workspaceRefreshKey: 0,
9008
- browser: null
9955
+ browser: null,
9956
+ viewMode: options.viewMode ?? "simple"
9009
9957
  };
9010
9958
  }
9011
9959
  function applyAgentChatEvent(state, event) {
@@ -9018,11 +9966,10 @@ function applyAgentChatEvent(state, event) {
9018
9966
  nextState = applyRetryIndicator(nextState, event);
9019
9967
  switch (event.type) {
9020
9968
  case "user.message":
9021
- return withMessages(nextState, applyUserMessage(
9022
- nextState.messages,
9023
- event.eventId,
9024
- event.data
9025
- ));
9969
+ return withMessages(
9970
+ { ...nextState, terminal: false, isRunning: true },
9971
+ applyUserMessage(nextState.messages, event.eventId, event.data)
9972
+ );
9026
9973
  case "skill.invoked":
9027
9974
  return withMessages(nextState, [
9028
9975
  ...nextState.messages,
@@ -9097,7 +10044,7 @@ function applyAgentChatEvent(state, event) {
9097
10044
  }
9098
10045
  case "harness.wake":
9099
10046
  case "llm.request":
9100
- return nextState.terminal ? nextState : { ...nextState, isRunning: true };
10047
+ return { ...nextState, terminal: false, isRunning: true };
9101
10048
  case "harness.crash":
9102
10049
  case "stream.timeout":
9103
10050
  return nextState;
@@ -9121,10 +10068,17 @@ function applyAgentChatEvent(state, event) {
9121
10068
  };
9122
10069
  }
9123
10070
  return nextState;
10071
+ case "expert.delegation":
10072
+ return applyExpertDelegation(nextState, event);
9124
10073
  case "expert.result":
9125
10074
  return withMessages(
9126
10075
  nextState,
9127
- applyExpertResult(nextState.messages, event.eventId)
10076
+ applyExpertResult(nextState.messages, event.eventId, event.data)
10077
+ );
10078
+ case "expert.failure":
10079
+ return withMessages(
10080
+ nextState,
10081
+ applyExpertFailure(nextState.messages, event.eventId, event.data)
9128
10082
  );
9129
10083
  case "expert.endorse":
9130
10084
  case "expert.override":
@@ -9137,15 +10091,19 @@ function applyAgentChatEvent(state, event) {
9137
10091
  nextState,
9138
10092
  applyUserFeedback(nextState.messages, event.data)
9139
10093
  );
9140
- case "clarify.response":
10094
+ case "ask_user_question.response":
9141
10095
  return withMessages(
9142
10096
  nextState,
9143
- applyClarifyResponse(nextState.messages, event.data)
10097
+ applyAskUserQuestionResponse(nextState.messages, event.data)
9144
10098
  );
9145
10099
  case "policy.denied":
9146
10100
  return applyPolicyDenied(nextState, event.data);
9147
10101
  case "session.start":
9148
10102
  return nextState;
10103
+ case "iteration.summary":
10104
+ return applyIterationSummary(nextState, event);
10105
+ case "turn.summary":
10106
+ return applyTurnSummary(nextState, event);
9149
10107
  }
9150
10108
  }
9151
10109
  function applyRetryIndicator(state, event) {
@@ -9278,6 +10236,7 @@ function browserMarker(event) {
9278
10236
  function applyLlmDelta(state, event) {
9279
10237
  const deltaContent = stringValue2(event.data.content);
9280
10238
  const deltaReasoning = stringValue2(event.data.reasoning);
10239
+ const { turnId, iterationIndex } = readTurnMeta(event.data);
9281
10240
  const messages = [...state.messages];
9282
10241
  const lastIdx = findLastAssistantIndex(messages);
9283
10242
  const lastMsg = lastIdx >= 0 ? messages[lastIdx] : null;
@@ -9292,7 +10251,9 @@ function applyLlmDelta(state, event) {
9292
10251
  messages[lastIdx] = {
9293
10252
  ...lastMsg,
9294
10253
  content: deltaContent ? lastMsg.content + deltaContent : lastMsg.content,
9295
- reasoning: deltaReasoning ? (lastMsg.reasoning ?? "") + deltaReasoning : lastMsg.reasoning
10254
+ reasoning: deltaReasoning ? (lastMsg.reasoning ?? "") + deltaReasoning : lastMsg.reasoning,
10255
+ turnId: turnId ?? lastMsg.turnId,
10256
+ iterationIndex: iterationIndex ?? lastMsg.iterationIndex
9296
10257
  };
9297
10258
  } else {
9298
10259
  messages.push({
@@ -9301,14 +10262,17 @@ function applyLlmDelta(state, event) {
9301
10262
  content: deltaContent,
9302
10263
  reasoning: deltaReasoning || void 0,
9303
10264
  createdAt: /* @__PURE__ */ new Date(),
9304
- status: "streaming"
10265
+ status: "streaming",
10266
+ turnId,
10267
+ iterationIndex
9305
10268
  });
9306
10269
  }
9307
10270
  return {
9308
10271
  ...state,
9309
10272
  messages,
9310
10273
  hadDeltas: state.hadDeltas || Boolean(deltaContent),
9311
- isRunning: state.terminal ? state.isRunning : true
10274
+ terminal: false,
10275
+ isRunning: true
9312
10276
  };
9313
10277
  }
9314
10278
  function applyLlmResponse(state, event) {
@@ -9318,6 +10282,7 @@ function applyLlmResponse(state, event) {
9318
10282
  const toolCalls = Array.isArray(message?.tool_calls) ? message?.tool_calls : [];
9319
10283
  const hasToolCalls = toolCalls.length > 0;
9320
10284
  const responseToolCallIds = toolCallIdsFromResponse(toolCalls);
10285
+ const { turnId, iterationIndex } = readTurnMeta(event.data);
9321
10286
  const idx = findLastAssistantIndex(messages);
9322
10287
  const prevAssistant = idx >= 0 ? messages[idx] : void 0;
9323
10288
  const prevHasTools = Boolean(prevAssistant?.toolCalls?.length);
@@ -9335,13 +10300,17 @@ function applyLlmResponse(state, event) {
9335
10300
  reasoning: (current.reasoning ?? "") + current.content,
9336
10301
  content: "",
9337
10302
  status: "streaming",
9338
- llmResponseEventId: event.eventId
10303
+ llmResponseEventId: event.eventId,
10304
+ turnId: turnId ?? current.turnId,
10305
+ iterationIndex: iterationIndex ?? current.iterationIndex
9339
10306
  };
9340
10307
  } else {
9341
10308
  messages[idx] = {
9342
10309
  ...current,
9343
10310
  status: "complete",
9344
- llmResponseEventId: event.eventId
10311
+ llmResponseEventId: event.eventId,
10312
+ turnId: turnId ?? current.turnId,
10313
+ iterationIndex: iterationIndex ?? current.iterationIndex
9345
10314
  };
9346
10315
  }
9347
10316
  } else if (matchesExistingToolTurn && idx >= 0) {
@@ -9350,7 +10319,9 @@ function applyLlmResponse(state, event) {
9350
10319
  ...current,
9351
10320
  reasoning: responseContent ? appendText(current.reasoning, responseContent) : current.reasoning,
9352
10321
  status: "streaming",
9353
- llmResponseEventId: event.eventId
10322
+ llmResponseEventId: event.eventId,
10323
+ turnId: turnId ?? current.turnId,
10324
+ iterationIndex: iterationIndex ?? current.iterationIndex
9354
10325
  };
9355
10326
  } else if (prevHasTools || !prevAssistant || hasUserAfter) {
9356
10327
  messages.push({
@@ -9360,7 +10331,9 @@ function applyLlmResponse(state, event) {
9360
10331
  reasoning: hasToolCalls && responseContent ? responseContent : void 0,
9361
10332
  createdAt: /* @__PURE__ */ new Date(),
9362
10333
  status: hasToolCalls ? "streaming" : "complete",
9363
- llmResponseEventId: event.eventId
10334
+ llmResponseEventId: event.eventId,
10335
+ turnId,
10336
+ iterationIndex
9364
10337
  });
9365
10338
  } else {
9366
10339
  const current = messages[idx];
@@ -9369,14 +10342,18 @@ function applyLlmResponse(state, event) {
9369
10342
  ...current,
9370
10343
  reasoning: (current.reasoning ?? "") + responseContent,
9371
10344
  status: "streaming",
9372
- llmResponseEventId: event.eventId
10345
+ llmResponseEventId: event.eventId,
10346
+ turnId: turnId ?? current.turnId,
10347
+ iterationIndex: iterationIndex ?? current.iterationIndex
9373
10348
  };
9374
10349
  } else {
9375
10350
  messages[idx] = {
9376
10351
  ...current,
9377
10352
  content: responseContent || current.content,
9378
10353
  status: hasToolCalls ? "streaming" : "complete",
9379
- llmResponseEventId: event.eventId
10354
+ llmResponseEventId: event.eventId,
10355
+ turnId: turnId ?? current.turnId,
10356
+ iterationIndex: iterationIndex ?? current.iterationIndex
9380
10357
  };
9381
10358
  }
9382
10359
  }
@@ -9409,6 +10386,7 @@ ${addition}`;
9409
10386
  }
9410
10387
  function applyLlmThinking(state, event) {
9411
10388
  const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
10389
+ const { turnId, iterationIndex } = readTurnMeta(event.data);
9412
10390
  const messages = [...state.messages];
9413
10391
  const idx = findLastAssistantIndex(messages);
9414
10392
  const prev = idx >= 0 ? messages[idx] : null;
@@ -9423,19 +10401,85 @@ function applyLlmThinking(state, event) {
9423
10401
  content: "",
9424
10402
  reasoning: reasoningText,
9425
10403
  createdAt: /* @__PURE__ */ new Date(),
9426
- status: "streaming"
10404
+ status: "streaming",
10405
+ turnId,
10406
+ iterationIndex
9427
10407
  });
9428
10408
  } else {
9429
10409
  messages[idx] = {
9430
10410
  ...prev,
9431
- reasoning: (prev.reasoning ?? "") + reasoningText
10411
+ reasoning: (prev.reasoning ?? "") + reasoningText,
10412
+ turnId: turnId ?? prev.turnId,
10413
+ iterationIndex: iterationIndex ?? prev.iterationIndex
9432
10414
  };
9433
10415
  }
9434
10416
  return {
9435
10417
  ...state,
9436
10418
  messages,
9437
- isRunning: state.terminal ? state.isRunning : true
10419
+ terminal: false,
10420
+ isRunning: true
10421
+ };
10422
+ }
10423
+ function applyIterationSummary(state, event) {
10424
+ const turnId = optionalStringValue(event.data.turn_id);
10425
+ const iterationIndex = optionalNumberValue(event.data.iteration_index);
10426
+ const summary = optionalStringValue(event.data.summary);
10427
+ if (!turnId || iterationIndex === null || !summary) return state;
10428
+ const idx = state.messages.findIndex(
10429
+ (m) => m.role === "assistant" && m.turnId === turnId && m.iterationIndex === iterationIndex
10430
+ );
10431
+ if (idx < 0) return state;
10432
+ const toolCallIds = Array.isArray(event.data.tool_call_ids) ? event.data.tool_call_ids.map((x) => String(x)) : [];
10433
+ const messages = [...state.messages];
10434
+ messages[idx] = {
10435
+ ...messages[idx],
10436
+ iterationSummary: {
10437
+ iterationIndex,
10438
+ summary,
10439
+ toolCallIds,
10440
+ startedAt: stringValue2(event.data.started_at),
10441
+ endedAt: stringValue2(event.data.ended_at)
10442
+ }
10443
+ };
10444
+ return { ...state, messages };
10445
+ }
10446
+ function applyTurnSummary(state, event) {
10447
+ const turnId = optionalStringValue(event.data.turn_id);
10448
+ if (!turnId) return state;
10449
+ const recap = stringValue2(event.data.recap);
10450
+ const artifacts = parseTurnArtifacts(event.data.artifacts);
10451
+ let idx = -1;
10452
+ for (let i = state.messages.length - 1; i >= 0; i--) {
10453
+ const message = state.messages[i];
10454
+ if (message.role === "assistant" && message.turnId === turnId) {
10455
+ idx = i;
10456
+ break;
10457
+ }
10458
+ }
10459
+ if (idx < 0) return state;
10460
+ const messages = [...state.messages];
10461
+ messages[idx] = {
10462
+ ...messages[idx],
10463
+ turnSummary: { turnId, recap, artifacts }
9438
10464
  };
10465
+ return { ...state, messages };
10466
+ }
10467
+ function parseTurnArtifacts(raw) {
10468
+ if (!Array.isArray(raw)) return [];
10469
+ const out = [];
10470
+ for (const item of raw) {
10471
+ if (!item || typeof item !== "object") continue;
10472
+ const obj = item;
10473
+ const kind = obj.kind;
10474
+ if (kind !== "file" && kind !== "artifact" && kind !== "url" && kind !== "command") {
10475
+ continue;
10476
+ }
10477
+ const label = typeof obj.label === "string" ? obj.label : "";
10478
+ const ref = typeof obj.ref === "string" ? obj.ref : "";
10479
+ if (!label || !ref) continue;
10480
+ out.push({ kind, label, ref });
10481
+ }
10482
+ return out;
9439
10483
  }
9440
10484
  function applyToolCall(state, event) {
9441
10485
  const messages = [...state.messages];
@@ -9472,7 +10516,8 @@ function applyToolCall(state, event) {
9472
10516
  return {
9473
10517
  ...state,
9474
10518
  messages,
9475
- isRunning: state.terminal ? state.isRunning : true
10519
+ terminal: false,
10520
+ isRunning: true
9476
10521
  };
9477
10522
  }
9478
10523
  function applyToolResult(messages, data) {
@@ -9550,16 +10595,84 @@ function buildErrorInfo(event) {
9550
10595
  retryable: Boolean(event.data.retryable)
9551
10596
  };
9552
10597
  }
9553
- function applyExpertResult(messages, eventId) {
10598
+ function applyExpertResult(messages, eventId, data) {
9554
10599
  const match = findLatestConsultExpertCall(messages);
9555
10600
  if (!match) return messages;
10601
+ const content = stringValue2(data.content);
9556
10602
  const next = [...messages];
9557
10603
  const msg = next[match.msgIdx];
9558
10604
  next[match.msgIdx] = {
9559
10605
  ...msg,
9560
- toolCalls: msg.toolCalls?.map(
9561
- (tc) => tc.id === match.toolId ? { ...tc, expertResultEventId: eventId } : tc
9562
- )
10606
+ toolCalls: msg.toolCalls?.map((tc) => {
10607
+ if (tc.id !== match.toolId) return tc;
10608
+ const updated = {
10609
+ ...tc,
10610
+ expertResultEventId: eventId,
10611
+ status: "complete"
10612
+ };
10613
+ if (tc.result === void 0 && content) {
10614
+ updated.result = JSON.stringify({ content });
10615
+ }
10616
+ return updated;
10617
+ })
10618
+ };
10619
+ return next;
10620
+ }
10621
+ function applyExpertDelegation(state, event) {
10622
+ if (findLatestConsultExpertCall(state.messages) !== null) {
10623
+ return state;
10624
+ }
10625
+ const expertName = stringValue2(event.data.expert);
10626
+ const task = stringValue2(event.data.task);
10627
+ if (!expertName) return state;
10628
+ const messages = [...state.messages];
10629
+ const assistantIdx = findLastAssistantIndex(messages);
10630
+ let assistant = assistantIdx >= 0 ? messages[assistantIdx] : null;
10631
+ const userAfterAssistant = assistantIdx >= 0 && hasUserAfterIndex(messages, assistantIdx);
10632
+ if (!assistant || assistant.status === "complete" || userAfterAssistant) {
10633
+ assistant = {
10634
+ id: `evt-${event.eventId}-expert-delegation`,
10635
+ role: "assistant",
10636
+ content: "",
10637
+ createdAt: /* @__PURE__ */ new Date(),
10638
+ status: "streaming"
10639
+ };
10640
+ messages.push(assistant);
10641
+ } else {
10642
+ assistant = { ...assistant };
10643
+ messages[assistantIdx] = assistant;
10644
+ }
10645
+ const toolCall = {
10646
+ id: `expert-delegation-${event.eventId}`,
10647
+ toolName: "consult_expert",
10648
+ args: JSON.stringify({ expert: expertName, question: task }),
10649
+ status: "running"
10650
+ };
10651
+ assistant.toolCalls = [...assistant.toolCalls ?? [], toolCall];
10652
+ return {
10653
+ ...state,
10654
+ messages,
10655
+ terminal: false,
10656
+ isRunning: true
10657
+ };
10658
+ }
10659
+ function applyExpertFailure(messages, eventId, data) {
10660
+ const match = findLatestConsultExpertCall(messages);
10661
+ if (!match) return messages;
10662
+ const errorMsg = stringValue2(data.error) || "Expert consultation failed.";
10663
+ const next = [...messages];
10664
+ const msg = next[match.msgIdx];
10665
+ next[match.msgIdx] = {
10666
+ ...msg,
10667
+ toolCalls: msg.toolCalls?.map((tc) => {
10668
+ if (tc.id !== match.toolId) return tc;
10669
+ return {
10670
+ ...tc,
10671
+ expertResultEventId: eventId,
10672
+ status: "error",
10673
+ result: tc.result ?? JSON.stringify({ error: errorMsg })
10674
+ };
10675
+ })
9563
10676
  };
9564
10677
  return next;
9565
10678
  }
@@ -9609,7 +10722,7 @@ function applyUserFeedback(messages, data) {
9609
10722
  }
9610
10723
  return messages;
9611
10724
  }
9612
- function applyClarifyResponse(messages, data) {
10725
+ function applyAskUserQuestionResponse(messages, data) {
9613
10726
  const targetToolId = stringValue2(data.tool_call_id);
9614
10727
  const responses = Array.isArray(data.responses) ? data.responses : void 0;
9615
10728
  if (!targetToolId || !responses) return messages;
@@ -9622,7 +10735,7 @@ function applyClarifyResponse(messages, data) {
9622
10735
  toolCalls: msg.toolCalls.map(
9623
10736
  (tc) => tc.id === targetToolId ? {
9624
10737
  ...tc,
9625
- clarifyAnswers: responses.map((response) => {
10738
+ askUserQuestionAnswers: responses.map((response) => {
9626
10739
  const row = objectValue(response) ?? {};
9627
10740
  return {
9628
10741
  question: stringValue2(row.question),
@@ -9704,8 +10817,39 @@ function stringValue2(value) {
9704
10817
  function numberValue(value) {
9705
10818
  return typeof value === "number" ? value : 0;
9706
10819
  }
10820
+ function optionalNumberValue(value) {
10821
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
10822
+ }
10823
+ function optionalStringValue(value) {
10824
+ return typeof value === "string" && value.length > 0 ? value : void 0;
10825
+ }
10826
+ function readTurnMeta(data) {
10827
+ const turnId = optionalStringValue(data.turn_id);
10828
+ const iterationIndex = optionalNumberValue(data.iteration_index);
10829
+ return {
10830
+ turnId,
10831
+ iterationIndex: iterationIndex === null ? void 0 : iterationIndex
10832
+ };
10833
+ }
9707
10834
 
9708
10835
  // src/runtime/use-agent-chat-runtime.ts
10836
+ var VIEW_MODE_KEY = "@invergent/agent-chat-react:viewMode";
10837
+ function readPersistedViewMode() {
10838
+ if (typeof window === "undefined") return null;
10839
+ try {
10840
+ const raw = window.localStorage.getItem(VIEW_MODE_KEY);
10841
+ if (raw === "simple" || raw === "expert") return raw;
10842
+ } catch {
10843
+ }
10844
+ return null;
10845
+ }
10846
+ function writePersistedViewMode(mode) {
10847
+ if (typeof window === "undefined") return;
10848
+ try {
10849
+ window.localStorage.setItem(VIEW_MODE_KEY, mode);
10850
+ } catch {
10851
+ }
10852
+ }
9709
10853
  function useAgentChatRuntime({
9710
10854
  adapter,
9711
10855
  agentId,
@@ -9713,7 +10857,13 @@ function useAgentChatRuntime({
9713
10857
  onSessionChange
9714
10858
  }) {
9715
10859
  const [state, setState] = react.useState(
9716
- () => createInitialAgentChatState({ isLoadingHistory: Boolean(sessionId) })
10860
+ () => createInitialAgentChatState({
10861
+ isLoadingHistory: Boolean(sessionId),
10862
+ // Seed from localStorage so the first paint matches the user's
10863
+ // last choice without flashing the default. The async adapter
10864
+ // load can still upgrade this later.
10865
+ viewMode: readPersistedViewMode() ?? "simple"
10866
+ })
9717
10867
  );
9718
10868
  const [session, setSession] = react.useState(null);
9719
10869
  const stateRef = react.useRef(state);
@@ -10009,6 +11159,41 @@ function useAgentChatRuntime({
10009
11159
  throw error;
10010
11160
  }
10011
11161
  }, [adapter, sessionId]);
11162
+ react.useEffect(() => {
11163
+ if (!adapter.getChatViewMode) {
11164
+ const cached = readPersistedViewMode();
11165
+ if (cached !== null) {
11166
+ setState(
11167
+ (prev) => prev.viewMode === cached ? prev : { ...prev, viewMode: cached }
11168
+ );
11169
+ }
11170
+ return;
11171
+ }
11172
+ let cancelled = false;
11173
+ void adapter.getChatViewMode().then((persisted) => {
11174
+ if (cancelled) return;
11175
+ if (persisted === "simple" || persisted === "expert") {
11176
+ setState(
11177
+ (prev) => prev.viewMode === persisted ? prev : { ...prev, viewMode: persisted }
11178
+ );
11179
+ }
11180
+ }).catch(() => void 0);
11181
+ return () => {
11182
+ cancelled = true;
11183
+ };
11184
+ }, [adapter]);
11185
+ const setViewMode = react.useCallback(
11186
+ (mode) => {
11187
+ setState(
11188
+ (prev) => prev.viewMode === mode ? prev : { ...prev, viewMode: mode }
11189
+ );
11190
+ writePersistedViewMode(mode);
11191
+ if (adapter.setChatViewMode) {
11192
+ void adapter.setChatViewMode(mode).catch(() => void 0);
11193
+ }
11194
+ },
11195
+ [adapter]
11196
+ );
10012
11197
  return {
10013
11198
  state,
10014
11199
  session,
@@ -10022,7 +11207,9 @@ function useAgentChatRuntime({
10022
11207
  stop,
10023
11208
  retry,
10024
11209
  markSending,
10025
- markSendError
11210
+ markSendError,
11211
+ viewMode: state.viewMode,
11212
+ setViewMode
10026
11213
  };
10027
11214
  }
10028
11215
  function parseEventData(data) {
@@ -10093,6 +11280,9 @@ function AgentChat({
10093
11280
  sessionId,
10094
11281
  onSessionChange
10095
11282
  });
11283
+ react.useEffect(() => {
11284
+ setShowWorkspace(runtime.viewMode === "expert");
11285
+ }, [runtime.viewMode]);
10096
11286
  const isReadOnlySession = isScheduledRunSession(runtime.session);
10097
11287
  const effectiveDisabled = disabled || isReadOnlySession;
10098
11288
  const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
@@ -10213,7 +11403,9 @@ function AgentChat({
10213
11403
  showWorkspace,
10214
11404
  onToggleWorkspace: handleToggleWorkspace,
10215
11405
  canShowBrowser: browserAvailable,
10216
- canShowWorkspace: workspaceAvailable
11406
+ canShowWorkspace: workspaceAvailable,
11407
+ viewMode: runtime.viewMode,
11408
+ onViewModeChange: runtime.setViewMode
10217
11409
  }
10218
11410
  )
10219
11411
  }
@@ -10249,7 +11441,8 @@ function AgentChat({
10249
11441
  {
10250
11442
  sessionId,
10251
11443
  state: browserState,
10252
- adapter
11444
+ adapter,
11445
+ onClose: () => setShowBrowser(false)
10253
11446
  }
10254
11447
  )
10255
11448
  }
@@ -10434,7 +11627,7 @@ function InputRequiredDetail({
10434
11627
  answer: answers[question.prompt]?.trim() ?? "",
10435
11628
  is_other: false
10436
11629
  }));
10437
- await adapter.submitClarifyResponse({
11630
+ await adapter.submitAskUserQuestionResponse({
10438
11631
  sessionId: item.sessionId,
10439
11632
  toolCallId,
10440
11633
  responses
@@ -11021,7 +12214,7 @@ function groupMissionTasksByBucket(tasks) {
11021
12214
  function deriveMissionWorkerActivityLabel(worker) {
11022
12215
  const kind = worker.latestEventKind;
11023
12216
  if (kind === "tool.call" && worker.latestEventSummary) {
11024
- return `tool: ${truncate2(worker.latestEventSummary, 80)}`;
12217
+ return `tool: ${truncate3(worker.latestEventSummary, 80)}`;
11025
12218
  }
11026
12219
  if (kind === "llm.response") {
11027
12220
  return "thinking";
@@ -11030,11 +12223,11 @@ function deriveMissionWorkerActivityLabel(worker) {
11030
12223
  return "received tool result";
11031
12224
  }
11032
12225
  if (kind && worker.latestEventSummary) {
11033
- return `${kind}: ${truncate2(worker.latestEventSummary, 60)}`;
12226
+ return `${kind}: ${truncate3(worker.latestEventSummary, 60)}`;
11034
12227
  }
11035
12228
  return worker.sessionStatus || "active";
11036
12229
  }
11037
- function truncate2(s, max) {
12230
+ function truncate3(s, max) {
11038
12231
  if (s.length <= max) return s;
11039
12232
  return `${s.slice(0, max - 1)}\u2026`;
11040
12233
  }
@@ -12405,7 +13598,8 @@ function pruneDeletedSessionNodes(nodes, deletedSessionId) {
12405
13598
  return nodes.filter((node) => !deletedIds.has(node.id));
12406
13599
  }
12407
13600
  function formatSessionTime(value) {
12408
- const date = new Date(value);
13601
+ const normalized = /[zZ]|[+-]\d{2}:?\d{2}$/.test(value) ? value : `${value}Z`;
13602
+ const date = new Date(normalized);
12409
13603
  if (Number.isNaN(date.getTime())) return "";
12410
13604
  return dateFns.formatDistanceToNow(date, { addSuffix: true });
12411
13605
  }
@@ -12445,7 +13639,7 @@ function TreeNodeRow({
12445
13639
  const title = entry.title ?? fallbackSessionTitle(entry);
12446
13640
  const subtitle = [
12447
13641
  formatRunKind(entry.runKind),
12448
- entry.model ?? entry.agentType,
13642
+ entry.model,
12449
13643
  formatSessionTime(entry.updatedAt)
12450
13644
  ].filter(Boolean).join(" \xB7 ");
12451
13645
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -12461,7 +13655,7 @@ function TreeNodeRow({
12461
13655
  className: cn(
12462
13656
  "group flex items-center gap-2 w-full py-2 pr-2 text-left cursor-pointer transition-colors border-l-2",
12463
13657
  "min-h-11 md:min-h-0",
12464
- isActive ? "bg-line text-foreground border-l-primary" : "bg-transparent text-subtle hover:bg-input hover:text-foreground border-l-transparent"
13658
+ isActive ? "bg-line text-foreground border-l-primary" : "bg-transparent text-muted-foreground hover:bg-input hover:text-foreground border-l-transparent"
12465
13659
  ),
12466
13660
  style: { paddingLeft: `${depth * 12 + 12}px` },
12467
13661
  children: [