@invergent/agent-chat-react 1.4.6 → 1.4.10

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
@@ -4,7 +4,7 @@ import { Collapsible as Collapsible$1, Slot, Dialog as Dialog$1, ScrollArea as S
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, ListTodoIcon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircle2Icon, XCircleIcon, CheckCircleIcon, CircleIcon, MessageSquareIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ArrowDownIcon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, PlusIcon, CornerDownLeftIcon, SquareIcon } from 'lucide-react';
7
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, ListTodoIcon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircle2Icon, XCircleIcon, CheckCircleIcon, CircleIcon, UsersIcon, MessageSquareIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
8
8
  import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
9
9
  import { cjk } from '@streamdown/cjk';
10
10
  import { code } from '@streamdown/code';
@@ -2712,16 +2712,20 @@ function ExpertToolBlock({ tc }) {
2712
2712
  }
2713
2713
  void submit("up");
2714
2714
  };
2715
- const expertName = parseArgs2(tc.args)?.expert ?? null;
2716
- return /* @__PURE__ */ jsxs("div", { children: [
2715
+ const args = parseArgs2(tc.args) ?? {};
2716
+ const expertName = args.expert ?? null;
2717
+ const question = args.question ?? args.prompt ?? "";
2718
+ const result = parseExpertResult(tc.result);
2719
+ const failed = Boolean(result?.error);
2720
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2717
2721
  /* @__PURE__ */ jsxs(
2718
2722
  "button",
2719
2723
  {
2720
2724
  type: "button",
2721
2725
  onClick: () => setExpanded(!expanded),
2722
2726
  className: cn(
2723
- "flex w-full items-center gap-1.5 rounded-md px-2 py-1",
2724
- "text-sm text-muted-foreground hover:bg-muted/50 transition-colors"
2727
+ "flex w-fit max-w-full items-center gap-1.5 rounded-md px-0 py-0.5",
2728
+ "text-sm text-muted-foreground hover:text-foreground transition-colors"
2725
2729
  ),
2726
2730
  children: [
2727
2731
  /* @__PURE__ */ jsx(
@@ -2733,21 +2737,26 @@ function ExpertToolBlock({ tc }) {
2733
2737
  )
2734
2738
  }
2735
2739
  ),
2736
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground/80", children: "consult_expert" }),
2740
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Consulted expert" }),
2737
2741
  expertName && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
2738
2742
  "\xB7 ",
2739
2743
  expertName
2740
- ] })
2744
+ ] }),
2745
+ failed && /* @__PURE__ */ jsx("span", { className: "text-red-500", children: "\xB7 failed" })
2741
2746
  ]
2742
2747
  }
2743
2748
  ),
2744
- expanded && /* @__PURE__ */ jsxs("div", { className: "ml-6 mt-0.5 space-y-1 text-sm ", children: [
2745
- /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded bg-muted/40 px-2 py-1 text-muted-foreground whitespace-pre-wrap break-all", children: formatArgs(tc.args) }),
2746
- tc.result && /* @__PURE__ */ jsxs("pre", { className: "overflow-x-auto rounded bg-muted/40 px-2 py-1 text-muted-foreground whitespace-pre-wrap break-all max-h-64 overflow-y-auto", children: [
2747
- /* @__PURE__ */ jsx("span", { className: "text-emerald-600 dark:text-emerald-400", children: "Result:" }),
2748
- "\n",
2749
- truncate(tc.result, 2e3)
2750
- ] })
2749
+ result?.summary && !expanded && /* @__PURE__ */ jsx("div", { className: "ml-6 rounded-md border-l-2 border-primary/50 bg-muted/30 px-3 py-2 text-sm text-foreground/90", children: result.summary }),
2750
+ expanded && /* @__PURE__ */ jsxs("div", { className: "ml-6 mt-0.5 space-y-1.5 text-sm", children: [
2751
+ question && /* @__PURE__ */ jsx(ExpertDetail, { label: "Question", content: question }),
2752
+ result?.summary && /* @__PURE__ */ jsx(
2753
+ ExpertDetail,
2754
+ {
2755
+ label: failed ? "Error" : "Response",
2756
+ content: result.summary,
2757
+ tone: failed ? "error" : "default"
2758
+ }
2759
+ )
2751
2760
  ] }),
2752
2761
  canRate && /* @__PURE__ */ jsxs("div", { className: "ml-6 mt-1 space-y-1.5", children: [
2753
2762
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
@@ -2813,6 +2822,36 @@ function ExpertToolBlock({ tc }) {
2813
2822
  ] })
2814
2823
  ] });
2815
2824
  }
