@invergent/agent-chat-react 1.5.3 → 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,8 +1,7 @@
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
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';
@@ -21,7 +20,6 @@ import { nanoid } from 'nanoid';
21
20
  import 'pdfjs-dist/web/pdf_viewer.css';
22
21
  import { formatDistanceToNow } from 'date-fns';
23
22
 
24
- // src/agent-chat.tsx
25
23
  var AgentChatAdapterContext = createContext(null);
26
24
  var AgentChatAdapterProvider = AgentChatAdapterContext.Provider;
27
25
  function useAgentChatAdapterContext() {
@@ -33,9 +31,6 @@ function useAgentChatAdapterContext() {
33
31
  }
34
32
  return value;
35
33
  }
36
- function cn(...inputs) {
37
- return twMerge(clsx(inputs));
38
- }
39
34
  var buttonVariants = cva(
40
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",
41
36
  {
@@ -87,7 +82,7 @@ function Button({
87
82
  var Conversation = ({ className, ...props }) => /* @__PURE__ */ jsx(
88
83
  StickToBottom,
89
84
  {
90
- className: cn("relative flex-1 overflow-y-auto", className),
85
+ className: cn("relative flex-1", className),
91
86
  initial: "smooth",
92
87
  resize: "smooth",
93
88
  role: "log",
@@ -570,6 +565,61 @@ function TimelineSeparator({
570
565
  }
571
566
  );
572
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
+ }
573
623
  function CopyButton({ text }) {
574
624
  const [copied, setCopied] = useState(false);
575
625
  return /* @__PURE__ */ jsx(
@@ -605,7 +655,8 @@ function parseTerminalResult(result, args) {
605
655
  const hasOutput = typeof parsed?.output === "string";
606
656
  const hasStdout = typeof parsed?.stdout === "string";
607
657
  const hasExitCode = typeof parsed?.exit_code === "number";
608
- if (!hasOutput && !hasStdout && !hasExitCode) {
658
+ const errorSummary = toolErrorSummary(result, 400);
659
+ if (!hasOutput && !hasStdout && !hasExitCode && !errorSummary) {
609
660
  return null;
610
661
  }
611
662
  let command = "";
@@ -615,20 +666,20 @@ function parseTerminalResult(result, args) {
615
666
  } catch {
616
667
  }
617
668
  const output = parsed.output ?? parsed.stdout ?? "";
618
- const stderr = parsed.stderr ?? parsed.error ?? "";
669
+ const stderr = parsed.stderr ?? errorSummary;
619
670
  const combined = stderr ? `${output}
620
671
  ${stderr}`.trim() : output;
621
672
  return {
622
673
  output: combined,
623
- exit_code: parsed.exit_code ?? 0,
624
- error: parsed.error ?? null,
674
+ exit_code: parsed.exit_code ?? (errorSummary ? 1 : 0),
675
+ error: errorSummary || null,
625
676
  command
626
677
  };
627
678
  } catch {
628
679
  return null;
629
680
  }
630
681
  }
631
- function TerminalCollapsible({ command, output, isRunning }) {
682
+ function TerminalCollapsible({ command, output, isRunning, hasError }) {
632
683
  const [isOpen, setIsOpen] = useState(false);
633
684
  return /* @__PURE__ */ jsxs(
634
685
  Collapsible,
@@ -638,7 +689,7 @@ function TerminalCollapsible({ command, output, isRunning }) {
638
689
  className: "not-prose w-full",
639
690
  children: [
640
691
  /* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "group/trigger flex w-fit items-center gap-2 text-sm transition-colors", children: [
641
- /* @__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" }) }),
642
693
  /* @__PURE__ */ jsx(
643
694
  ChevronDownIcon,
644
695
  {
@@ -695,7 +746,8 @@ function TerminalToolBlock({ tc }) {
695
746
  {
696
747
  command: result.command,
697
748
  output,
698
- isRunning
749
+ isRunning,
750
+ hasError: result.exit_code !== 0 || Boolean(result.error)
699
751
  }
700
752
  );
701
753
  }
@@ -2340,7 +2392,7 @@ var Terminal = ({
2340
2392
  }
2341
2393
  ) });
2342
2394
  };
2343
- function parseArgs(args) {
2395
+ function parseArgs2(args) {
2344
2396
  try {
2345
2397
  return JSON.parse(args);
2346
2398
  } catch {
@@ -2564,7 +2616,7 @@ function OutputPreview({ output }) {
2564
2616
  }
2565
2617
  function ProcessToolBlock({ tc }) {
2566
2618
  const isRunning = tc.status === "running";
2567
- const args = parseArgs(tc.args);
2619
+ const args = parseArgs2(tc.args);
2568
2620
  const result = parseResult(tc.result);
2569
2621
  const action = args.action || "unknown";
2570
2622
  const actionLabel = {
@@ -2647,44 +2699,6 @@ function Textarea({ className, ...props }) {
2647
2699
  }
2648
2700
  );
2649
2701
  }
2650
-
2651
- // src/components/chat/tools/shared.ts
2652
- function statusColorClass(status) {
2653
- if (status === "running") return "bg-primary animate-pulse";
2654
- if (status === "error") return "bg-red-500";
2655
- if (status === "cancelled") return "bg-muted-foreground/40";
2656
- return "bg-emerald-500";
2657
- }
2658
- function effectiveStatus(tc) {
2659
- if (tc.cancelled) return "cancelled";
2660
- if (tc.status !== "complete" || !tc.result) return tc.status;
2661
- try {
2662
- const parsed = JSON.parse(tc.result);
2663
- if (parsed?.exit_code !== void 0 && parsed.exit_code !== 0) return "error";
2664
- if (parsed?.error) return "error";
2665
- if (parsed?.status === "blocked" || parsed?.status === "error") return "error";
2666
- if (parsed?.success === false) return "error";
2667
- } catch {
2668
- }
2669
- return "complete";
2670
- }
2671
- function formatArgs(args) {
2672
- try {
2673
- return JSON.stringify(JSON.parse(args), null, 2);
2674
- } catch {
2675
- return args;
2676
- }
2677
- }
2678
- function parseArgs2(args) {
2679
- try {
2680
- return JSON.parse(args);
2681
- } catch {
2682
- return null;
2683
- }
2684
- }
2685
- function truncate(s, max) {
2686
- return s.length > max ? s.slice(0, max) + "\n... (truncated)" : s;
2687
- }
2688
2702
  var MAX_REASON_LENGTH = 500;
2689
2703
  function ExpertToolBlock({ tc }) {
2690
2704
  const [expanded, setExpanded] = useState(false);
@@ -2720,7 +2734,7 @@ function ExpertToolBlock({ tc }) {
2720
2734
  }
2721
2735
  void submit("up");
2722
2736
  };
2723
- const args = parseArgs2(tc.args) ?? {};
2737
+ const args = parseArgs(tc.args) ?? {};
2724
2738
  const expertName = args.expert ?? null;
2725
2739
  const question = args.question ?? args.prompt ?? "";
2726
2740
  const result = parseExpertResult(tc.result);
@@ -2832,7 +2846,7 @@ function ExpertToolBlock({ tc }) {
2832
2846
  }
2833
2847
  function parseExpertResult(result) {
2834
2848
  if (!result) return null;
2835
- const parsed = parseArgs2(result);
2849
+ const parsed = parseArgs(result);
2836
2850
  if (parsed) {
2837
2851
  return {
2838
2852
  ...parsed,
@@ -2939,11 +2953,11 @@ function ReasonForm({
2939
2953
  ] });
2940
2954
  }
2941
2955
  function SkillsListBlock({ tc }) {
2942
- const args = parseArgs2(tc.args) ?? {};
2956
+ const args = parseArgs(tc.args) ?? {};
2943
2957
  const filter = args.category ? `category: ${args.category}` : "all";
2944
2958
  let summary = "";
2945
2959
  if (tc.result) {
2946
- const parsed = parseArgs2(tc.result);
2960
+ const parsed = parseArgs(tc.result);
2947
2961
  if (parsed?.count !== void 0) {
2948
2962
  summary = `${parsed.count} skill${parsed.count === 1 ? "" : "s"}`;
2949
2963
  }
@@ -2958,11 +2972,11 @@ function SkillsListBlock({ tc }) {
2958
2972
  ] });
2959
2973
  }
2960
2974
  function SkillViewBlock({ tc }) {
2961
- const args = parseArgs2(tc.args) ?? {};
2975
+ const args = parseArgs(tc.args) ?? {};
2962
2976
  const target = args.file_path ? `${args.name ?? "?"}/${args.file_path}` : args.name ?? "?";
2963
2977
  let summary = "";
2964
2978
  if (tc.result) {
2965
- const parsed = parseArgs2(tc.result);
2979
+ const parsed = parseArgs(tc.result);
2966
2980
  if (parsed?.staged_at) {
2967
2981
  summary = `staged at ${parsed.staged_at}`;
2968
2982
  } else if (parsed?.token_estimate) {
@@ -3009,7 +3023,7 @@ function buildAnswer(q, sel) {
3009
3023
  }
3010
3024
  function ClarifyToolBlock({ tc }) {
3011
3025
  const { adapter, sessionId } = useAgentChatAdapterContext();
3012
- const args = useMemo(() => parseArgs2(tc.args), [tc.args]);
3026
+ const args = useMemo(() => parseArgs(tc.args), [tc.args]);
3013
3027
  const questions = args?.questions ?? [];
3014
3028
  const [active, setActive] = useState(0);
3015
3029
  const [selections, setSelections] = useState(
@@ -3291,7 +3305,7 @@ function ClarifyLocked({
3291
3305
  ] });
3292
3306
  }
3293
3307
  function ArtifactToolBlock({ tc }) {
3294
- const args = parseArgs2(tc.args) ?? {};
3308
+ const args = parseArgs(tc.args) ?? {};
3295
3309
  const status = effectiveStatus(tc);
3296
3310
  const label = status === "running" ? "Creating artifact\u2026" : status === "error" ? "Tried to create" : "Created";
3297
3311
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-sm ", children: [
@@ -3305,7 +3319,7 @@ function firstLine(s) {
3305
3319
  }
3306
3320
  function DelegateToolBlock({ tc }) {
3307
3321
  const [expanded, setExpanded] = useState(false);
3308
- const args = parseArgs2(tc.args);
3322
+ const args = parseArgs(tc.args);
3309
3323
  const goal = args?.goal ?? "";
3310
3324
  const context = args?.context ?? "";
3311
3325
  const agentType = args?.agent_type;
@@ -3415,8 +3429,8 @@ var TARGET_LABEL = {
3415
3429
  };
3416
3430
  function MemoryToolBlock({ tc }) {
3417
3431
  const [isOpen, setIsOpen] = useState(false);
3418
- const args = parseArgs2(tc.args) ?? {};
3419
- const result = tc.result ? parseArgs2(tc.result) : null;
3432
+ const args = parseArgs(tc.args) ?? {};
3433
+ const result = tc.result ? parseArgs(tc.result) : null;
3420
3434
  const action = args.action ?? "add";
3421
3435
  const target = args.target ?? "memory";
3422
3436
  const verb = ACTION_VERB[action] ?? action;
@@ -3494,8 +3508,8 @@ var ACTION_LABEL = {
3494
3508
  remove_file: "Remove skill file"
3495
3509
  };
3496
3510
  function SkillManageToolBlock({ tc }) {
3497
- const args = parseArgs2(tc.args) ?? {};
3498
- const result = tc.result ? parseArgs2(tc.result) : null;
3511
+ const args = parseArgs(tc.args) ?? {};
3512
+ const result = tc.result ? parseArgs(tc.result) : null;
3499
3513
  const action = args.action ?? "manage";
3500
3514
  const label = ACTION_LABEL[action] ?? "Manage skill";
3501
3515
  const target = args.file_path ? `${args.name ?? "?"}/${args.file_path}` : args.name ?? "?";
@@ -3516,8 +3530,8 @@ function firstLine2(value) {
3516
3530
  return (index === -1 ? value : value.slice(0, index)).trim();
3517
3531
  }
3518
3532
  function CoordinatorToolBlock({ tc }) {
3519
- const args = parseArgs2(tc.args) ?? {};
3520
- const result = tc.result ? parseArgs2(tc.result) : null;
3533
+ const args = parseArgs(tc.args) ?? {};
3534
+ const result = tc.result ? parseArgs(tc.result) : null;
3521
3535
  const failed = Boolean(result?.error);
3522
3536
  let label = "Worker";
3523
3537
  let target = "";
@@ -5468,7 +5482,7 @@ function exportArtifact(payload) {
5468
5482
  };
5469
5483
  case "chart":
5470
5484
  return {
5471
- text: JSON.stringify(payload.spec.vega_lite, null, 2),
5485
+ text: JSON.stringify(payload.spec.chart_js, null, 2),
5472
5486
  mime: "application/json",
5473
5487
  extension: "json"
5474
5488
  };
@@ -5529,7 +5543,7 @@ async function copyText(text) {
5529
5543
  }
5530
5544
  }
5531
5545
  var ArtifactChart = lazy(
5532
- () => import('./artifact-chart-7J6GOR4M.js').then((m) => ({ default: m.ArtifactChart }))
5546
+ () => import('./artifact-chart-X53FKRDZ.js').then((m) => ({ default: m.ArtifactChart }))
5533
5547
  );
5534
5548
  var KIND_LABEL = {
5535
5549
  markdown: "Markdown document",
@@ -5654,7 +5668,7 @@ function ArtifactBody({
5654
5668
  Suspense,
5655
5669
  {
5656
5670
  fallback: /* @__PURE__ */ jsx(Shimmer, { duration: 5, className: "text-sm text-muted-foreground", children: "Loading chart\u2026" }),
5657
- children: /* @__PURE__ */ jsx(ArtifactChart, { spec: payload.spec })
5671
+ children: /* @__PURE__ */ jsx(ArtifactChart, { spec: payload.spec, fill })
5658
5672
  }
5659
5673
  );
5660
5674
  case "html":
@@ -5874,7 +5888,7 @@ function OrphanSystemMarker({
5874
5888
  );
5875
5889
  }
5876
5890
  if (message.systemKind === "error" && message.errorInfo) {
5877
- 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 }) });
5878
5892
  }
5879
5893
  return null;
5880
5894
  }
@@ -5928,6 +5942,7 @@ function TimelineEntryItem({
5928
5942
  ] });
5929
5943
  }
5930
5944
  if (entry.kind === "tool") {
5945
+ const failureSummary = effectiveStatus(entry.tc) === "error" ? toolErrorSummary(entry.tc.result) : "";
5931
5946
  return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
5932
5947
  /* @__PURE__ */ jsxs(TimelineHeader, { children: [
5933
5948
  /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
@@ -5938,7 +5953,10 @@ function TimelineEntryItem({
5938
5953
  }
5939
5954
  )
5940
5955
  ] }),
5941
- /* @__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
+ ] }) })
5942
5960
  ] });
5943
5961
  }
5944
5962
  if (entry.kind === "text") {
@@ -6056,6 +6074,7 @@ function ChatThread({
6056
6074
  sessionId,
6057
6075
  messages,
6058
6076
  isRunning,
6077
+ isLoadingHistory = false,
6059
6078
  onSend,
6060
6079
  onStop,
6061
6080
  onFileSelect,
@@ -6078,7 +6097,14 @@ function ChatThread({
6078
6097
  }, [messages]);
6079
6098
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col overflow-hidden bg-background text-sm", children: [
6080
6099
  /* @__PURE__ */ jsxs(Conversation, { className: "relative flex-1 min-h-0", children: [
6081
- /* @__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(
6082
6108
  ConversationEmptyState,
6083
6109
  {
6084
6110
  icon: /* @__PURE__ */ jsx(MessageSquareIcon, { className: "size-8 opacity-40" }),
@@ -6131,7 +6157,7 @@ function ChatThread({
6131
6157
  ] }) }),
6132
6158
  /* @__PURE__ */ jsx(ConversationScrollButton, {})
6133
6159
  ] }),
6134
- /* @__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: [
6135
6161
  retryIndicator && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(RetryBanner, { indicator: retryIndicator }) }),
6136
6162
  /* @__PURE__ */ jsx(
6137
6163
  ChatComposer,
@@ -6732,7 +6758,7 @@ function formatPdfPreviewError(error) {
6732
6758
  return error instanceof Error ? error.message : "Failed to render PDF preview.";
6733
6759
  }
6734
6760
  var SKELETON_WIDTHS2 = [75, 60, 90, 65, 80, 70, 85, 55];
6735
- var DEFAULT_WIDTH = 500;
6761
+ var DEFAULT_WIDTH = 400;
6736
6762
  var MIN_WIDTH = 300;
6737
6763
  var MAX_WIDTH = 900;
6738
6764
  function collectExpandedPaths(entries, depth = 0) {
@@ -6814,6 +6840,8 @@ function WorkspacePanel({
6814
6840
  sessionId,
6815
6841
  selectedPath,
6816
6842
  onSelectedPathChange,
6843
+ collapsed = false,
6844
+ onCollapsedChange,
6817
6845
  disabled = false
6818
6846
  }) {
6819
6847
  const fileInputRef = useRef(null);
@@ -7008,6 +7036,25 @@ function WorkspacePanel({
7008
7036
  window.removeEventListener("mouseup", onMouseUp);
7009
7037
  };
7010
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
+ }
7011
7058
  return /* @__PURE__ */ jsxs(
7012
7059
  "aside",
7013
7060
  {
@@ -7040,6 +7087,19 @@ function WorkspacePanel({
7040
7087
  /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium text-foreground", children: rootName }),
7041
7088
  /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-faint", children: "Workspace" })
7042
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
+ ] }),
7043
7103
  /* @__PURE__ */ jsxs(Tooltip, { children: [
7044
7104
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
7045
7105
  Button,
@@ -7239,10 +7299,11 @@ var EMPTY_TOKEN_USAGE = {
7239
7299
  contextWindow: 0,
7240
7300
  model: ""
7241
7301
  };
7242
- function createInitialAgentChatState() {
7302
+ function createInitialAgentChatState(options = {}) {
7243
7303
  return {
7244
7304
  messages: [],
7245
7305
  isRunning: false,
7306
+ isLoadingHistory: options.isLoadingHistory ?? false,
7246
7307
  tokenUsage: EMPTY_TOKEN_USAGE,
7247
7308
  retryIndicator: null,
7248
7309
  lastEventId: 0,
@@ -7254,6 +7315,7 @@ function createInitialAgentChatState() {
7254
7315
  function applyAgentChatEvent(state, event) {
7255
7316
  let nextState = {
7256
7317
  ...state,
7318
+ isLoadingHistory: false,
7257
7319
  lastEventId: Math.max(state.lastEventId, event.eventId),
7258
7320
  sessionDone: state.sessionDone || event.type === "session.done"
7259
7321
  };
@@ -7787,11 +7849,12 @@ function useAgentChatRuntime({
7787
7849
  onSessionChange
7788
7850
  }) {
7789
7851
  const [state, setState] = useState(
7790
- () => createInitialAgentChatState()
7852
+ () => createInitialAgentChatState({ isLoadingHistory: Boolean(sessionId) })
7791
7853
  );
7792
7854
  const stateRef = useRef(state);
7793
7855
  const streamRef = useRef(null);
7794
7856
  const reconnectTimerRef = useRef(null);
7857
+ const previousSessionIdRef = useRef(sessionId);
7795
7858
  useEffect(() => {
7796
7859
  stateRef.current = state;
7797
7860
  }, [state]);
@@ -7807,6 +7870,8 @@ function useAgentChatRuntime({
7807
7870
  stream?.close();
7808
7871
  }, []);
7809
7872
  useEffect(() => {
7873
+ const previousSessionId = previousSessionIdRef.current;
7874
+ previousSessionIdRef.current = sessionId;
7810
7875
  clearReconnectTimer();
7811
7876
  closeStream();
7812
7877
  if (!sessionId) {
@@ -7814,11 +7879,11 @@ function useAgentChatRuntime({
7814
7879
  return;
7815
7880
  }
7816
7881
  let cancelled = false;
7817
- const connect = () => {
7882
+ const connect = (after) => {
7818
7883
  if (cancelled) return;
7819
7884
  const stream = adapter.openEventStream({
7820
7885
  sessionId,
7821
- after: stateRef.current.lastEventId
7886
+ after: after ?? stateRef.current.lastEventId
7822
7887
  });
7823
7888
  streamRef.current = stream;
7824
7889
  for (const eventType of AGENT_CHAT_LISTENED_EVENTS) {
@@ -7841,14 +7906,32 @@ function useAgentChatRuntime({
7841
7906
  streamRef.current = null;
7842
7907
  }
7843
7908
  if (!stateRef.current.sessionDone && !cancelled) {
7844
- reconnectTimerRef.current = setTimeout(connect, 3e3);
7909
+ reconnectTimerRef.current = setTimeout(() => connect(), 3e3);
7845
7910
  }
7846
7911
  };
7847
7912
  };
7848
- setState(createInitialAgentChatState());
7849
- 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);
7850
7927
  adapter.getSession({ sessionId }).then((session) => {
7851
7928
  if (cancelled) return;
7929
+ if (session.messageCount === 0) {
7930
+ setState((prev) => ({
7931
+ ...prev,
7932
+ isLoadingHistory: false
7933
+ }));
7934
+ }
7852
7935
  if (isTerminalStatus(session.status)) {
7853
7936
  setState((prev) => ({
7854
7937
  ...prev,
@@ -7931,13 +8014,18 @@ function useAgentChatRuntime({
7931
8014
  }, []);
7932
8015
  const send = useCallback(
7933
8016
  async (content) => {
8017
+ markSending(content);
7934
8018
  if (!sessionId) {
7935
- const session = await adapter.createSession({ agentId });
7936
- await adapter.sendMessage({ sessionId: session.id, content });
7937
- 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
+ }
7938
8027
  return;
7939
8028
  }
7940
- markSending(content);
7941
8029
  try {
7942
8030
  await adapter.sendMessage({ sessionId, content });
7943
8031
  } catch (error) {
@@ -7974,6 +8062,7 @@ function useAgentChatRuntime({
7974
8062
  return {
7975
8063
  messages: state.messages,
7976
8064
  isRunning: state.isRunning,
8065
+ isLoadingHistory: state.isLoadingHistory,
7977
8066
  tokenUsage: state.tokenUsage,
7978
8067
  retryIndicator: state.retryIndicator,
7979
8068
  send,
@@ -8010,6 +8099,7 @@ function AgentChat({
8010
8099
  disabled
8011
8100
  }) {
8012
8101
  const [workspacePath, setWorkspacePath] = useState(null);
8102
+ const [workspaceCollapsed, setWorkspaceCollapsed] = useState(false);
8013
8103
  const runtime = useAgentChatRuntime({
8014
8104
  adapter,
8015
8105
  agentId,
@@ -8041,6 +8131,7 @@ function AgentChat({
8041
8131
  sessionId,
8042
8132
  messages: runtime.messages,
8043
8133
  isRunning: runtime.isRunning,
8134
+ isLoadingHistory: runtime.isLoadingHistory,
8044
8135
  onSend: (content) => void runtime.send(content),
8045
8136
  onStop: () => void runtime.stop(),
8046
8137
  onRetry: runtime.retry,
@@ -8057,6 +8148,8 @@ function AgentChat({
8057
8148
  sessionId,
8058
8149
  selectedPath: workspacePath,
8059
8150
  onSelectedPathChange: setWorkspacePath,
8151
+ collapsed: workspaceCollapsed,
8152
+ onCollapsedChange: setWorkspaceCollapsed,
8060
8153
  disabled
8061
8154
  }
8062
8155
  )
@@ -8127,6 +8220,20 @@ function mergeTreeNodes(groups) {
8127
8220
  }
8128
8221
  return Array.from(byId.values());
8129
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
+ }
8130
8237
  function formatSessionTime(value) {
8131
8238
  const date = new Date(value);
8132
8239
  if (Number.isNaN(date.getTime())) return "";
@@ -8243,11 +8350,12 @@ function SessionTreePanel({
8243
8350
  title = "Running",
8244
8351
  sessionListLimit = DEFAULT_SESSION_LIST_LIMIT,
8245
8352
  hideRoot = false,
8353
+ hideHeader = false,
8354
+ loadList = false,
8246
8355
  onSessionSelect,
8247
8356
  onSessionDelete
8248
8357
  }) {
8249
8358
  const [nodes, setNodes] = useState([]);
8250
- const [loading, setLoading] = useState(false);
8251
8359
  const [error, setError] = useState(null);
8252
8360
  const [hasEverLoaded, setHasEverLoaded] = useState(false);
8253
8361
  const [deletingSessionId, setDeletingSessionId] = useState(
@@ -8259,18 +8367,16 @@ function SessionTreePanel({
8259
8367
  const resetContext = useRef(null);
8260
8368
  const refetch = useCallback(
8261
8369
  async (opts) => {
8262
- const canLoadSessionList = Boolean(agentId && adapter.listSessions);
8370
+ const canLoadSessionList = Boolean(loadList && adapter.listSessions);
8263
8371
  const canLoadSessionTree = Boolean(sessionId && adapter.getSessionTree);
8264
8372
  if (!canLoadSessionList && !canLoadSessionTree) {
8265
8373
  setNodes([]);
8266
8374
  setHasEverLoaded(true);
8267
- setLoading(false);
8268
8375
  return;
8269
8376
  }
8270
8377
  const currentRequestId = ++requestId.current;
8271
- if (!opts?.silent) setLoading(true);
8272
8378
  try {
8273
- const sessionListPromise = agentId && adapter.listSessions ? adapter.listSessions({
8379
+ const sessionListPromise = loadList && adapter.listSessions ? adapter.listSessions({
8274
8380
  agentId,
8275
8381
  limit: sessionListLimit
8276
8382
  }) : Promise.resolve(null);
@@ -8296,13 +8402,9 @@ function SessionTreePanel({
8296
8402
  if (!opts?.silent) {
8297
8403
  setError(e instanceof Error ? e.message : "Failed to load tree");
8298
8404
  }
8299
- } finally {
8300
- if (mounted.current && currentRequestId === requestId.current && !opts?.silent) {
8301
- setLoading(false);
8302
- }
8303
8405
  }
8304
8406
  },
8305
- [adapter, agentId, sessionId, sessionListLimit]
8407
+ [adapter, agentId, loadList, sessionId, sessionListLimit]
8306
8408
  );
8307
8409
  useEffect(() => {
8308
8410
  mounted.current = true;
@@ -8312,8 +8414,8 @@ function SessionTreePanel({
8312
8414
  }, []);
8313
8415
  useEffect(() => {
8314
8416
  const previous = resetContext.current;
8315
- const shouldReset = !previous || previous.adapter !== adapter || previous.agentId !== agentId || previous.sessionListLimit !== sessionListLimit;
8316
- resetContext.current = { adapter, agentId, sessionListLimit };
8417
+ const shouldReset = !previous || previous.loadList !== loadList || previous.agentId !== agentId || previous.sessionListLimit !== sessionListLimit;
8418
+ resetContext.current = { loadList, agentId, sessionListLimit };
8317
8419
  if (shouldReset) {
8318
8420
  setNodes([]);
8319
8421
  setHasEverLoaded(false);
@@ -8321,7 +8423,7 @@ function SessionTreePanel({
8321
8423
  }
8322
8424
  setError(null);
8323
8425
  void refetch();
8324
- }, [adapter, agentId, refetch, sessionListLimit]);
8426
+ }, [adapter, agentId, loadList, refetch, sessionListLimit]);
8325
8427
  const runningCount = useMemo(
8326
8428
  () => nodes.filter((n) => n.status === "active").length,
8327
8429
  [nodes]
@@ -8358,6 +8460,11 @@ function SessionTreePanel({
8358
8460
  setDeletingSessionId(id);
8359
8461
  try {
8360
8462
  await adapter.deleteSession({ sessionId: id });
8463
+ setNodes((current) => {
8464
+ const next = pruneDeletedSessionNodes(current, id);
8465
+ lastFingerprint.current = treeFingerprint(next);
8466
+ return next;
8467
+ });
8361
8468
  onSessionDelete?.(id);
8362
8469
  await refetch({ silent: true });
8363
8470
  } catch (e) {
@@ -8372,24 +8479,12 @@ function SessionTreePanel({
8372
8479
  if (nodes.length === 0) return null;
8373
8480
  const topLevel = hideRoot ? roots.flatMap((r) => r.children) : roots;
8374
8481
  if (topLevel.length === 0) return null;
8375
- return /* @__PURE__ */ jsxs("div", { className: "border-t border-line", children: [
8376
- /* @__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: [
8377
8484
  /* @__PURE__ */ jsx(UsersIcon, { className: "w-3.5 h-3.5" }),
8378
8485
  /* @__PURE__ */ jsx("span", { children: title }),
8379
8486
  runningCount > 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", className: "h-4 px-1.5 text-[10px] ml-auto", children: runningCount })
8380
8487
  ] }),
8381
- loading && /* @__PURE__ */ jsx(
8382
- "div",
8383
- {
8384
- className: "px-3 py-2",
8385
- role: "status",
8386
- "aria-label": "Loading sessions",
8387
- children: /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
8388
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-28" }),
8389
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-20" })
8390
- ] })
8391
- }
8392
- ),
8393
8488
  error && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-destructive", children: error }),
8394
8489
  !error && /* @__PURE__ */ jsx("div", { className: "px-1 pb-2", children: topLevel.map((entry) => /* @__PURE__ */ jsx(
8395
8490
  TreeNodeRow,