@invergent/agent-chat-react 1.5.2 → 1.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,9 @@
1
+ import { cn } from './chunk-QSC4UIVT.js';
1
2
  import { createContext, memo, useRef, useState, useEffect, useCallback, useMemo, lazy, useContext, Suspense, Children } from 'react';
2
3
  import { cva } from 'class-variance-authority';
3
4
  import { Collapsible as Collapsible$1, Slot, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Tooltip as Tooltip$1, Tabs as Tabs$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1, HoverCard as HoverCard$1, Progress as Progress$1 } from 'radix-ui';
4
- import { clsx } from 'clsx';
5
- import { twMerge } from 'tailwind-merge';
6
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, UsersIcon, MessageSquareIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
6
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, UsersIcon, MessageSquareIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
8
7
  import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
9
8
  import { cjk } from '@streamdown/cjk';
10
9
  import { code } from '@streamdown/code';
@@ -18,9 +17,9 @@ import Ansi from 'ansi-to-react';
18
17
  import { getUsage } from 'tokenlens';
19
18
  import { Command as Command$1 } from 'cmdk';
20
19
  import { nanoid } from 'nanoid';
20
+ import 'pdfjs-dist/web/pdf_viewer.css';
21
21
  import { formatDistanceToNow } from 'date-fns';
22
22
 
23
- // src/agent-chat.tsx
24
23
  var AgentChatAdapterContext = createContext(null);
25
24
  var AgentChatAdapterProvider = AgentChatAdapterContext.Provider;
26
25
  function useAgentChatAdapterContext() {
@@ -32,9 +31,6 @@ function useAgentChatAdapterContext() {
32
31
  }
33
32
  return value;
34
33
  }