2825
+ function parseExpertResult(result) {
2826
+ if (!result) return null;
2827
+ const parsed = parseArgs2(result);
2828
+ if (parsed) {
2829
+ return {
2830
+ ...parsed,
2831
+ summary: parsed.summary ?? parsed.response ?? parsed.content
2832
+ };
2833
+ }
2834
+ return { summary: result };
2835
+ }
2836
+ function ExpertDetail({
2837
+ label,
2838
+ content,
2839
+ tone = "default"
2840
+ }) {
2841
+ return /* @__PURE__ */ jsxs(
2842
+ "div",
2843
+ {
2844
+ className: cn(
2845
+ "rounded-md border-l-2 px-3 py-2",
2846
+ tone === "error" ? "border-red-500/50 bg-red-500/5" : "border-primary/50 bg-muted/30"
2847
+ ),
2848
+ children: [
2849
+ /* @__PURE__ */ jsx("div", { className: "mb-1 text-[10px] uppercase tracking-wide text-muted-foreground/70", children: label }),
2850
+ /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap wrap-break-word text-foreground/90", children: content })
2851
+ ]
2852
+ }
2853
+ );
2854
+ }
2816
2855
  function ReasonForm({
2817
2856
  initialValue,
2818
2857
  busy,
@@ -3438,6 +3477,71 @@ function MemoryEntry({
3438
3477
  }
3439
3478
  );
3440
3479
  }
3480
+ var ACTION_LABEL = {
3481
+ create: "Create skill",
3482
+ patch: "Patch skill",
3483
+ edit: "Edit skill",
3484
+ delete: "Delete skill",
3485
+ write_file: "Write skill file",
3486
+ remove_file: "Remove skill file"
3487
+ };
3488
+ function SkillManageToolBlock({ tc }) {
3489
+ const args = parseArgs2(tc.args) ?? {};
3490
+ const result = tc.result ? parseArgs2(tc.result) : null;
3491
+ const action = args.action ?? "manage";
3492
+ const label = ACTION_LABEL[action] ?? "Manage skill";
3493
+ const target = args.file_path ? `${args.name ?? "?"}/${args.file_path}` : args.name ?? "?";
3494
+ const failed = result?.success === false || Boolean(result?.error);
3495
+ const summary = result?.error ?? result?.message ?? result?.path;
3496
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-sm", children: [
3497
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: label }),
3498
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate", children: target }),
3499
+ summary && /* @__PURE__ */ jsxs("span", { className: failed ? "text-red-500 truncate" : "text-muted-foreground/70 truncate", children: [
3500
+ "\u2192 ",
3501
+ summary
3502
+ ] })
3503
+ ] });
3504
+ }
3505
+ function firstLine2(value) {
3506
+ if (!value) return "";
3507
+ const index = value.indexOf("\n");
3508
+ return (index === -1 ? value : value.slice(0, index)).trim();
3509
+ }
3510
+ function CoordinatorToolBlock({ tc }) {
3511
+ const args = parseArgs2(tc.args) ?? {};
3512
+ const result = tc.result ? parseArgs2(tc.result) : null;
3513
+ const failed = Boolean(result?.error);
3514
+ let label = "Worker";
3515
+ let target = "";
3516
+ let detail = "";
3517
+ if (tc.toolName === "spawn_worker") {
3518
+ label = tc.status === "running" ? "Spawning worker" : "Spawned worker";
3519
+ target = args.agent_type ?? args.model ?? "";
3520
+ detail = firstLine2(args.goal);
3521
+ } else if (tc.toolName === "send_worker_message") {
3522
+ label = "Message worker";
3523
+ target = args.worker_id ?? "";
3524
+ detail = firstLine2(args.message);
3525
+ } else if (tc.toolName === "stop_worker") {
3526
+ label = "Stop worker";
3527
+ target = args.worker_id ?? "";
3528
+ detail = firstLine2(args.reason);
3529
+ }
3530
+ const resultId = result?.worker_id ?? result?.session_id;
3531
+ const summary = result?.error ?? resultId ?? result?.status;
3532
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-sm", children: [
3533
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: label }),
3534
+ target && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate", children: target }),
3535
+ detail && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70 truncate", children: [
3536
+ "\xB7 ",
3537
+ detail
3538
+ ] }),
3539
+ summary && /* @__PURE__ */ jsxs("span", { className: failed ? "text-red-500 truncate" : "text-muted-foreground/70 truncate", children: [
3540
+ "\u2192 ",
3541
+ summary
3542
+ ] })
3543
+ ] });
3544
+ }
3441
3545
  function DefaultToolBlock({ tc }) {
3442
3546
  const [expanded, setExpanded] = useState(false);
3443
3547
  return /* @__PURE__ */ jsxs("div", { children: [
@@ -3506,6 +3610,8 @@ function ToolCallBlock({ tc, onFileSelect }) {
3506
3610
  return /* @__PURE__ */ jsx(SkillsListBlock, { tc });
3507
3611
  case "skill_view":
3508
3612
  return /* @__PURE__ */ jsx(SkillViewBlock, { tc });
3613
+ case "skill_manage":
3614
+ return /* @__PURE__ */ jsx(SkillManageToolBlock, { tc });
3509
3615
  case "clarify":
3510
3616
  return /* @__PURE__ */ jsx(ClarifyToolBlock, { tc });
3511
3617
  case "create_artifact":
@@ -3514,6 +3620,10 @@ function ToolCallBlock({ tc, onFileSelect }) {
3514
3620
  return /* @__PURE__ */ jsx(DelegateToolBlock, { tc });
3515
3621
  case "memory":
3516
3622
  return /* @__PURE__ */ jsx(MemoryToolBlock, { tc });
3623
+ case "spawn_worker":
3624
+ case "send_worker_message":
3625
+ case "stop_worker":
3626
+ return /* @__PURE__ */ jsx(CoordinatorToolBlock, { tc });
3517
3627
  default:
3518
3628
  return /* @__PURE__ */ jsx(DefaultToolBlock, { tc });
3519
3629
  }
@@ -6404,7 +6514,8 @@ function WorkspacePanel({
6404
6514
  adapter,
6405
6515
  sessionId,
6406
6516
  selectedPath,
6407
- onSelectedPathChange
6517
+ onSelectedPathChange,
6518
+ disabled = false
6408
6519
  }) {
6409
6520
  const fileInputRef = useRef(null);
6410
6521
  const [entries, setEntries] = useState([]);
@@ -6523,7 +6634,7 @@ function WorkspacePanel({
6523
6634
  );
6524
6635
  const handleUpload = useCallback(
6525
6636
  async (files) => {
6526
- if (!sessionId || files.length === 0) return;
6637
+ if (disabled || !sessionId || files.length === 0) return;
6527
6638
  setUploading(true);
6528
6639
  setNotice(null);
6529
6640
  try {
@@ -6544,7 +6655,7 @@ function WorkspacePanel({
6544
6655
  if (fileInputRef.current) fileInputRef.current.value = "";
6545
6656
  }
6546
6657
  },
6547
- [adapter, fetchTree, sessionId]
6658
+ [adapter, disabled, fetchTree, sessionId]
6548
6659
  );
6549
6660
  const handleDelete = useCallback(
6550
6661
  async (path) => {
@@ -6618,6 +6729,7 @@ function WorkspacePanel({
6618
6729
  type: "file",
6619
6730
  multiple: true,
6620
6731
  className: "hidden",
6732
+ disabled,
6621
6733
  onChange: (event) => {
6622
6734
  if (event.target.files) void handleUpload(event.target.files);
6623
6735
  }
@@ -6636,7 +6748,7 @@ function WorkspacePanel({
6636
6748
  variant: "ghost",
6637
6749
  size: "icon-sm",
6638
6750
  onClick: () => fileInputRef.current?.click(),
6639
- disabled: !sessionId || uploading,
6751
+ disabled: disabled || !sessionId || uploading,
6640
6752
  "aria-label": "Upload files",
6641
6753
  children: uploading ? /* @__PURE__ */ jsx(Loader2Icon, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(UploadIcon, { className: "size-4" })
6642
6754
  }
@@ -6688,7 +6800,7 @@ function WorkspacePanel({
6688
6800
  size: "sm",
6689
6801
  className: "mt-3 gap-1.5",
6690
6802
  onClick: () => fileInputRef.current?.click(),
6691
- disabled: !sessionId,
6803
+ disabled: disabled || !sessionId,
6692
6804
  children: [
6693
6805
  /* @__PURE__ */ jsx(UploadIcon, { className: "size-3.5" }),
6694
6806
  "Upload files"
@@ -7639,14 +7751,352 @@ function AgentChat({
7639
7751
  adapter,
7640
7752
  sessionId,
7641
7753
  selectedPath: workspacePath,
7642
- onSelectedPathChange: setWorkspacePath
7754
+ onSelectedPathChange: setWorkspacePath,
7755
+ disabled
7643
7756
  }
7644
7757
  )
7645
7758
  ] }) })
7646
7759
  }
7647
7760
  );
7648
7761
  }
7762
+ var POLL_INTERVAL_ACTIVE_MS = 4e3;
7763
+ var POLL_INTERVAL_IDLE_MS = 3e4;
7764
+ var DEFAULT_SESSION_LIST_LIMIT = 50;
7765
+ function buildTree(nodes) {
7766
+ const byId = /* @__PURE__ */ new Map();
7767
+ for (const n of nodes) byId.set(n.id, { ...n, children: [] });
7768
+ const roots = [];
7769
+ for (const n of byId.values()) {
7770
+ const parent = n.parentId ? byId.get(n.parentId) : void 0;
7771
+ if (parent) {
7772
+ parent.children.push(n);
7773
+ } else {
7774
+ roots.push(n);
7775
+ }
7776
+ }
7777
+ const sortRec = (e) => {
7778
+ e.children.sort((a, b) => a.createdAt < b.createdAt ? -1 : 1);
7779
+ for (const c of e.children) sortRec(c);
7780
+ };
7781
+ for (const r of roots) sortRec(r);
7782
+ return roots;
7783
+ }
7784
+ function treeFingerprint(nodes) {
7785
+ return nodes.map(
7786
+ (n) => `${n.id}:${n.parentId ?? ""}:${n.status}:${n.agentType ?? ""}:${n.messageCount ?? 0}:${n.toolCallCount ?? 0}:${n.updatedAt}`
7787
+ ).join("|");
7788
+ }
7789
+ function sessionToTreeNode(session) {
7790
+ const timestamp = session.updatedAt ?? session.createdAt ?? "";
7791
+ return {
7792
+ id: session.id,
7793
+ parentId: session.parentId ?? null,
7794
+ agentId: session.agentId,
7795
+ channel: session.channel,
7796
+ status: session.status,
7797
+ title: session.title,
7798
+ model: session.model,
7799
+ messageCount: session.messageCount,
7800
+ toolCallCount: session.toolCallCount,
7801
+ createdAt: session.createdAt ?? timestamp,
7802
+ updatedAt: session.updatedAt ?? timestamp
7803
+ };
7804
+ }
7805
+ function mergeNodeFields(current, next) {
7806
+ return {
7807
+ ...current,
7808
+ ...Object.fromEntries(
7809
+ Object.entries(next).filter(([, value]) => value !== void 0)
7810
+ ),
7811
+ messageCount: next.messageCount ?? current.messageCount,
7812
+ toolCallCount: next.toolCallCount ?? current.toolCallCount
7813
+ };
7814
+ }
7815
+ function mergeTreeNodes(groups) {
7816
+ const byId = /* @__PURE__ */ new Map();
7817
+ for (const group of groups) {
7818
+ for (const node of group) {
7819
+ const current = byId.get(node.id);
7820
+ byId.set(node.id, current ? mergeNodeFields(current, node) : node);
7821
+ }
7822
+ }
7823
+ return Array.from(byId.values());
7824
+ }
7825
+ function statusColor(status) {
7826
+ switch (status) {
7827
+ case "active":
7828
+ return "default";
7829
+ case "completed":
7830
+ return "secondary";
7831
+ case "failed":
7832
+ return "destructive";
7833
+ default:
7834
+ return "outline";
7835
+ }
7836
+ }
7837
+ function TreeNodeRow({
7838
+ entry,
7839
+ depth,
7840
+ activeSessionId,
7841
+ canStop,
7842
+ canDelete,
7843
+ deletingSessionId,
7844
+ onSelect,
7845
+ onStop,
7846
+ onDelete
7847
+ }) {
7848
+ const [expanded, setExpanded] = useState(true);
7849
+ const hasChildren = entry.children.length > 0;
7850
+ const isActive = entry.id === activeSessionId;
7851
+ const isRunning = entry.status === "active";
7852
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
7853
+ /* @__PURE__ */ jsxs(
7854
+ "div",
7855
+ {
7856
+ role: "button",
7857
+ tabIndex: 0,
7858
+ onClick: () => onSelect(entry.id),
7859
+ onKeyDown: (e) => {
7860
+ if (e.key === "Enter" || e.key === " ") onSelect(entry.id);
7861
+ },
7862
+ className: cn(
7863
+ "group flex items-center gap-1.5 py-1.5 pr-1 rounded cursor-pointer text-sm transition-colors",
7864
+ isActive ? "bg-line text-foreground" : "hover:bg-input text-subtle hover:text-foreground"
7865
+ ),
7866
+ style: { paddingLeft: `${depth * 12 + 4}px` },
7867
+ children: [
7868
+ hasChildren ? /* @__PURE__ */ jsx(
7869
+ "button",
7870
+ {
7871
+ type: "button",
7872
+ className: "p-0.5 rounded hover:bg-line",
7873
+ onClick: (e) => {
7874
+ e.stopPropagation();
7875
+ setExpanded(!expanded);
7876
+ },
7877
+ "aria-label": expanded ? "Collapse" : "Expand",
7878
+ "aria-expanded": expanded,
7879
+ children: expanded ? /* @__PURE__ */ jsx(ChevronDownIcon, { className: "w-3.5 h-3.5" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "w-3.5 h-3.5" })
7880
+ }
7881
+ ) : /* @__PURE__ */ jsx("span", { className: "w-4 h-4 shrink-0" }),
7882
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 flex items-center gap-1.5", children: [
7883
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: entry.title ?? "session" }),
7884
+ entry.agentType && /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "h-4 px-1.5 text-[10px]", children: entry.agentType }),
7885
+ /* @__PURE__ */ jsx(
7886
+ Badge,
7887
+ {
7888
+ variant: statusColor(entry.status),
7889
+ className: "h-4 px-1.5 text-[10px]",
7890
+ children: entry.status
7891
+ }
7892
+ )
7893
+ ] }),
7894
+ isRunning && canStop && /* @__PURE__ */ jsx(
7895
+ "button",
7896
+ {
7897
+ type: "button",
7898
+ className: "p-1 rounded opacity-0 group-hover:opacity-100 hover:bg-destructive/10 hover:text-destructive transition-all",
7899
+ onClick: (e) => {
7900
+ e.stopPropagation();
7901
+ onStop(entry.id);
7902
+ },
7903
+ "aria-label": "Stop sub-agent",
7904
+ title: "Stop sub-agent",
7905
+ children: /* @__PURE__ */ jsx(SquareIcon, { className: "w-3 h-3", fill: "currentColor" })
7906
+ }
7907
+ ),
7908
+ canDelete && /* @__PURE__ */ jsx(
7909
+ "button",
7910
+ {
7911
+ type: "button",
7912
+ className: "p-1 rounded opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-destructive/10 hover:text-destructive disabled:pointer-events-none disabled:opacity-50 transition-all",
7913
+ onClick: (e) => {
7914
+ e.stopPropagation();
7915
+ onDelete(entry.id);
7916
+ },
7917
+ "aria-label": "Delete session",
7918
+ title: "Delete session",
7919
+ disabled: deletingSessionId === entry.id,
7920
+ children: /* @__PURE__ */ jsx(Trash2Icon, { className: "w-3 h-3" })
7921
+ }
7922
+ )
7923
+ ]
7924
+ }
7925
+ ),
7926
+ hasChildren && expanded && entry.children.map((child) => /* @__PURE__ */ jsx(
7927
+ TreeNodeRow,
7928
+ {
7929
+ entry: child,
7930
+ depth: depth + 1,
7931
+ activeSessionId,
7932
+ canStop,
7933
+ canDelete,
7934
+ deletingSessionId,
7935
+ onSelect,
7936
+ onStop,
7937
+ onDelete
7938
+ },
7939
+ child.id
7940
+ ))
7941
+ ] });
7942
+ }
7943
+ function SessionTreePanel({
7944
+ adapter,
7945
+ sessionId = null,
7946
+ activeSessionId = sessionId ?? void 0,
7947
+ agentId,
7948
+ title = "Running",
7949
+ sessionListLimit = DEFAULT_SESSION_LIST_LIMIT,
7950
+ hideRoot = false,
7951
+ onSessionSelect,
7952
+ onSessionDelete
7953
+ }) {
7954
+ const [nodes, setNodes] = useState([]);
7955
+ const [loading, setLoading] = useState(false);
7956
+ const [error, setError] = useState(null);
7957
+ const [hasEverLoaded, setHasEverLoaded] = useState(false);
7958
+ const [deletingSessionId, setDeletingSessionId] = useState(
7959
+ null
7960
+ );
7961
+ const mounted = useRef(true);
7962
+ const requestId = useRef(0);
7963
+ const lastFingerprint = useRef("");
7964
+ const refetch = useCallback(
7965
+ async (opts) => {
7966
+ const canLoadSessionList = Boolean(agentId && adapter.listSessions);
7967
+ const canLoadSessionTree = Boolean(sessionId && adapter.getSessionTree);
7968
+ if (!canLoadSessionList && !canLoadSessionTree) {
7969
+ setNodes([]);
7970
+ setHasEverLoaded(true);
7971
+ setLoading(false);
7972
+ return;
7973
+ }
7974
+ const currentRequestId = ++requestId.current;
7975
+ if (!opts?.silent) setLoading(true);
7976
+ try {
7977
+ const sessionListPromise = agentId && adapter.listSessions ? adapter.listSessions({
7978
+ agentId,
7979
+ limit: sessionListLimit
7980
+ }) : Promise.resolve(null);
7981
+ const sessionTreePromise = sessionId && adapter.getSessionTree ? adapter.getSessionTree({ sessionId }) : Promise.resolve(null);
7982
+ const [sessionList, sessionTree] = await Promise.all([
7983
+ sessionListPromise,
7984
+ sessionTreePromise
7985
+ ]);
7986
+ if (!mounted.current || currentRequestId !== requestId.current) return;
7987
+ const nextNodes = mergeTreeNodes([
7988
+ sessionList?.sessions.map(sessionToTreeNode) ?? [],
7989
+ sessionTree?.nodes ?? []
7990
+ ]);
7991
+ const fp = treeFingerprint(nextNodes);
7992
+ if (fp !== lastFingerprint.current) {
7993
+ lastFingerprint.current = fp;
7994
+ setNodes(nextNodes);
7995
+ }
7996
+ setError(null);
7997
+ setHasEverLoaded(true);
7998
+ } catch (e) {
7999
+ if (!mounted.current || currentRequestId !== requestId.current) return;
8000
+ if (!opts?.silent) {
8001
+ setError(e instanceof Error ? e.message : "Failed to load tree");
8002
+ }
8003
+ } finally {
8004
+ if (mounted.current && currentRequestId === requestId.current && !opts?.silent) {
8005
+ setLoading(false);
8006
+ }
8007
+ }
8008
+ },
8009
+ [adapter, agentId, sessionId, sessionListLimit]
8010
+ );
8011
+ useEffect(() => {
8012
+ mounted.current = true;
8013
+ return () => {
8014
+ mounted.current = false;
8015
+ };
8016
+ }, []);
8017
+ useEffect(() => {
8018
+ setNodes([]);
8019
+ setError(null);
8020
+ setHasEverLoaded(false);
8021
+ lastFingerprint.current = "";
8022
+ void refetch();
8023
+ }, [refetch, sessionId]);
8024
+ const runningCount = useMemo(
8025
+ () => nodes.filter((n) => n.status === "active").length,
8026
+ [nodes]
8027
+ );
8028
+ useEffect(() => {
8029
+ const interval = runningCount > 0 ? POLL_INTERVAL_ACTIVE_MS : POLL_INTERVAL_IDLE_MS;
8030
+ const id = setInterval(() => {
8031
+ void refetch({ silent: true });
8032
+ }, interval);
8033
+ return () => clearInterval(id);
8034
+ }, [refetch, runningCount]);
8035
+ const roots = useMemo(() => buildTree(nodes), [nodes]);
8036
+ const handleSelect = useCallback(
8037
+ (id) => {
8038
+ onSessionSelect?.(id);
8039
+ },
8040
+ [onSessionSelect]
8041
+ );
8042
+ const handleStop = useCallback(
8043
+ async (id) => {
8044
+ if (!adapter.stopSession) return;
8045
+ try {
8046
+ await adapter.stopSession({ sessionId: id });
8047
+ await refetch({ silent: true });
8048
+ } catch (e) {
8049
+ setError(e instanceof Error ? e.message : "Failed to stop sub-agent");
8050
+ }
8051
+ },
8052
+ [adapter, refetch]
8053
+ );
8054
+ const handleDelete = useCallback(
8055
+ async (id) => {
8056
+ if (!adapter.deleteSession || deletingSessionId) return;
8057
+ setDeletingSessionId(id);
8058
+ try {
8059
+ await adapter.deleteSession({ sessionId: id });
8060
+ onSessionDelete?.(id);
8061
+ await refetch({ silent: true });
8062
+ } catch (e) {
8063
+ setError(e instanceof Error ? e.message : "Failed to delete session");
8064
+ } finally {
8065
+ if (mounted.current) setDeletingSessionId(null);
8066
+ }
8067
+ },
8068
+ [adapter, deletingSessionId, onSessionDelete, refetch]
8069
+ );
8070
+ if (!hasEverLoaded) return null;
8071
+ if (nodes.length === 0) return null;
8072
+ const topLevel = hideRoot ? roots.flatMap((r) => r.children) : roots;
8073
+ if (topLevel.length === 0) return null;
8074
+ return /* @__PURE__ */ jsxs("div", { className: "border-t border-line", children: [
8075
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-3 py-2 text-xs font-semibold uppercase tracking-wide text-faint", children: [
8076
+ /* @__PURE__ */ jsx(UsersIcon, { className: "w-3.5 h-3.5" }),
8077
+ /* @__PURE__ */ jsx("span", { children: title }),
8078
+ runningCount > 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", className: "h-4 px-1.5 text-[10px] ml-auto", children: runningCount })
8079
+ ] }),
8080
+ loading && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-faint", children: "Loading..." }),
8081
+ error && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-destructive", children: error }),
8082
+ !error && /* @__PURE__ */ jsx("div", { className: "px-1 pb-2", children: topLevel.map((entry) => /* @__PURE__ */ jsx(
8083
+ TreeNodeRow,
8084
+ {
8085
+ entry,
8086
+ depth: 0,
8087
+ activeSessionId: activeSessionId ?? "",
8088
+ canStop: Boolean(adapter.stopSession),
8089
+ canDelete: Boolean(adapter.deleteSession),
8090
+ deletingSessionId,
8091
+ onSelect: handleSelect,
8092
+ onStop: handleStop,
8093
+ onDelete: handleDelete
8094
+ },
8095
+ entry.id
8096
+ )) })
8097
+ ] });
8098
+ }
7649
8099
 
7650
- export { AgentChat, AgentChatAdapterProvider, useAgentChatAdapterContext, useAgentChatRuntime };
8100
+ export { AgentChat, AgentChatAdapterProvider, MessageResponse, SessionTreePanel, useAgentChatAdapterContext, useAgentChatRuntime };
7651
8101
  //# sourceMappingURL=index.js.map
7652
8102
  //# sourceMappingURL=index.js.map