35
- function cn(...inputs) {
36
- return twMerge(clsx(inputs));
37
- }
38
34
  var buttonVariants = cva(
39
35
  "group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding text-xs font-semibold tracking-widest whitespace-nowrap uppercase transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
40
36
  {
@@ -86,7 +82,7 @@ function Button({
86
82
  var Conversation = ({ className, ...props }) => /* @__PURE__ */ jsx(
87
83
  StickToBottom,
88
84
  {
89
- className: cn("relative flex-1 overflow-y-auto", className),
85
+ className: cn("relative flex-1", className),
90
86
  initial: "smooth",
91
87
  resize: "smooth",
92
88
  role: "log",
@@ -569,6 +565,61 @@ function TimelineSeparator({
569
565
  }
570
566
  );
571
567
  }
568
+
569
+ // src/components/chat/tools/shared.ts
570
+ function statusColorClass(status) {
571
+ if (status === "running") return "bg-primary animate-pulse";
572
+ if (status === "error") return "bg-red-500";
573
+ if (status === "cancelled") return "bg-muted-foreground/40";
574
+ return "bg-emerald-500";
575
+ }
576
+ function effectiveStatus(tc) {
577
+ if (tc.cancelled) return "cancelled";
578
+ if (tc.status !== "complete" || !tc.result) return tc.status;
579
+ try {
580
+ const parsed = JSON.parse(tc.result);
581
+ if (parsed?.exit_code !== void 0 && parsed.exit_code !== 0) return "error";
582
+ if (parsed?.error) return "error";
583
+ if (parsed?.status === "blocked" || parsed?.status === "error") return "error";
584
+ if (parsed?.success === false) return "error";
585
+ } catch {
586
+ }
587
+ return "complete";
588
+ }
589
+ function formatArgs(args) {
590
+ try {
591
+ return JSON.stringify(JSON.parse(args), null, 2);
592
+ } catch {
593
+ return args;
594
+ }
595
+ }
596
+ function parseArgs(args) {
597
+ try {
598
+ return JSON.parse(args);
599
+ } catch {
600
+ return null;
601
+ }
602
+ }
603
+ function truncate(s, max) {
604
+ return s.length > max ? s.slice(0, max) + "\n... (truncated)" : s;
605
+ }
606
+ function toolErrorSummary(result, max = 180) {
607
+ if (!result) return "";
608
+ try {
609
+ const parsed = JSON.parse(result);
610
+ const error = typeof parsed?.error === "string" ? parsed.error : "";
611
+ if (error === "sandbox_unavailable") {
612
+ return "Sandbox is unavailable. Workspace commands cannot run right now.";
613
+ }
614
+ const reason = typeof parsed?.reason === "string" ? parsed.reason : "";
615
+ const message = typeof parsed?.message === "string" ? parsed.message : "";
616
+ const detail = reason || message;
617
+ const summary = error && detail && detail !== error ? `${error}: ${detail}` : error || detail;
618
+ return summary ? summary.replace(/\s+/g, " ").slice(0, max) : "";
619
+ } catch {
620
+ return "";
621
+ }
622
+ }
572
623
  function CopyButton({ text }) {
573
624
  const [copied, setCopied] = useState(false);
574
625
  return /* @__PURE__ */ jsx(
@@ -604,7 +655,8 @@ function parseTerminalResult(result, args) {
604
655
  const hasOutput = typeof parsed?.output === "string";
605
656
  const hasStdout = typeof parsed?.stdout === "string";
606
657
  const hasExitCode = typeof parsed?.exit_code === "number";
607
- if (!hasOutput && !hasStdout && !hasExitCode) {
658
+ const errorSummary = toolErrorSummary(result, 400);
659
+ if (!hasOutput && !hasStdout && !hasExitCode && !errorSummary) {
608
660
  return null;
609
661
  }
610
662
  let command = "";
@@ -614,20 +666,20 @@ function parseTerminalResult(result, args) {
614
666
  } catch {
615
667
  }
616
668
  const output = parsed.output ?? parsed.stdout ?? "";
617
- const stderr = parsed.stderr ?? parsed.error ?? "";
669
+ const stderr = parsed.stderr ?? errorSummary;
618
670
  const combined = stderr ? `${output}
619
671
  ${stderr}`.trim() : output;
620
672
  return {
621
673
  output: combined,
622
- exit_code: parsed.exit_code ?? 0,
623
- error: parsed.error ?? null,
674
+ exit_code: parsed.exit_code ?? (errorSummary ? 1 : 0),
675
+ error: errorSummary || null,
624
676
  command
625
677
  };
626
678
  } catch {
627
679
  return null;
628
680
  }
629
681
  }
630
- function TerminalCollapsible({ command, output, isRunning }) {
682
+ function TerminalCollapsible({ command, output, isRunning, hasError }) {
631
683
  const [isOpen, setIsOpen] = useState(false);
632
684
  return /* @__PURE__ */ jsxs(
633
685
  Collapsible,
@@ -637,7 +689,7 @@ function TerminalCollapsible({ command, output, isRunning }) {
637
689
  className: "not-prose w-full",
638
690
  children: [
639
691
  /* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "group/trigger flex w-fit items-center gap-2 text-sm transition-colors", children: [
640
- /* @__PURE__ */ jsx("span", { className: "text-left", children: isRunning ? /* @__PURE__ */ jsx(Shimmer, { as: "span", duration: 1, children: "Running command..." }) : /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Command result" }) }),
692
+ /* @__PURE__ */ jsx("span", { className: "text-left", children: isRunning ? /* @__PURE__ */ jsx(Shimmer, { as: "span", duration: 1, children: "Running command..." }) : hasError ? /* @__PURE__ */ jsx("span", { className: "font-semibold text-destructive", children: "Command failed" }) : /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Command result" }) }),
641
693
  /* @__PURE__ */ jsx(
642
694
  ChevronDownIcon,
643
695
  {
@@ -694,7 +746,8 @@ function TerminalToolBlock({ tc }) {
694
746
  {
695
747
  command: result.command,
696
748
  output,
697
- isRunning
749
+ isRunning,
750
+ hasError: result.exit_code !== 0 || Boolean(result.error)
698
751
  }
699
752
  );
700
753
  }
@@ -2339,7 +2392,7 @@ var Terminal = ({
2339
2392
  }
2340
2393
  ) });
2341
2394
  };
2342
- function parseArgs(args) {
2395
+ function parseArgs2(args) {
2343
2396
  try {
2344
2397
  return JSON.parse(args);
2345
2398
  } catch {
@@ -2563,7 +2616,7 @@ function OutputPreview({ output }) {
2563
2616
  }
2564
2617
  function ProcessToolBlock({ tc }) {
2565
2618
  const isRunning = tc.status === "running";
2566
- const args = parseArgs(tc.args);
2619
+ const args = parseArgs2(tc.args);
2567
2620
  const result = parseResult(tc.result);
2568
2621
  const action = args.action || "unknown";
2569
2622
  const actionLabel = {
@@ -2646,44 +2699,6 @@ function Textarea({ className, ...props }) {
2646
2699
  }
2647
2700
  );
2648
2701
  }
2649
-
2650
- // src/components/chat/tools/shared.ts
2651
- function statusColorClass(status) {
2652
- if (status === "running") return "bg-primary animate-pulse";
2653
- if (status === "error") return "bg-red-500";
2654
- if (status === "cancelled") return "bg-muted-foreground/40";
2655
- return "bg-emerald-500";
2656
- }
2657
- function effectiveStatus(tc) {
2658
- if (tc.cancelled) return "cancelled";
2659
- if (tc.status !== "complete" || !tc.result) return tc.status;
2660
- try {
2661
- const parsed = JSON.parse(tc.result);
2662
- if (parsed?.exit_code !== void 0 && parsed.exit_code !== 0) return "error";
2663
- if (parsed?.error) return "error";
2664
- if (parsed?.status === "blocked" || parsed?.status === "error") return "error";
2665
- if (parsed?.success === false) return "error";
2666
- } catch {
2667
- }
2668
- return "complete";
2669
- }
2670
- function formatArgs(args) {
2671
- try {
2672
- return JSON.stringify(JSON.parse(args), null, 2);
2673
- } catch {
2674
- return args;
2675
- }
2676
- }
2677
- function parseArgs2(args) {
2678
- try {
2679
- return JSON.parse(args);
2680
- } catch {
2681
- return null;
2682
- }
2683
- }
2684
- function truncate(s, max) {
2685
- return s.length > max ? s.slice(0, max) + "\n... (truncated)" : s;
2686
- }
2687
2702
  var MAX_REASON_LENGTH = 500;
2688
2703
  function ExpertToolBlock({ tc }) {
2689
2704
  const [expanded, setExpanded] = useState(false);
@@ -2719,7 +2734,7 @@ function ExpertToolBlock({ tc }) {
2719
2734
  }
2720
2735
  void submit("up");
2721
2736
  };
2722
- const args = parseArgs2(tc.args) ?? {};
2737
+ const args = parseArgs(tc.args) ?? {};
2723
2738
  const expertName = args.expert ?? null;
2724
2739
  const question = args.question ?? args.prompt ?? "";
2725
2740
  const result = parseExpertResult(tc.result);
@@ -2831,7 +2846,7 @@ function ExpertToolBlock({ tc }) {
2831
2846
  }
2832
2847
  function parseExpertResult(result) {
2833
2848
  if (!result) return null;
2834
- const parsed = parseArgs2(result);
2849
+ const parsed = parseArgs(result);
2835
2850
  if (parsed) {
2836
2851
  return {
2837
2852
  ...parsed,
@@ -2938,11 +2953,11 @@ function ReasonForm({
2938
2953
  ] });
2939
2954
  }
2940
2955
  function SkillsListBlock({ tc }) {
2941
- const args = parseArgs2(tc.args) ?? {};
2956
+ const args = parseArgs(tc.args) ?? {};
2942
2957
  const filter = args.category ? `category: ${args.category}` : "all";
2943
2958
  let summary = "";
2944
2959
  if (tc.result) {
2945
- const parsed = parseArgs2(tc.result);
2960
+ const parsed = parseArgs(tc.result);
2946
2961
  if (parsed?.count !== void 0) {
2947
2962
  summary = `${parsed.count} skill${parsed.count === 1 ? "" : "s"}`;
2948
2963
  }
@@ -2957,11 +2972,11 @@ function SkillsListBlock({ tc }) {
2957
2972
  ] });
2958
2973
  }
2959
2974
  function SkillViewBlock({ tc }) {
2960
- const args = parseArgs2(tc.args) ?? {};
2975
+ const args = parseArgs(tc.args) ?? {};
2961
2976
  const target = args.file_path ? `${args.name ?? "?"}/${args.file_path}` : args.name ?? "?";
2962
2977
  let summary = "";
2963
2978
  if (tc.result) {
2964
- const parsed = parseArgs2(tc.result);
2979
+ const parsed = parseArgs(tc.result);
2965
2980
  if (parsed?.staged_at) {
2966
2981
  summary = `staged at ${parsed.staged_at}`;
2967
2982
  } else if (parsed?.token_estimate) {
@@ -3008,7 +3023,7 @@ function buildAnswer(q, sel) {
3008
3023
  }
3009
3024
  function ClarifyToolBlock({ tc }) {
3010
3025
  const { adapter, sessionId } = useAgentChatAdapterContext();
3011
- const args = useMemo(() => parseArgs2(tc.args), [tc.args]);
3026
+ const args = useMemo(() => parseArgs(tc.args), [tc.args]);
3012
3027
  const questions = args?.questions ?? [];
3013
3028
  const [active, setActive] = useState(0);
3014
3029
  const [selections, setSelections] = useState(
@@ -3290,7 +3305,7 @@ function ClarifyLocked({
3290
3305
  ] });
3291
3306
  }
3292
3307
  function ArtifactToolBlock({ tc }) {
3293
- const args = parseArgs2(tc.args) ?? {};
3308
+ const args = parseArgs(tc.args) ?? {};
3294
3309
  const status = effectiveStatus(tc);
3295
3310
  const label = status === "running" ? "Creating artifact\u2026" : status === "error" ? "Tried to create" : "Created";
3296
3311
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-sm ", children: [
@@ -3304,7 +3319,7 @@ function firstLine(s) {
3304
3319
  }
3305
3320
  function DelegateToolBlock({ tc }) {
3306
3321
  const [expanded, setExpanded] = useState(false);
3307
- const args = parseArgs2(tc.args);
3322
+ const args = parseArgs(tc.args);
3308
3323
  const goal = args?.goal ?? "";
3309
3324
  const context = args?.context ?? "";
3310
3325
  const agentType = args?.agent_type;
@@ -3414,8 +3429,8 @@ var TARGET_LABEL = {
3414
3429
  };
3415
3430
  function MemoryToolBlock({ tc }) {
3416
3431
  const [isOpen, setIsOpen] = useState(false);
3417
- const args = parseArgs2(tc.args) ?? {};
3418
- const result = tc.result ? parseArgs2(tc.result) : null;
3432
+ const args = parseArgs(tc.args) ?? {};
3433
+ const result = tc.result ? parseArgs(tc.result) : null;
3419
3434
  const action = args.action ?? "add";
3420
3435
  const target = args.target ?? "memory";
3421
3436
  const verb = ACTION_VERB[action] ?? action;
@@ -3493,8 +3508,8 @@ var ACTION_LABEL = {
3493
3508
  remove_file: "Remove skill file"
3494
3509
  };
3495
3510
  function SkillManageToolBlock({ tc }) {
3496
- const args = parseArgs2(tc.args) ?? {};
3497
- const result = tc.result ? parseArgs2(tc.result) : null;
3511
+ const args = parseArgs(tc.args) ?? {};
3512
+ const result = tc.result ? parseArgs(tc.result) : null;
3498
3513
  const action = args.action ?? "manage";
3499
3514
  const label = ACTION_LABEL[action] ?? "Manage skill";
3500
3515
  const target = args.file_path ? `${args.name ?? "?"}/${args.file_path}` : args.name ?? "?";
@@ -3515,8 +3530,8 @@ function firstLine2(value) {
3515
3530
  return (index === -1 ? value : value.slice(0, index)).trim();
3516
3531
  }
3517
3532
  function CoordinatorToolBlock({ tc }) {
3518
- const args = parseArgs2(tc.args) ?? {};
3519
- const result = tc.result ? parseArgs2(tc.result) : null;
3533
+ const args = parseArgs(tc.args) ?? {};
3534
+ const result = tc.result ? parseArgs(tc.result) : null;
3520
3535
  const failed = Boolean(result?.error);
3521
3536
  let label = "Worker";
3522
3537
  let target = "";
@@ -5467,7 +5482,7 @@ function exportArtifact(payload) {
5467
5482
  };
5468
5483
  case "chart":
5469
5484
  return {
5470
- text: JSON.stringify(payload.spec.vega_lite, null, 2),
5485
+ text: JSON.stringify(payload.spec.chart_js, null, 2),
5471
5486
  mime: "application/json",
5472
5487
  extension: "json"
5473
5488
  };
@@ -5528,7 +5543,7 @@ async function copyText(text) {
5528
5543
  }
5529
5544
  }
5530
5545
  var ArtifactChart = lazy(
5531
- () => import('./artifact-chart-7J6GOR4M.js').then((m) => ({ default: m.ArtifactChart }))
5546
+ () => import('./artifact-chart-X53FKRDZ.js').then((m) => ({ default: m.ArtifactChart }))
5532
5547
  );
5533
5548
  var KIND_LABEL = {
5534
5549
  markdown: "Markdown document",
@@ -5653,7 +5668,7 @@ function ArtifactBody({
5653
5668
  Suspense,
5654
5669
  {
5655
5670
  fallback: /* @__PURE__ */ jsx(Shimmer, { duration: 5, className: "text-sm text-muted-foreground", children: "Loading chart\u2026" }),
5656
- children: /* @__PURE__ */ jsx(ArtifactChart, { spec: payload.spec })
5671
+ children: /* @__PURE__ */ jsx(ArtifactChart, { spec: payload.spec, fill })
5657
5672
  }
5658
5673
  );
5659
5674
  case "html":
@@ -5873,7 +5888,7 @@ function OrphanSystemMarker({
5873
5888
  );
5874
5889
  }
5875
5890
  if (message.systemKind === "error" && message.errorInfo) {
5876
- return /* @__PURE__ */ jsx("div", { className: "mx-auto my-2 w-full max-w-4xl px-4", children: /* @__PURE__ */ jsx(ErrorMessage, { errorInfo: message.errorInfo, onRetry }) });
5891
+ return /* @__PURE__ */ jsx("div", { className: "mx-auto my-2 w-full max-w-4xl", children: /* @__PURE__ */ jsx(ErrorMessage, { errorInfo: message.errorInfo, onRetry }) });
5877
5892
  }
5878
5893
  return null;
5879
5894
  }
@@ -5927,6 +5942,7 @@ function TimelineEntryItem({
5927
5942
  ] });
5928
5943
  }
5929
5944
  if (entry.kind === "tool") {
5945
+ const failureSummary = effectiveStatus(entry.tc) === "error" ? toolErrorSummary(entry.tc.result) : "";
5930
5946
  return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
5931
5947
  /* @__PURE__ */ jsxs(TimelineHeader, { children: [
5932
5948
  /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
@@ -5937,7 +5953,10 @@ function TimelineEntryItem({
5937
5953
  }
5938
5954
  )
5939
5955
  ] }),
5940
- /* @__PURE__ */ jsx(TimelineContent, { children: entry.tc.cancelled ? /* @__PURE__ */ jsx(CancelledToolRow, { tc: entry.tc }) : /* @__PURE__ */ jsx(ToolCallBlock, { tc: entry.tc, onFileSelect }) })
5956
+ /* @__PURE__ */ jsx(TimelineContent, { children: entry.tc.cancelled ? /* @__PURE__ */ jsx(CancelledToolRow, { tc: entry.tc }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
5957
+ /* @__PURE__ */ jsx(ToolCallBlock, { tc: entry.tc, onFileSelect }),
5958
+ failureSummary ? /* @__PURE__ */ jsx("div", { className: "max-w-full truncate text-xs text-destructive", title: failureSummary, children: failureSummary }) : null
5959
+ ] }) })
5941
5960
  ] });
5942
5961
  }
5943
5962
  if (entry.kind === "text") {
@@ -6055,6 +6074,7 @@ function ChatThread({
6055
6074
  sessionId,
6056
6075
  messages,
6057
6076
  isRunning,
6077
+ isLoadingHistory = false,
6058
6078
  onSend,
6059
6079
  onStop,
6060
6080
  onFileSelect,
@@ -6077,7 +6097,14 @@ function ChatThread({
6077
6097
  }, [messages]);
6078
6098
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col overflow-hidden bg-background text-sm", children: [
6079
6099
  /* @__PURE__ */ jsxs(Conversation, { className: "relative flex-1 min-h-0", children: [
6080
- /* @__PURE__ */ jsx(ConversationContent, { className: "mx-auto w-full max-w-3xl", children: messages.length === 0 && !disabled ? /* @__PURE__ */ jsx(
6100
+ /* @__PURE__ */ jsx(ConversationContent, { className: "mx-auto w-full max-w-4xl", children: messages.length === 0 && isLoadingHistory ? /* @__PURE__ */ jsx(
6101
+ ConversationEmptyState,
6102
+ {
6103
+ icon: /* @__PURE__ */ jsx(MessageSquareIcon, { className: "size-8 opacity-40" }),
6104
+ title: "Loading conversation",
6105
+ description: "Fetching the session history."
6106
+ }
6107
+ ) : messages.length === 0 && !disabled ? /* @__PURE__ */ jsx(
6081
6108
  ConversationEmptyState,
6082
6109
  {
6083
6110
  icon: /* @__PURE__ */ jsx(MessageSquareIcon, { className: "size-8 opacity-40" }),
@@ -6130,7 +6157,7 @@ function ChatThread({
6130
6157
  ] }) }),
6131
6158
  /* @__PURE__ */ jsx(ConversationScrollButton, {})
6132
6159
  ] }),
6133
- /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-6 pb-5 pt-3", children: [
6160
+ /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-4xl px-6 pb-5 pt-3", children: [
6134
6161
  retryIndicator && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(RetryBanner, { indicator: retryIndicator }) }),
6135
6162
  /* @__PURE__ */ jsx(
6136
6163
  ChatComposer,
@@ -6360,17 +6387,24 @@ function getLanguageHint(path) {
6360
6387
  return map[ext] ?? "plaintext";
6361
6388
  }
6362
6389
  var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
6390
+ var PDF_WORKER_SRC = new URL(
6391
+ "pdfjs-dist/legacy/build/pdf.worker.mjs",
6392
+ import.meta.url
6393
+ ).toString();
6363
6394
  function FileViewer({
6364
6395
  file,
6365
6396
  loading,
6366
6397
  error,
6398
+ downloadUrl,
6399
+ onDelete,
6367
6400
  onClose
6368
6401
  }) {
6369
6402
  const visible = loading || file !== null || error !== null;
6370
6403
  if (!visible) return null;
6371
6404
  const fileName2 = file?.path.split("/").pop() ?? "File";
6372
6405
  const lang = file ? getLanguageHint(file.path) : "";
6373
- const isImage = file?.encoding === "base64";
6406
+ const isPdf = file?.mime_type === "application/pdf" || file?.path.toLowerCase().endsWith(".pdf") || false;
6407
+ const isImage = file?.encoding === "base64" && !isPdf;
6374
6408
  const HeaderIcon = isImage ? ImageIcon : FileTextIcon;
6375
6409
  return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col border-t border-line", children: [
6376
6410
  /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
@@ -6385,6 +6419,28 @@ function FileViewer({
6385
6419
  ] })
6386
6420
  ] })
6387
6421
  ] }),
6422
+ downloadUrl && /* @__PURE__ */ jsx(
6423
+ "a",
6424
+ {
6425
+ href: downloadUrl,
6426
+ download: fileName2,
6427
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
6428
+ "aria-label": `Download ${fileName2}`,
6429
+ title: "Download",
6430
+ children: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-3.5" })
6431
+ }
6432
+ ),
6433
+ onDelete && /* @__PURE__ */ jsx(
6434
+ "button",
6435
+ {
6436
+ type: "button",
6437
+ onClick: onDelete,
6438
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
6439
+ "aria-label": `Delete ${fileName2}`,
6440
+ title: "Delete",
6441
+ children: /* @__PURE__ */ jsx(TrashIcon, { className: "size-3.5" })
6442
+ }
6443
+ ),
6388
6444
  /* @__PURE__ */ jsx(
6389
6445
  "button",
6390
6446
  {
@@ -6411,6 +6467,8 @@ function FileViewer({
6411
6467
  /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
6412
6468
  /* @__PURE__ */ jsx("span", { children: error })
6413
6469
  ] }),
6470
+ file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
6471
+ file && isPdf && file.encoding === "base64" && /* @__PURE__ */ jsx(PdfPreview, { file, fileName: fileName2 }),
6414
6472
  file && isImage && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsx(
6415
6473
  "img",
6416
6474
  {
@@ -6419,66 +6477,290 @@ function FileViewer({
6419
6477
  className: "max-h-[60vh] max-w-full rounded object-contain"
6420
6478
  }
6421
6479
  ) }),
6422
- file && !isImage && /* @__PURE__ */ jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-[11px] leading-relaxed text-foreground", children: /* @__PURE__ */ jsx("code", { "data-language": lang, children: file.content }) })
6480
+ file && !isImage && !isPdf && /* @__PURE__ */ jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-[11px] leading-relaxed text-foreground", children: /* @__PURE__ */ jsx("code", { "data-language": lang, children: file.content }) })
6423
6481
  ] })
6424
6482
  ] });
6425
6483
  }
6426
- var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
6427
- ".py",
6428
- ".js",
6429
- ".ts",
6430
- ".tsx",
6431
- ".jsx",
6432
- ".json",
6433
- ".md",
6434
- ".yaml",
6435
- ".yml",
6436
- ".toml",
6437
- ".sql",
6438
- ".sh",
6439
- ".css",
6440
- ".html",
6441
- ".txt",
6442
- ".csv",
6443
- ".rst",
6444
- ".cfg",
6445
- ".ini",
6446
- ".env",
6447
- ".rs",
6448
- ".go",
6449
- ".java",
6450
- ".rb",
6451
- ".php",
6452
- ".c",
6453
- ".cpp",
6454
- ".h",
6455
- ".lock",
6456
- ".gitignore",
6457
- ".dockerignore",
6458
- ".editorconfig"
6459
- ]);
6460
- var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
6461
- ".png",
6462
- ".jpg",
6463
- ".jpeg",
6464
- ".gif",
6465
- ".webp",
6466
- ".svg",
6467
- ".bmp",
6468
- ".ico",
6469
- ".avif",
6470
- ".tiff",
6471
- ".tif"
6472
- ]);
6484
+ function FileViewerError({ message }) {
6485
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
6486
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
6487
+ /* @__PURE__ */ jsx("span", { children: message })
6488
+ ] });
6489
+ }
6490
+ function PdfPreview({
6491
+ file,
6492
+ fileName: fileName2
6493
+ }) {
6494
+ const containerRef = useRef(null);
6495
+ const viewerRef = useRef(null);
6496
+ const pdfViewerRef = useRef(null);
6497
+ const eventBusRef = useRef(null);
6498
+ const findControllerRef = useRef(null);
6499
+ const [error, setError] = useState(null);
6500
+ const [pageNumber, setPageNumber] = useState(1);
6501
+ const [pagesCount, setPagesCount] = useState(0);
6502
+ const [scale, setScale] = useState(1);
6503
+ const [query, setQuery] = useState("");
6504
+ useEffect(() => {
6505
+ let cancelled = false;
6506
+ let loadingTask = null;
6507
+ let pdfDocument = null;
6508
+ const renderPdf = async () => {
6509
+ setError(null);
6510
+ const container = containerRef.current;
6511
+ const viewerElement = viewerRef.current;
6512
+ if (!container || !viewerElement) return;
6513
+ const pdfBytes = decodeBase64(file.content);
6514
+ const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
6515
+ pdfjs.GlobalWorkerOptions.workerSrc = PDF_WORKER_SRC;
6516
+ globalThis.pdfjsLib = pdfjs;
6517
+ const pdfViewerModule = await import('pdfjs-dist/web/pdf_viewer.mjs');
6518
+ loadingTask = pdfjs.getDocument({ data: pdfBytes });
6519
+ const pdf = await loadingTask.promise;
6520
+ pdfDocument = pdf;
6521
+ if (cancelled) return;
6522
+ const eventBus = new pdfViewerModule.EventBus();
6523
+ const linkService = new pdfViewerModule.PDFLinkService({ eventBus });
6524
+ const findController = new pdfViewerModule.PDFFindController({
6525
+ eventBus,
6526
+ linkService
6527
+ });
6528
+ const pdfViewer = new pdfViewerModule.PDFViewer({
6529
+ container,
6530
+ viewer: viewerElement,
6531
+ eventBus,
6532
+ linkService,
6533
+ findController,
6534
+ removePageBorders: true
6535
+ });
6536
+ linkService.setViewer(pdfViewer);
6537
+ linkService.setDocument(pdf);
6538
+ findController.setDocument(pdf);
6539
+ const onPagesInit = () => {
6540
+ pdfViewer.currentScaleValue = "page-width";
6541
+ setScale(pdfViewer.currentScale || 1);
6542
+ setPagesCount(pdfViewer.pagesCount);
6543
+ setPageNumber(pdfViewer.currentPageNumber);
6544
+ };
6545
+ const onPageChanging = (event) => {
6546
+ if (typeof event.pageNumber === "number") {
6547
+ setPageNumber(event.pageNumber);
6548
+ }
6549
+ };
6550
+ const onScaleChanging = (event) => {
6551
+ if (typeof event.scale === "number") {
6552
+ setScale(event.scale);
6553
+ }
6554
+ };
6555
+ eventBus.on("pagesinit", onPagesInit);
6556
+ eventBus.on("pagechanging", onPageChanging);
6557
+ eventBus.on("scalechanging", onScaleChanging);
6558
+ pdfViewerRef.current = pdfViewer;
6559
+ eventBusRef.current = eventBus;
6560
+ findControllerRef.current = findController;
6561
+ setPagesCount(pdf.numPages);
6562
+ pdfViewer.setDocument(pdf);
6563
+ };
6564
+ void renderPdf().catch((nextError) => {
6565
+ if (cancelled) return;
6566
+ if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
6567
+ return;
6568
+ }
6569
+ setError(formatPdfPreviewError(nextError));
6570
+ });
6571
+ return () => {
6572
+ cancelled = true;
6573
+ loadingTask?.destroy();
6574
+ pdfDocument?.destroy();
6575
+ pdfViewerRef.current?.cleanup();
6576
+ pdfViewerRef.current = null;
6577
+ eventBusRef.current = null;
6578
+ findControllerRef.current = null;
6579
+ };
6580
+ }, [file.content]);
6581
+ const setViewerPage = (nextPage) => {
6582
+ const viewer = pdfViewerRef.current;
6583
+ if (!viewer) return;
6584
+ const clampedPage = Math.min(
6585
+ Math.max(1, nextPage),
6586
+ Math.max(1, viewer.pagesCount)
6587
+ );
6588
+ viewer.currentPageNumber = clampedPage;
6589
+ setPageNumber(clampedPage);
6590
+ };
6591
+ const setViewerScale = (nextScale) => {
6592
+ const viewer = pdfViewerRef.current;
6593
+ if (!viewer) return;
6594
+ const clampedScale = Math.min(3, Math.max(0.5, nextScale));
6595
+ viewer.currentScale = clampedScale;
6596
+ setScale(clampedScale);
6597
+ };
6598
+ const runFind = (findPrevious = false) => {
6599
+ const eventBus = eventBusRef.current;
6600
+ if (!eventBus || !query.trim()) return;
6601
+ eventBus.dispatch("find", {
6602
+ source: eventBus,
6603
+ type: "again",
6604
+ query,
6605
+ phraseSearch: true,
6606
+ caseSensitive: false,
6607
+ entireWord: false,
6608
+ highlightAll: true,
6609
+ findPrevious,
6610
+ matchDiacritics: true
6611
+ });
6612
+ };
6613
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col", children: [
6614
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
6615
+ /* @__PURE__ */ jsx(
6616
+ Button,
6617
+ {
6618
+ type: "button",
6619
+ variant: "ghost",
6620
+ size: "icon-xs",
6621
+ onClick: () => setViewerPage(pageNumber - 1),
6622
+ disabled: pageNumber <= 1,
6623
+ "aria-label": "Previous PDF page",
6624
+ title: "Previous page",
6625
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-3.5" })
6626
+ }
6627
+ ),
6628
+ /* @__PURE__ */ jsx(
6629
+ Input,
6630
+ {
6631
+ type: "number",
6632
+ min: 1,
6633
+ max: pagesCount || 1,
6634
+ value: pageNumber,
6635
+ onChange: (event) => setViewerPage(Number(event.target.value)),
6636
+ "aria-label": "PDF page number",
6637
+ className: "h-7 w-12 border-input px-1 text-center text-xs"
6638
+ }
6639
+ ),
6640
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
6641
+ "/ ",
6642
+ pagesCount || "-"
6643
+ ] }),
6644
+ /* @__PURE__ */ jsx(
6645
+ Button,
6646
+ {
6647
+ type: "button",
6648
+ variant: "ghost",
6649
+ size: "icon-xs",
6650
+ onClick: () => setViewerPage(pageNumber + 1),
6651
+ disabled: pagesCount > 0 && pageNumber >= pagesCount,
6652
+ "aria-label": "Next PDF page",
6653
+ title: "Next page",
6654
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5" })
6655
+ }
6656
+ ),
6657
+ /* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
6658
+ /* @__PURE__ */ jsx(
6659
+ Button,
6660
+ {
6661
+ type: "button",
6662
+ variant: "ghost",
6663
+ size: "icon-xs",
6664
+ onClick: () => setViewerScale(scale - 0.1),
6665
+ disabled: scale <= 0.5,
6666
+ "aria-label": "Zoom out PDF",
6667
+ title: "Zoom out",
6668
+ children: /* @__PURE__ */ jsx(MinusIcon, { className: "size-3.5" })
6669
+ }
6670
+ ),
6671
+ /* @__PURE__ */ jsxs(
6672
+ "button",
6673
+ {
6674
+ type: "button",
6675
+ className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
6676
+ onClick: () => {
6677
+ const viewer = pdfViewerRef.current;
6678
+ if (!viewer) return;
6679
+ viewer.currentScaleValue = "page-width";
6680
+ setScale(viewer.currentScale || 1);
6681
+ },
6682
+ "aria-label": "Fit PDF to width",
6683
+ title: "Fit width",
6684
+ children: [
6685
+ Math.round(scale * 100),
6686
+ "%"
6687
+ ]
6688
+ }
6689
+ ),
6690
+ /* @__PURE__ */ jsx(
6691
+ Button,
6692
+ {
6693
+ type: "button",
6694
+ variant: "ghost",
6695
+ size: "icon-xs",
6696
+ onClick: () => setViewerScale(scale + 0.1),
6697
+ disabled: scale >= 3,
6698
+ "aria-label": "Zoom in PDF",
6699
+ title: "Zoom in",
6700
+ children: /* @__PURE__ */ jsx(PlusIcon, { className: "size-3.5" })
6701
+ }
6702
+ ),
6703
+ /* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
6704
+ /* @__PURE__ */ jsx(SearchIcon, { className: "size-3.5 text-muted-foreground" }),
6705
+ /* @__PURE__ */ jsx(
6706
+ Input,
6707
+ {
6708
+ type: "search",
6709
+ value: query,
6710
+ onChange: (event) => setQuery(event.target.value),
6711
+ onKeyDown: (event) => {
6712
+ if (event.key === "Enter") runFind(event.shiftKey);
6713
+ },
6714
+ placeholder: "Find",
6715
+ "aria-label": "Find in PDF",
6716
+ className: "h-7 w-28 border-input px-1 text-xs"
6717
+ }
6718
+ ),
6719
+ /* @__PURE__ */ jsx(
6720
+ Button,
6721
+ {
6722
+ type: "button",
6723
+ variant: "ghost",
6724
+ size: "xs",
6725
+ onClick: () => runFind(false),
6726
+ disabled: !query.trim(),
6727
+ children: "Find"
6728
+ }
6729
+ )
6730
+ ] }),
6731
+ error && /* @__PURE__ */ jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
6732
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
6733
+ /* @__PURE__ */ jsx("span", { children: error })
6734
+ ] }),
6735
+ !error && /* @__PURE__ */ jsx("div", { className: "relative h-[70vh] min-h-[420px] flex-1 bg-muted/20", children: /* @__PURE__ */ jsx(
6736
+ "div",
6737
+ {
6738
+ ref: containerRef,
6739
+ "aria-label": `PDF viewer for ${fileName2}`,
6740
+ className: "absolute inset-0 overflow-auto",
6741
+ children: /* @__PURE__ */ jsx("div", { ref: viewerRef, className: "pdfViewer" })
6742
+ }
6743
+ ) })
6744
+ ] });
6745
+ }
6746
+ function decodeBase64(content) {
6747
+ const binary = globalThis.atob(content);
6748
+ const bytes = new Uint8Array(binary.length);
6749
+ for (let index = 0; index < binary.length; index += 1) {
6750
+ bytes[index] = binary.charCodeAt(index);
6751
+ }
6752
+ return bytes;
6753
+ }
6754
+ function formatPdfPreviewError(error) {
6755
+ if (error instanceof Error && (error.name === "InvalidCharacterError" || error.message === "Invalid character")) {
6756
+ return "PDF preview data is not valid base64.";
6757
+ }
6758
+ return error instanceof Error ? error.message : "Failed to render PDF preview.";
6759
+ }
6473
6760
  var SKELETON_WIDTHS2 = [75, 60, 90, 65, 80, 70, 85, 55];
6474
- var DEFAULT_WIDTH = 500;
6761
+ var DEFAULT_WIDTH = 400;
6475
6762
  var MIN_WIDTH = 300;
6476
6763
  var MAX_WIDTH = 900;
6477
- function isViewable(name) {
6478
- const dot = name.lastIndexOf(".");
6479
- const ext = dot >= 0 ? name.slice(dot).toLowerCase() : "";
6480
- return TEXT_EXTENSIONS.has(ext) || IMAGE_EXTENSIONS.has(ext);
6481
- }
6482
6764
  function collectExpandedPaths(entries, depth = 0) {
6483
6765
  const paths = [];
6484
6766
  for (const entry of entries) {
@@ -6503,8 +6785,8 @@ function findEntry(entries, path) {
6503
6785
  }
6504
6786
  function RenderEntries({
6505
6787
  entries,
6506
- onFileSelect,
6507
- onDelete
6788
+ onDelete,
6789
+ downloadUrlFor
6508
6790
  }) {
6509
6791
  return /* @__PURE__ */ jsx(Fragment, { children: entries.map((entry) => {
6510
6792
  if (entry.kind === "dir") {
@@ -6512,27 +6794,26 @@ function RenderEntries({
6512
6794
  RenderEntries,
6513
6795
  {
6514
6796
  entries: entry.children,
6515
- onFileSelect,
6516
- onDelete
6797
+ onDelete,
6798
+ downloadUrlFor
6517
6799
  }
6518
6800
  ) }, entry.path);
6519
6801
  }
6520
- const viewable = isViewable(entry.name);
6802
+ const fileName2 = entry.name;
6521
6803
  return /* @__PURE__ */ jsxs(FileTreeFile, { name: entry.name, path: entry.path, children: [
6522
6804
  /* @__PURE__ */ jsx("span", { className: "size-4 shrink-0" }),
6523
6805
  /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: entry.name }),
6524
6806
  entry.size != null && /* @__PURE__ */ jsx("span", { className: "ml-1 shrink-0 text-xs text-muted-foreground/60", children: formatFileSize(entry.size) }),
6525
6807
  /* @__PURE__ */ jsxs("div", { className: "ml-1 flex shrink-0 items-center gap-0 opacity-0 transition-opacity group-hover:opacity-100", children: [
6526
- viewable && /* @__PURE__ */ jsx(
6527
- "button",
6808
+ /* @__PURE__ */ jsx(
6809
+ "a",
6528
6810
  {
6529
- type: "button",
6811
+ href: downloadUrlFor(entry.path),
6812
+ download: fileName2,
6530
6813
  className: "rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
6531
- onClick: (event) => {
6532
- event.stopPropagation();
6533
- onFileSelect(entry.path);
6534
- },
6535
- title: "View",
6814
+ onClick: (event) => event.stopPropagation(),
6815
+ title: "Download",
6816
+ "aria-label": `Download ${fileName2}`,
6536
6817
  children: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-3" })
6537
6818
  }
6538
6819
  ),
@@ -6546,6 +6827,7 @@ function RenderEntries({
6546
6827
  onDelete(entry.path);
6547
6828
  },
6548
6829
  title: "Delete",
6830
+ "aria-label": `Delete ${fileName2}`,
6549
6831
  children: /* @__PURE__ */ jsx(TrashIcon, { className: "size-3" })
6550
6832
  }
6551
6833
  )
@@ -6558,6 +6840,8 @@ function WorkspacePanel({
6558
6840
  sessionId,
6559
6841
  selectedPath,
6560
6842
  onSelectedPathChange,
6843
+ collapsed = false,
6844
+ onCollapsedChange,
6561
6845
  disabled = false
6562
6846
  }) {
6563
6847
  const fileInputRef = useRef(null);
@@ -6752,6 +7036,25 @@ function WorkspacePanel({
6752
7036
  window.removeEventListener("mouseup", onMouseUp);
6753
7037
  };
6754
7038
  }, []);
7039
+ if (collapsed) {
7040
+ return /* @__PURE__ */ jsx(
7041
+ "aside",
7042
+ {
7043
+ role: "button",
7044
+ tabIndex: 0,
7045
+ className: "relative z-10 flex min-h-0 w-10 shrink-0 cursor-pointer items-center justify-center border-l border-muted-foreground/20 bg-card text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30",
7046
+ "aria-label": "Expand workspace",
7047
+ onClick: () => onCollapsedChange?.(false),
7048
+ onKeyDown: (event) => {
7049
+ if (event.key === "Enter" || event.key === " ") {
7050
+ event.preventDefault();
7051
+ onCollapsedChange?.(false);
7052
+ }
7053
+ },
7054
+ children: /* @__PURE__ */ jsx(FolderOpenIcon, { className: "size-4 text-amber-500" })
7055
+ }
7056
+ );
7057
+ }
6755
7058
  return /* @__PURE__ */ jsxs(
6756
7059
  "aside",
6757
7060
  {
@@ -6784,6 +7087,19 @@ function WorkspacePanel({
6784
7087
  /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium text-foreground", children: rootName }),
6785
7088
  /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-faint", children: "Workspace" })
6786
7089
  ] }),
7090
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
7091
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
7092
+ Button,
7093
+ {
7094
+ variant: "ghost",
7095
+ size: "icon-sm",
7096
+ onClick: () => onCollapsedChange?.(true),
7097
+ "aria-label": "Collapse workspace",
7098
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-4" })
7099
+ }
7100
+ ) }),
7101
+ /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: "Collapse" })
7102
+ ] }),
6787
7103
  /* @__PURE__ */ jsxs(Tooltip, { children: [
6788
7104
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
6789
7105
  Button,
@@ -6863,8 +7179,8 @@ function WorkspacePanel({
6863
7179
  RenderEntries,
6864
7180
  {
6865
7181
  entries,
6866
- onFileSelect: handleSelect,
6867
- onDelete: setDeleteTarget
7182
+ onDelete: setDeleteTarget,
7183
+ downloadUrlFor: (path) => sessionId ? adapter.getWorkspaceDownloadUrl({ sessionId, path }) : "#"
6868
7184
  }
6869
7185
  )
6870
7186
  }
@@ -6876,7 +7192,13 @@ function WorkspacePanel({
6876
7192
  file,
6877
7193
  loading: fileLoading,
6878
7194
  error: fileError,
7195
+ downloadUrl: file && sessionId ? adapter.getWorkspaceDownloadUrl({
7196
+ sessionId,
7197
+ path: file.path
7198
+ }) : null,
7199
+ onDelete: file ? () => setDeleteTarget(file.path) : null,
6879
7200
  onClose: () => {
7201
+ onSelectedPathChange(null);
6880
7202
  setFile(null);
6881
7203
  setFileError(null);
6882
7204
  }
@@ -6977,10 +7299,11 @@ var EMPTY_TOKEN_USAGE = {
6977
7299
  contextWindow: 0,
6978
7300
  model: ""
6979
7301
  };
6980
- function createInitialAgentChatState() {
7302
+ function createInitialAgentChatState(options = {}) {
6981
7303
  return {
6982
7304
  messages: [],
6983
7305
  isRunning: false,
7306
+ isLoadingHistory: options.isLoadingHistory ?? false,
6984
7307
  tokenUsage: EMPTY_TOKEN_USAGE,
6985
7308
  retryIndicator: null,
6986
7309
  lastEventId: 0,
@@ -6992,6 +7315,7 @@ function createInitialAgentChatState() {
6992
7315
  function applyAgentChatEvent(state, event) {
6993
7316
  let nextState = {
6994
7317
  ...state,
7318
+ isLoadingHistory: false,
6995
7319
  lastEventId: Math.max(state.lastEventId, event.eventId),
6996
7320
  sessionDone: state.sessionDone || event.type === "session.done"
6997
7321
  };
@@ -7525,11 +7849,12 @@ function useAgentChatRuntime({
7525
7849
  onSessionChange
7526
7850
  }) {
7527
7851
  const [state, setState] = useState(
7528
- () => createInitialAgentChatState()
7852
+ () => createInitialAgentChatState({ isLoadingHistory: Boolean(sessionId) })
7529
7853
  );
7530
7854
  const stateRef = useRef(state);
7531
7855
  const streamRef = useRef(null);
7532
7856
  const reconnectTimerRef = useRef(null);
7857
+ const previousSessionIdRef = useRef(sessionId);
7533
7858
  useEffect(() => {
7534
7859
  stateRef.current = state;
7535
7860
  }, [state]);
@@ -7545,6 +7870,8 @@ function useAgentChatRuntime({
7545
7870
  stream?.close();
7546
7871
  }, []);
7547
7872
  useEffect(() => {
7873
+ const previousSessionId = previousSessionIdRef.current;
7874
+ previousSessionIdRef.current = sessionId;
7548
7875
  clearReconnectTimer();
7549
7876
  closeStream();
7550
7877
  if (!sessionId) {
@@ -7552,11 +7879,11 @@ function useAgentChatRuntime({
7552
7879
  return;
7553
7880
  }
7554
7881
  let cancelled = false;
7555
- const connect = () => {
7882
+ const connect = (after) => {
7556
7883
  if (cancelled) return;
7557
7884
  const stream = adapter.openEventStream({
7558
7885
  sessionId,
7559
- after: stateRef.current.lastEventId
7886
+ after: after ?? stateRef.current.lastEventId
7560
7887
  });
7561
7888
  streamRef.current = stream;
7562
7889
  for (const eventType of AGENT_CHAT_LISTENED_EVENTS) {
@@ -7579,14 +7906,32 @@ function useAgentChatRuntime({
7579
7906
  streamRef.current = null;
7580
7907
  }
7581
7908
  if (!stateRef.current.sessionDone && !cancelled) {
7582
- reconnectTimerRef.current = setTimeout(connect, 3e3);
7909
+ reconnectTimerRef.current = setTimeout(() => connect(), 3e3);
7583
7910
  }
7584
7911
  };
7585
7912
  };
7586
- setState(createInitialAgentChatState());
7587
- connect();
7913
+ const currentState = stateRef.current;
7914
+ const preservePendingFirstMessage = previousSessionId === null && currentState.isRunning && currentState.messages.some(
7915
+ (message) => message.role === "user" && message.id.startsWith("local-")
7916
+ );
7917
+ const initialState = preservePendingFirstMessage ? {
7918
+ ...createInitialAgentChatState({ isLoadingHistory: false }),
7919
+ messages: currentState.messages,
7920
+ isRunning: true
7921
+ } : createInitialAgentChatState({
7922
+ isLoadingHistory: true
7923
+ });
7924
+ stateRef.current = initialState;
7925
+ setState(initialState);
7926
+ connect(0);
7588
7927
  adapter.getSession({ sessionId }).then((session) => {
7589
7928
  if (cancelled) return;
7929
+ if (session.messageCount === 0) {
7930
+ setState((prev) => ({
7931
+ ...prev,
7932
+ isLoadingHistory: false
7933
+ }));
7934
+ }
7590
7935
  if (isTerminalStatus(session.status)) {
7591
7936
  setState((prev) => ({
7592
7937
  ...prev,
@@ -7669,13 +8014,18 @@ function useAgentChatRuntime({
7669
8014
  }, []);
7670
8015
  const send = useCallback(
7671
8016
  async (content) => {
8017
+ markSending(content);
7672
8018
  if (!sessionId) {
7673
- const session = await adapter.createSession({ agentId });
7674
- await adapter.sendMessage({ sessionId: session.id, content });
7675
- onSessionChange?.(session.id);
8019
+ try {
8020
+ const session = await adapter.createSession({ agentId });
8021
+ onSessionChange?.(session.id);
8022
+ await adapter.sendMessage({ sessionId: session.id, content });
8023
+ } catch (error) {
8024
+ markSendError(error instanceof Error ? error.message : "send failed");
8025
+ throw error;
8026
+ }
7676
8027
  return;
7677
8028
  }
7678
- markSending(content);
7679
8029
  try {
7680
8030
  await adapter.sendMessage({ sessionId, content });
7681
8031
  } catch (error) {
@@ -7712,6 +8062,7 @@ function useAgentChatRuntime({
7712
8062
  return {
7713
8063
  messages: state.messages,
7714
8064
  isRunning: state.isRunning,
8065
+ isLoadingHistory: state.isLoadingHistory,
7715
8066
  tokenUsage: state.tokenUsage,
7716
8067
  retryIndicator: state.retryIndicator,
7717
8068
  send,
@@ -7748,6 +8099,7 @@ function AgentChat({
7748
8099
  disabled
7749
8100
  }) {
7750
8101
  const [workspacePath, setWorkspacePath] = useState(null);
8102
+ const [workspaceCollapsed, setWorkspaceCollapsed] = useState(false);
7751
8103
  const runtime = useAgentChatRuntime({
7752
8104
  adapter,
7753
8105
  agentId,
@@ -7779,6 +8131,7 @@ function AgentChat({
7779
8131
  sessionId,
7780
8132
  messages: runtime.messages,
7781
8133
  isRunning: runtime.isRunning,
8134
+ isLoadingHistory: runtime.isLoadingHistory,
7782
8135
  onSend: (content) => void runtime.send(content),
7783
8136
  onStop: () => void runtime.stop(),
7784
8137
  onRetry: runtime.retry,
@@ -7795,6 +8148,8 @@ function AgentChat({
7795
8148
  sessionId,
7796
8149
  selectedPath: workspacePath,
7797
8150
  onSelectedPathChange: setWorkspacePath,
8151
+ collapsed: workspaceCollapsed,
8152
+ onCollapsedChange: setWorkspaceCollapsed,
7798
8153
  disabled
7799
8154
  }
7800
8155
  )
@@ -7865,6 +8220,20 @@ function mergeTreeNodes(groups) {
7865
8220
  }
7866
8221
  return Array.from(byId.values());
7867
8222
  }
8223
+ function pruneDeletedSessionNodes(nodes, deletedSessionId) {
8224
+ const deletedIds = /* @__PURE__ */ new Set([deletedSessionId]);
8225
+ let changed = true;
8226
+ while (changed) {
8227
+ changed = false;
8228
+ for (const node of nodes) {
8229
+ if (node.parentId && deletedIds.has(node.parentId) && !deletedIds.has(node.id)) {
8230
+ deletedIds.add(node.id);
8231
+ changed = true;
8232
+ }
8233
+ }
8234
+ }
8235
+ return nodes.filter((node) => !deletedIds.has(node.id));
8236
+ }
7868
8237
  function formatSessionTime(value) {
7869
8238
  const date = new Date(value);
7870
8239
  if (Number.isNaN(date.getTime())) return "";
@@ -7981,11 +8350,12 @@ function SessionTreePanel({
7981
8350
  title = "Running",
7982
8351
  sessionListLimit = DEFAULT_SESSION_LIST_LIMIT,
7983
8352
  hideRoot = false,
8353
+ hideHeader = false,
8354
+ loadList = false,
7984
8355
  onSessionSelect,
7985
8356
  onSessionDelete
7986
8357
  }) {
7987
8358
  const [nodes, setNodes] = useState([]);
7988
- const [loading, setLoading] = useState(false);
7989
8359
  const [error, setError] = useState(null);
7990
8360
  const [hasEverLoaded, setHasEverLoaded] = useState(false);
7991
8361
  const [deletingSessionId, setDeletingSessionId] = useState(
@@ -7997,18 +8367,16 @@ function SessionTreePanel({
7997
8367
  const resetContext = useRef(null);
7998
8368
  const refetch = useCallback(
7999
8369
  async (opts) => {
8000
- const canLoadSessionList = Boolean(agentId && adapter.listSessions);
8370
+ const canLoadSessionList = Boolean(loadList && adapter.listSessions);
8001
8371
  const canLoadSessionTree = Boolean(sessionId && adapter.getSessionTree);
8002
8372
  if (!canLoadSessionList && !canLoadSessionTree) {
8003
8373
  setNodes([]);
8004
8374
  setHasEverLoaded(true);
8005
- setLoading(false);
8006
8375
  return;
8007
8376
  }
8008
8377
  const currentRequestId = ++requestId.current;
8009
- if (!opts?.silent) setLoading(true);
8010
8378
  try {
8011
- const sessionListPromise = agentId && adapter.listSessions ? adapter.listSessions({
8379
+ const sessionListPromise = loadList && adapter.listSessions ? adapter.listSessions({
8012
8380
  agentId,
8013
8381
  limit: sessionListLimit
8014
8382
  }) : Promise.resolve(null);
@@ -8034,13 +8402,9 @@ function SessionTreePanel({
8034
8402
  if (!opts?.silent) {
8035
8403
  setError(e instanceof Error ? e.message : "Failed to load tree");
8036
8404
  }
8037
- } finally {
8038
- if (mounted.current && currentRequestId === requestId.current && !opts?.silent) {
8039
- setLoading(false);
8040
- }
8041
8405
  }
8042
8406
  },
8043
- [adapter, agentId, sessionId, sessionListLimit]
8407
+ [adapter, agentId, loadList, sessionId, sessionListLimit]
8044
8408
  );
8045
8409
  useEffect(() => {
8046
8410
  mounted.current = true;
@@ -8050,8 +8414,8 @@ function SessionTreePanel({
8050
8414
  }, []);
8051
8415
  useEffect(() => {
8052
8416
  const previous = resetContext.current;
8053
- const shouldReset = !previous || previous.adapter !== adapter || previous.agentId !== agentId || previous.sessionListLimit !== sessionListLimit;
8054
- resetContext.current = { adapter, agentId, sessionListLimit };
8417
+ const shouldReset = !previous || previous.loadList !== loadList || previous.agentId !== agentId || previous.sessionListLimit !== sessionListLimit;
8418
+ resetContext.current = { loadList, agentId, sessionListLimit };
8055
8419
  if (shouldReset) {
8056
8420
  setNodes([]);
8057
8421
  setHasEverLoaded(false);
@@ -8059,7 +8423,7 @@ function SessionTreePanel({
8059
8423
  }
8060
8424
  setError(null);
8061
8425
  void refetch();
8062
- }, [adapter, agentId, refetch, sessionListLimit]);
8426
+ }, [adapter, agentId, loadList, refetch, sessionListLimit]);
8063
8427
  const runningCount = useMemo(
8064
8428
  () => nodes.filter((n) => n.status === "active").length,
8065
8429
  [nodes]
@@ -8096,6 +8460,11 @@ function SessionTreePanel({
8096
8460
  setDeletingSessionId(id);
8097
8461
  try {
8098
8462
  await adapter.deleteSession({ sessionId: id });
8463
+ setNodes((current) => {
8464
+ const next = pruneDeletedSessionNodes(current, id);
8465
+ lastFingerprint.current = treeFingerprint(next);
8466
+ return next;
8467
+ });
8099
8468
  onSessionDelete?.(id);
8100
8469
  await refetch({ silent: true });
8101
8470
  } catch (e) {
@@ -8110,24 +8479,12 @@ function SessionTreePanel({
8110
8479
  if (nodes.length === 0) return null;
8111
8480
  const topLevel = hideRoot ? roots.flatMap((r) => r.children) : roots;
8112
8481
  if (topLevel.length === 0) return null;
8113
- return /* @__PURE__ */ jsxs("div", { className: "border-t border-line", children: [
8114
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-3 py-2 text-xs font-semibold uppercase tracking-wide", children: [
8482
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
8483
+ !hideHeader && /* @__PURE__ */ jsxs("div", { className: "border-t border-line flex items-center gap-1.5 px-3 py-2 text-xs font-semibold uppercase tracking-wide", children: [
8115
8484
  /* @__PURE__ */ jsx(UsersIcon, { className: "w-3.5 h-3.5" }),
8116
8485
  /* @__PURE__ */ jsx("span", { children: title }),
8117
8486
  runningCount > 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", className: "h-4 px-1.5 text-[10px] ml-auto", children: runningCount })
8118
8487
  ] }),
8119
- loading && /* @__PURE__ */ jsx(
8120
- "div",
8121
- {
8122
- className: "px-3 py-2",
8123
- role: "status",
8124
- "aria-label": "Loading sessions",
8125
- children: /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
8126
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-28" }),
8127
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-20" })
8128
- ] })
8129
- }
8130
- ),
8131
8488
  error && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-destructive", children: error }),
8132
8489
  !error && /* @__PURE__ */ jsx("div", { className: "px-1 pb-2", children: topLevel.map((entry) => /* @__PURE__ */ jsx(
8133
8490
  TreeNodeRow,