@invergent/agent-chat-react 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { cn } from './chunk-QSC4UIVT.js';
2
- import { createContext, memo, useRef, useEffect, useCallback, useMemo, useState, lazy, useContext, Fragment as Fragment$1, Suspense, Children } from 'react';
3
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, ChevronRightIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, FolderOpenIcon, Loader2Icon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, CheckIcon, CopyIcon, TerminalIcon, SparklesIcon, ClockIcon, PlusIcon, CloudIcon, HardDriveIcon, PaperclipIcon, GlobeIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, SearchIcon, CircleCheckIcon, ThumbsUpIcon, ThumbsDownIcon, GitBranchIcon, ActivityIcon, EyeIcon, ListTodoIcon, ArrowUpIcon, ListIcon, PenLineIcon, ArrowRight, BookOpenIcon, FileEditIcon, WrenchIcon, FileCodeIcon, CodeIcon, FileArchiveIcon, FileVideoIcon, FileAudioIcon, FileImageIcon, FileChartLineIcon, FileSpreadsheetIcon, XCircleIcon, CheckCircle2Icon, Code, CircleXIcon, SkullIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
2
+ import { createContext, memo, useRef, useEffect, useCallback, useMemo, useState, useLayoutEffect, lazy, useContext, Fragment as Fragment$1, Suspense, Children } from 'react';
3
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, ChevronRightIcon, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, FolderOpenIcon, Loader2Icon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, CheckIcon, CopyIcon, TerminalIcon, SparklesIcon, ClockIcon, PlusIcon, CloudIcon, HardDriveIcon, PaperclipIcon, GlobeIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, SearchIcon, CircleCheckIcon, ThumbsUpIcon, ThumbsDownIcon, BookOpenIcon, GitBranchIcon, ActivityIcon, EyeIcon, ListTodoIcon, ArrowUpIcon, ListIcon, PenLineIcon, ArrowRight, FileEditIcon, WrenchIcon, FileCodeIcon, CodeIcon, FileArchiveIcon, FileVideoIcon, FileAudioIcon, FileImageIcon, FileChartLineIcon, FileSpreadsheetIcon, XCircleIcon, CheckCircle2Icon, Code, CircleXIcon, SkullIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
4
4
  import { cva } from 'class-variance-authority';
5
5
  import { Collapsible as Collapsible$1, Slot, Tooltip as Tooltip$1, Progress as Progress$1, Tabs as Tabs$1, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Popover as Popover$1 } from 'radix-ui';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -2201,6 +2201,37 @@ function MCPToolBlock({ tc }) {
2201
2201
  summary && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 truncate text-xs", children: summary })
2202
2202
  ] });
2203
2203
  }
2204
+ function ResearchMemoryBlock({ tc }) {
2205
+ const args = parseArgs(tc.args) ?? {};
2206
+ const result = tc.result ? parseArgs(tc.result) ?? {} : {};
2207
+ let detail = "";
2208
+ if (args.action === "add") {
2209
+ const host = args.url ? hostname(args.url) : "";
2210
+ const sid = result.source_id ?? "";
2211
+ detail = sid ? `recorded ${sid}${host ? ` \xB7 ${host}` : ""}` : host ? `recorded \xB7 ${host}` : "recorded";
2212
+ } else if (args.action === "retrieve") {
2213
+ const n = result.sources?.length ?? 0;
2214
+ detail = args.query ? `${n} source${n === 1 ? "" : "s"} for "${truncate2(args.query, 40)}"` : `${n} source${n === 1 ? "" : "s"}`;
2215
+ } else if (args.action === "list") {
2216
+ const n = result.sources?.length ?? 0;
2217
+ detail = `${n} source${n === 1 ? "" : "s"}`;
2218
+ }
2219
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm py-0.5", children: [
2220
+ /* @__PURE__ */ jsx(BookOpenIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
2221
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Research" }),
2222
+ detail && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: detail })
2223
+ ] });
2224
+ }
2225
+ function hostname(url) {
2226
+ try {
2227
+ return new URL(url).hostname.replace(/^www\./, "");
2228
+ } catch {
2229
+ return url;
2230
+ }
2231
+ }
2232
+ function truncate2(s, n) {
2233
+ return s.length > n ? `${s.slice(0, n)}\u2026` : s;
2234
+ }
2204
2235
  function VisionAnalyzeBlock({ tc }) {
2205
2236
  let image = "";
2206
2237
  try {
@@ -3898,13 +3929,9 @@ function firstLine(s) {
3898
3929
  return idx === -1 ? s : s.slice(0, idx);
3899
3930
  }
3900
3931
  function DelegateToolBlock({ tc }) {
3901
- const [expanded, setExpanded] = useState(false);
3902
3932
  const args = parseArgs(tc.args);
3903
- const goal = args?.goal ?? "";
3904
- const context = args?.context ?? "";
3933
+ const goal = firstLine(args?.goal ?? "").trim();
3905
3934
  const agentType = args?.agent_type;
3906
- const model = args?.model;
3907
- const summary = firstLine(goal).trim();
3908
3935
  let resultError = null;
3909
3936
  if (tc.result) {
3910
3937
  try {
@@ -3913,84 +3940,29 @@ function DelegateToolBlock({ tc }) {
3913
3940
  } catch {
3914
3941
  }
3915
3942
  }
3916
- return /* @__PURE__ */ jsxs("div", { children: [
3917
- /* @__PURE__ */ jsxs(
3918
- "button",
3943
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm py-0.5", children: [
3944
+ /* @__PURE__ */ jsx(GitBranchIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
3945
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Delegate" }),
3946
+ agentType && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70 text-xs", children: [
3947
+ "\xB7 ",
3948
+ agentType
3949
+ ] }),
3950
+ goal && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70 truncate text-xs italic", children: [
3951
+ "\xB7 \u201C",
3952
+ goal,
3953
+ "\u201D"
3954
+ ] }),
3955
+ resultError && /* @__PURE__ */ jsx(
3956
+ "span",
3919
3957
  {
3920
- type: "button",
3921
- onClick: () => setExpanded(!expanded),
3922
3958
  className: cn(
3923
- "flex w-full items-center gap-1.5 rounded-md px-2 py-1",
3924
- "text-sm text-muted-foreground hover:bg-muted/50 transition-colors"
3959
+ "shrink-0 text-xs",
3960
+ "text-destructive"
3925
3961
  ),
3926
- children: [
3927
- /* @__PURE__ */ jsx(
3928
- ChevronRightIcon,
3929
- {
3930
- className: cn(
3931
- "size-3 shrink-0 transition-transform duration-150",
3932
- expanded && "rotate-90"
3933
- )
3934
- }
3935
- ),
3936
- /* @__PURE__ */ jsx(GitBranchIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
3937
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground/80", children: "delegate_task" }),
3938
- agentType && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
3939
- "\xB7 ",
3940
- agentType
3941
- ] }),
3942
- summary && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground truncate min-w-0", children: [
3943
- "\xB7 ",
3944
- summary
3945
- ] })
3946
- ]
3962
+ title: resultError,
3963
+ children: "\xB7 failed"
3947
3964
  }
3948
- ),
3949
- expanded && /* @__PURE__ */ jsxs("div", { className: "ml-6 mt-0.5 space-y-1.5 text-sm font-mono", children: [
3950
- (agentType || model) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-x-3 gap-y-0.5 text-xs text-muted-foreground", children: [
3951
- agentType && /* @__PURE__ */ jsxs("span", { children: [
3952
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70", children: "agent:" }),
3953
- " ",
3954
- agentType
3955
- ] }),
3956
- model && /* @__PURE__ */ jsxs("span", { children: [
3957
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70", children: "model:" }),
3958
- " ",
3959
- model
3960
- ] })
3961
- ] }),
3962
- goal && /* @__PURE__ */ jsxs("div", { className: "rounded bg-muted/40 px-2 py-1", children: [
3963
- /* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground/70", children: "Goal" }),
3964
- /* @__PURE__ */ jsx("pre", { className: "mt-0.5 whitespace-pre-wrap break-words text-foreground/90", children: goal })
3965
- ] }),
3966
- context && /* @__PURE__ */ jsxs("div", { className: "rounded bg-muted/40 px-2 py-1", children: [
3967
- /* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground/70", children: "Context" }),
3968
- /* @__PURE__ */ jsx("pre", { className: "mt-0.5 whitespace-pre-wrap break-words text-muted-foreground", children: truncate(context, 2e3) })
3969
- ] }),
3970
- tc.status === "running" && !tc.result && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground italic", children: "Sub-agent running\u2026" }),
3971
- tc.result && /* @__PURE__ */ jsxs(
3972
- "div",
3973
- {
3974
- className: cn(
3975
- "rounded px-2 py-1 max-h-64 overflow-y-auto",
3976
- resultError ? "bg-destructive/10" : "bg-muted/40"
3977
- ),
3978
- children: [
3979
- /* @__PURE__ */ jsx(
3980
- "div",
3981
- {
3982
- className: cn(
3983
- "text-[10px] uppercase tracking-wide",
3984
- resultError ? "text-destructive/80" : "text-emerald-600 dark:text-emerald-400"
3985
- ),
3986
- children: resultError ? "Error" : "Sub-agent response"
3987
- }
3988
- ),
3989
- /* @__PURE__ */ jsx("pre", { className: "mt-0.5 whitespace-pre-wrap break-words text-foreground/90", children: truncate(resultError ?? tc.result, 4e3) })
3990
- ]
3991
- }
3992
- )
3993
- ] })
3965
+ )
3994
3966
  ] });
3995
3967
  }
3996
3968
  var ACTION_VERB = {
@@ -4229,38 +4201,6 @@ function ResearchOutlineBlock({ tc }) {
4229
4201
  outline ? /* @__PURE__ */ jsx("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap font-mono text-[11px] leading-snug", children: outline }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "updated" })
4230
4202
  ] });
4231
4203
  }
4232
- function ResearchMemoryBlock({ tc }) {
4233
- const args = parseArgs(tc.args) ?? {};
4234
- const result = tc.result ? parseArgs(tc.result) ?? {} : {};
4235
- let label;
4236
- if (args.action === "add") {
4237
- const host = args.url ? hostname(args.url) : "";
4238
- label = `Recorded source ${result.source_id ?? ""}${host ? ` \xB7 ${host}` : ""}`;
4239
- } else if (args.action === "retrieve") {
4240
- const n = result.sources?.length ?? 0;
4241
- label = `Retrieved ${n} source${n === 1 ? "" : "s"}${args.query ? ` for "${truncate2(args.query)}"` : ""}`;
4242
- } else if (args.action === "list") {
4243
- const n = result.sources?.length ?? 0;
4244
- label = `Listed ${n} source${n === 1 ? "" : "s"}`;
4245
- } else {
4246
- label = "research_memory";
4247
- }
4248
- return /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
4249
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "research" }),
4250
- " ",
4251
- label
4252
- ] });
4253
- }
4254
- function hostname(url) {
4255
- try {
4256
- return new URL(url).hostname.replace(/^www\./, "");
4257
- } catch {
4258
- return url;
4259
- }
4260
- }
4261
- function truncate2(s, n = 40) {
4262
- return s.length > n ? `${s.slice(0, n)}\u2026` : s;
4263
- }
4264
4204
  function ToolCallBlock({
4265
4205
  tc,
4266
4206
  resolvedArtifactName,
@@ -4370,12 +4310,13 @@ function WebSearchGroupBlock({ tcs }) {
4370
4310
  /* @__PURE__ */ jsx("ul", { className: "mt-1 ml-5 space-y-0.5", children: tcs.map((tc) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(WebSearchRow, { tc }) }, tc.id)) })
4371
4311
  ] });
4372
4312
  }
4313
+ var COLLAPSED_MAX_HEIGHT_PX = 160;
4373
4314
  var ChatMessage = memo(function ChatMessage2({
4374
4315
  message,
4375
4316
  onFileSelect
4376
4317
  }) {
4377
4318
  return /* @__PURE__ */ jsx(Message, { from: "user", children: /* @__PURE__ */ jsxs(MessageContent, { children: [
4378
- message.content,
4319
+ /* @__PURE__ */ jsx(CollapsibleMessageBody, { content: message.content }),
4379
4320
  message.images && message.images.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mt-2", children: message.images.map((img, i) => /* @__PURE__ */ jsx(
4380
4321
  "img",
4381
4322
  {
@@ -4395,6 +4336,48 @@ var ChatMessage = memo(function ChatMessage2({
4395
4336
  )) })
4396
4337
  ] }) });
4397
4338
  });
4339
+ function CollapsibleMessageBody({ content }) {
4340
+ const ref = useRef(null);
4341
+ const [overflowing, setOverflowing] = useState(false);
4342
+ const [expanded, setExpanded] = useState(false);
4343
+ useLayoutEffect(() => {
4344
+ const el = ref.current;
4345
+ if (!el) return;
4346
+ const measure = () => {
4347
+ setOverflowing(el.scrollHeight > el.clientHeight + 1);
4348
+ };
4349
+ measure();
4350
+ const observer = new ResizeObserver(measure);
4351
+ observer.observe(el);
4352
+ return () => observer.disconnect();
4353
+ }, [content]);
4354
+ return /* @__PURE__ */ jsxs("div", { children: [
4355
+ /* @__PURE__ */ jsx(
4356
+ "div",
4357
+ {
4358
+ ref,
4359
+ className: cn(
4360
+ "whitespace-pre-wrap wrap-break-word",
4361
+ !expanded && "overflow-hidden"
4362
+ ),
4363
+ style: !expanded ? { maxHeight: COLLAPSED_MAX_HEIGHT_PX } : void 0,
4364
+ children: content
4365
+ }
4366
+ ),
4367
+ (overflowing || expanded) && /* @__PURE__ */ jsx(
4368
+ "button",
4369
+ {
4370
+ type: "button",
4371
+ onClick: () => setExpanded((prev) => !prev),
4372
+ className: cn(
4373
+ "mt-2 text-xs font-bold text-primary",
4374
+ "hover:underline focus:outline-none focus-visible:underline"
4375
+ ),
4376
+ children: expanded ? "Show less..." : "Show more..."
4377
+ }
4378
+ )
4379
+ ] });
4380
+ }
4398
4381
  function iconForMime(mime) {
4399
4382
  if (!mime) return File$1;
4400
4383
  if (mime.startsWith("image/")) return FileImage;
@@ -5860,10 +5843,10 @@ function ChatComposerInner({
5860
5843
  }
5861
5844
  }, [buttonMenuOpen, menuDismissed, textInput.value]);
5862
5845
  useEffect(() => {
5863
- if (textInput.value.startsWith("/")) {
5846
+ if (!buttonMenuOpen && textInput.value.startsWith("/")) {
5864
5847
  setMenuMode("all");
5865
5848
  }
5866
- }, [textInput.value]);
5849
+ }, [buttonMenuOpen, textInput.value]);
5867
5850
  useEffect(() => {
5868
5851
  if (showSlashMenu && (menuMode === "skills" || menuMode === "all")) {
5869
5852
  adapter.listSlashCommands?.().then(setAdapterCommands).catch(() => {
@@ -6035,7 +6018,7 @@ function ChatComposerInner({
6035
6018
  },
6036
6019
  children: [
6037
6020
  /* @__PURE__ */ jsx(AttachmentPreviewStrip, {}),
6038
- viewMode === "expert" && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2 px-1 pb-2", children: [
6021
+ viewMode === "expert" && !disabled && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2 px-1 pb-2", children: [
6039
6022
  /* @__PURE__ */ jsxs(
6040
6023
  Button,
6041
6024
  {
@@ -6110,7 +6093,7 @@ function ChatComposerInner({
6110
6093
  ) }),
6111
6094
  /* @__PURE__ */ jsxs(PromptInputFooter, { children: [
6112
6095
  /* @__PURE__ */ jsxs(PromptInputTools, { children: [
6113
- /* @__PURE__ */ jsxs(Popover, { open: addMenuOpen, onOpenChange: setAddMenuOpen, children: [
6096
+ !disabled && /* @__PURE__ */ jsxs(Popover, { open: addMenuOpen, onOpenChange: setAddMenuOpen, children: [
6114
6097
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(PromptInputButton, { "aria-label": "Add", children: /* @__PURE__ */ jsx(PlusIcon, { className: "size-4" }) }) }),
6115
6098
  /* @__PURE__ */ jsx(
6116
6099
  PopoverContent,
@@ -6182,7 +6165,7 @@ function ChatComposerInner({
6182
6165
  children: /* @__PURE__ */ jsx(FolderIcon, { className: "size-4" })
6183
6166
  }
6184
6167
  ),
6185
- tokenUsage && tokenUsage.contextWindow > 0 && /* @__PURE__ */ jsxs(
6168
+ !disabled && tokenUsage && tokenUsage.contextWindow > 0 && /* @__PURE__ */ jsxs(
6186
6169
  Context,
6187
6170
  {
6188
6171
  usedTokens: tokenUsage.totalTokens,
@@ -6270,7 +6253,7 @@ function ChatComposerInner({
6270
6253
  ]
6271
6254
  }
6272
6255
  ),
6273
- /* @__PURE__ */ jsx(
6256
+ !disabled && /* @__PURE__ */ jsx(
6274
6257
  PromptInputSubmit,
6275
6258
  {
6276
6259
  status,
@@ -6407,6 +6390,80 @@ function ChatComposerInner({
6407
6390
  }
6408
6391
  );
6409
6392
  }
6393
+ function ResearchSourcesPanel({
6394
+ sources
6395
+ }) {
6396
+ const [expanded, setExpanded] = useState(false);
6397
+ const rootRef = useRef(null);
6398
+ useEffect(() => {
6399
+ const root = rootRef.current;
6400
+ if (!root) return;
6401
+ const handler = (event) => {
6402
+ const target = event.target;
6403
+ if (target?.closest('[id^="source-"]')) {
6404
+ setExpanded(true);
6405
+ }
6406
+ };
6407
+ document.addEventListener("scroll-to-source", handler, true);
6408
+ return () => document.removeEventListener("scroll-to-source", handler, true);
6409
+ }, []);
6410
+ if (sources.length === 0) return null;
6411
+ return /* @__PURE__ */ jsxs(
6412
+ "div",
6413
+ {
6414
+ ref: rootRef,
6415
+ className: "rounded-md border border-line bg-card",
6416
+ children: [
6417
+ /* @__PURE__ */ jsxs(
6418
+ "button",
6419
+ {
6420
+ type: "button",
6421
+ onClick: () => setExpanded((prev) => !prev),
6422
+ "aria-expanded": expanded,
6423
+ className: "flex w-full items-center gap-1.5 px-2 py-1.5 text-left hover:bg-muted/40",
6424
+ children: [
6425
+ /* @__PURE__ */ jsx(
6426
+ ChevronRightIcon,
6427
+ {
6428
+ className: cn(
6429
+ "size-3 shrink-0 text-muted-foreground transition-transform duration-150",
6430
+ expanded && "rotate-90"
6431
+ )
6432
+ }
6433
+ ),
6434
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: [
6435
+ "Sources \xB7 ",
6436
+ sources.length
6437
+ ] })
6438
+ ]
6439
+ }
6440
+ ),
6441
+ expanded && /* @__PURE__ */ jsx("ul", { className: "max-h-35 overflow-y-auto border-t border-line py-1", children: sources.map((s) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
6442
+ "a",
6443
+ {
6444
+ id: `source-${s.sourceId}`,
6445
+ href: s.url,
6446
+ target: "_blank",
6447
+ rel: "noreferrer",
6448
+ className: "group flex items-baseline gap-2 px-2 py-1 hover:bg-muted/60",
6449
+ children: [
6450
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] font-semibold text-primary tabular-nums", children: s.sourceId }),
6451
+ /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-foreground group-hover:underline", children: s.title || s.url }),
6452
+ /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 truncate text-[10px] text-muted-foreground max-w-[40%]", children: hostname2(s.url) })
6453
+ ]
6454
+ }
6455
+ ) }, s.sourceId)) })
6456
+ ]
6457
+ }
6458
+ );
6459
+ }
6460
+ function hostname2(url) {
6461
+ try {
6462
+ return new URL(url).hostname.replace(/^www\./, "");
6463
+ } catch {
6464
+ return url;
6465
+ }
6466
+ }
6410
6467
  var MAX_REASON_LENGTH2 = 500;
6411
6468
  function TurnFeedback({ msg }) {
6412
6469
  const { adapter, sessionId } = useAgentChatAdapterContext();
@@ -6779,8 +6836,20 @@ var ArtifactContent = ({
6779
6836
  className,
6780
6837
  ...props
6781
6838
  }) => /* @__PURE__ */ jsx("div", { className: cn("flex-1 overflow-auto p-4", className), ...props });
6782
- function ArtifactMarkdown({ spec }) {
6783
- return /* @__PURE__ */ jsx(MessageResponse, { children: spec.content ?? "" });
6839
+ function ArtifactMarkdown({
6840
+ spec,
6841
+ fill = false
6842
+ }) {
6843
+ return /* @__PURE__ */ jsx(
6844
+ "div",
6845
+ {
6846
+ className: cn(
6847
+ "px-4 py-3",
6848
+ !fill && "max-h-112 overflow-y-auto"
6849
+ ),
6850
+ children: /* @__PURE__ */ jsx(MessageResponse, { children: spec.content ?? "" })
6851
+ }
6852
+ );
6784
6853
  }
6785
6854
  function formatCell(value) {
6786
6855
  if (value == null) return "";
@@ -7150,7 +7219,7 @@ function ArtifactBody({
7150
7219
  }
7151
7220
  switch (payload.kind) {
7152
7221
  case "markdown":
7153
- return /* @__PURE__ */ jsx(ArtifactMarkdown, { spec: payload.spec });
7222
+ return /* @__PURE__ */ jsx(ArtifactMarkdown, { spec: payload.spec, fill });
7154
7223
  case "table":
7155
7224
  return /* @__PURE__ */ jsx(ArtifactTable, { spec: payload.spec });
7156
7225
  case "chart":
@@ -8043,17 +8112,15 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
8043
8112
  }];
8044
8113
  }
8045
8114
  if (msg.systemKind === "artifact") {
8046
- const { artifactId, name, kind, version } = unpackArtifactMeta(
8047
- msg.systemMeta,
8048
- msg.content
8049
- );
8115
+ const { artifactId, name, kind, version, originatingSessionId } = unpackArtifactMeta(msg.systemMeta, msg.content);
8050
8116
  return [{
8051
8117
  kind: "artifact",
8052
8118
  key: msg.id,
8053
8119
  artifactId,
8054
8120
  name,
8055
8121
  artifactKind: kind,
8056
- version
8122
+ version,
8123
+ originatingSessionId
8057
8124
  }];
8058
8125
  }
8059
8126
  if (msg.systemKind === "browser_marker" || msg.systemKind === "browser_marker_warning") {
@@ -8225,7 +8292,8 @@ function unpackArtifactMeta(systemMeta, fallbackName) {
8225
8292
  artifactId: meta.artifact_id ?? "",
8226
8293
  name: meta.name ?? fallbackName,
8227
8294
  kind: meta.kind ?? "markdown",
8228
- version: meta.version ?? 1
8295
+ version: meta.version ?? 1,
8296
+ originatingSessionId: meta.originating_session_id ?? null
8229
8297
  };
8230
8298
  }
8231
8299
  function OrphanSystemMarker({
@@ -8251,7 +8319,7 @@ function OrphanSystemMarker({
8251
8319
  return /* @__PURE__ */ jsx(
8252
8320
  ArtifactBlock,
8253
8321
  {
8254
- sessionId,
8322
+ sessionId: unpacked.originatingSessionId ?? sessionId,
8255
8323
  artifactId: unpacked.artifactId,
8256
8324
  name: unpacked.name,
8257
8325
  kind: unpacked.kind,
@@ -8284,25 +8352,32 @@ function OrphanSystemMarker({
8284
8352
  function cancelledToolLabel(toolName) {
8285
8353
  const map = {
8286
8354
  terminal: "Command",
8287
- execute_code: "Execute Code",
8355
+ execute_code: "Execute code",
8288
8356
  read_file: "Read",
8289
8357
  write_file: "Write",
8290
8358
  patch: "Patch",
8291
- search_files: "Search Files",
8292
- list_files: "List Files",
8293
- web_search: "Web Search",
8294
- web_extract: "Web Fetch",
8295
- web_crawl: "Web Crawl",
8296
- session_search: "Session Search",
8359
+ search_files: "Search files",
8360
+ list_files: "List files",
8361
+ web_search: "Web search",
8362
+ web_extract: "Web fetch",
8363
+ web_crawl: "Web crawl",
8364
+ session_search: "Session search",
8297
8365
  memory: "Memory",
8298
8366
  todo: "Todo",
8299
8367
  skills_list: "Skills",
8300
8368
  skill_view: "Skill",
8301
8369
  consult_expert: "Expert",
8302
- delegate_task: "Delegate",
8370
+ delegate_task: "Delegate task",
8303
8371
  ask_user_question: "Ask User Question",
8304
8372
  process: "Process",
8305
- create_artifact: "Artifact"
8373
+ create_artifact: "Create artifact",
8374
+ // ``Research memory`` reads as a noun ("memory of research") and
8375
+ // makes the shimmer awkward ("Running Research memory…"). The
8376
+ // one-liner renderer shows just ``Research`` as the label, so
8377
+ // mirror that here so the live shimmer matches the row that
8378
+ // replaces it on completion.
8379
+ research_memory: "Research",
8380
+ research_outline: "Research"
8306
8381
  };
8307
8382
  return map[toolName] ?? toolName;
8308
8383
  }
@@ -8431,7 +8506,7 @@ function TimelineEntryItem({
8431
8506
  /* @__PURE__ */ jsx(TimelineContent, { children: sessionId ? /* @__PURE__ */ jsx(
8432
8507
  ArtifactBlock,
8433
8508
  {
8434
- sessionId,
8509
+ sessionId: entry.originatingSessionId ?? sessionId,
8435
8510
  artifactId: entry.artifactId,
8436
8511
  name: entry.name,
8437
8512
  kind: entry.artifactKind,
@@ -8572,8 +8647,15 @@ function extractToolDetail(tc) {
8572
8647
  case "web_search":
8573
8648
  case "web_crawl":
8574
8649
  return stringArg("query");
8575
- case "web_extract":
8576
- return stringArg("url");
8650
+ case "web_extract": {
8651
+ const url = stringArg("url");
8652
+ if (!url) return null;
8653
+ try {
8654
+ return new URL(url).hostname.replace(/^www\./, "");
8655
+ } catch {
8656
+ return truncate3(url, 40);
8657
+ }
8658
+ }
8577
8659
  case "skill_view":
8578
8660
  case "skill_manage":
8579
8661
  return stringArg("name") ?? stringArg("skill");
@@ -8583,6 +8665,40 @@ function extractToolDetail(tc) {
8583
8665
  return stringArg("name") ?? stringArg("task") ?? stringArg("title");
8584
8666
  case "memory":
8585
8667
  return stringArg("action") ?? stringArg("key");
8668
+ case "research_memory": {
8669
+ const action = stringArg("action");
8670
+ if (action === "add") {
8671
+ const title = stringArg("title");
8672
+ if (title) return truncate3(title, 60);
8673
+ const url = stringArg("url");
8674
+ if (url) {
8675
+ try {
8676
+ return new URL(url).hostname.replace(/^www\./, "");
8677
+ } catch {
8678
+ return truncate3(url, 40);
8679
+ }
8680
+ }
8681
+ return null;
8682
+ }
8683
+ if (action === "retrieve") {
8684
+ return stringArg("query");
8685
+ }
8686
+ return action;
8687
+ }
8688
+ case "research_outline": {
8689
+ const action = stringArg("action");
8690
+ if (action === "set") {
8691
+ const outline = stringArg("outline");
8692
+ if (outline) {
8693
+ const sections = outline.split(/\r?\n/).filter((line) => /^#{2,6}\s+\S/.test(line)).length;
8694
+ if (sections > 0) {
8695
+ return `${sections} ${sections === 1 ? "section" : "sections"}`;
8696
+ }
8697
+ }
8698
+ return "outline";
8699
+ }
8700
+ return action;
8701
+ }
8586
8702
  default:
8587
8703
  return null;
8588
8704
  }
@@ -8668,7 +8784,9 @@ var _TOOL_ROW_ICON = {
8668
8784
  delegate_task: ArrowRight,
8669
8785
  create_artifact: FileTextIcon,
8670
8786
  memory: PenLineIcon,
8671
- todo: ListIcon
8787
+ todo: ListIcon,
8788
+ research_memory: BookOpenIcon,
8789
+ research_outline: ListIcon
8672
8790
  };
8673
8791
  function _toolRowIcon(toolName) {
8674
8792
  return _TOOL_ROW_ICON[toolName] ?? WrenchIcon;
@@ -8714,6 +8832,31 @@ function _toolRowLabel(tc) {
8714
8832
  return detail ? `Memory ${detail}` : "Updated memory";
8715
8833
  case "todo":
8716
8834
  return detail ? `Todo ${detail}` : "Updated todo list";
8835
+ case "research_memory": {
8836
+ const args = parseArgs(tc.args);
8837
+ const action = args?.action;
8838
+ if (action === "add") {
8839
+ return detail ? `Stored source "${detail}"` : "Stored a source";
8840
+ }
8841
+ if (action === "retrieve") {
8842
+ return detail ? `Retrieved sources for "${detail}"` : "Retrieved sources";
8843
+ }
8844
+ if (action === "list") {
8845
+ return "Listed sources";
8846
+ }
8847
+ return "Updated research memory";
8848
+ }
8849
+ case "research_outline": {
8850
+ const args = parseArgs(tc.args);
8851
+ const action = args?.action;
8852
+ if (action === "set") {
8853
+ return detail ? `Updated outline (${detail})` : "Updated outline";
8854
+ }
8855
+ if (action === "get") {
8856
+ return "Read outline";
8857
+ }
8858
+ return "Touched outline";
8859
+ }
8717
8860
  default:
8718
8861
  return cancelledToolLabel(tc.toolName);
8719
8862
  }
@@ -8887,7 +9030,8 @@ function SimpleAssistantGroup({
8887
9030
  sessionId,
8888
9031
  artifactFallbacks,
8889
9032
  onFileSelect,
8890
- onRetry
9033
+ onRetry,
9034
+ hideTurnSummary = false
8891
9035
  }) {
8892
9036
  const assistantMessages = messages.filter((m) => m.role === "assistant");
8893
9037
  const tail = assistantMessages[assistantMessages.length - 1];
@@ -8955,7 +9099,7 @@ function SimpleAssistantGroup({
8955
9099
  tail
8956
9100
  }
8957
9101
  ),
8958
- effectiveTurnSummary && /* @__PURE__ */ jsx(
9102
+ effectiveTurnSummary && !hideTurnSummary && /* @__PURE__ */ jsx(
8959
9103
  TurnSummaryCard,
8960
9104
  {
8961
9105
  summary: effectiveTurnSummary,
@@ -8976,7 +9120,8 @@ function AssistantGroup({
8976
9120
  artifactFallbacks,
8977
9121
  onFileSelect,
8978
9122
  onRetry,
8979
- viewMode = "simple"
9123
+ viewMode = "simple",
9124
+ hideTurnSummary = false
8980
9125
  }) {
8981
9126
  if (viewMode === "simple") {
8982
9127
  return /* @__PURE__ */ jsx(
@@ -8987,7 +9132,8 @@ function AssistantGroup({
8987
9132
  sessionId,
8988
9133
  artifactFallbacks,
8989
9134
  onFileSelect,
8990
- onRetry
9135
+ onRetry,
9136
+ hideTurnSummary
8991
9137
  }
8992
9138
  );
8993
9139
  }
@@ -9094,7 +9240,9 @@ function ChatThread({
9094
9240
  canShowWorkspace = false,
9095
9241
  viewMode = "simple",
9096
9242
  onViewModeChange,
9097
- deepResearchEnabled = false
9243
+ deepResearchEnabled = false,
9244
+ researchSources = [],
9245
+ hideTurnSummary = false
9098
9246
  }) {
9099
9247
  const groups = useMemo(() => groupMessages(messages), [messages]);
9100
9248
  const awaitingInput = useMemo(() => isAwaitingUserInput(messages), [messages]);
@@ -9192,7 +9340,8 @@ function ChatThread({
9192
9340
  artifactFallbacks,
9193
9341
  onFileSelect,
9194
9342
  onRetry: groupRetry,
9195
- viewMode
9343
+ viewMode,
9344
+ hideTurnSummary
9196
9345
  },
9197
9346
  group.messages[0].id
9198
9347
  );
@@ -9203,14 +9352,8 @@ function ChatThread({
9203
9352
  ] }),
9204
9353
  /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
9205
9354
  retryIndicator && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(RetryBanner, { indicator: retryIndicator }) }),
9206
- composerDisabled && composerDisabledReason ? /* @__PURE__ */ jsx(
9207
- "div",
9208
- {
9209
- className: "rounded border border-line bg-muted/40 px-3 py-2 text-sm text-foreground/60",
9210
- role: "status",
9211
- children: composerDisabledReason
9212
- }
9213
- ) : /* @__PURE__ */ jsx(
9355
+ researchSources.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(ResearchSourcesPanel, { sources: researchSources }) }),
9356
+ /* @__PURE__ */ jsx(
9214
9357
  ChatComposer,
9215
9358
  {
9216
9359
  onSend,
@@ -9234,44 +9377,6 @@ function ChatThread({
9234
9377
  ] })
9235
9378
  ] });
9236
9379
  }
9237
- function ResearchSourcesPanel({
9238
- sources
9239
- }) {
9240
- if (sources.length === 0) {
9241
- return /* @__PURE__ */ jsx("div", { className: "p-3 text-xs text-muted-foreground", children: "No research sources yet. Sources appear here as the deep-research agent curates evidence." });
9242
- }
9243
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 p-2", children: [
9244
- /* @__PURE__ */ jsxs("div", { className: "px-1 pb-1 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: [
9245
- "Sources \xB7 ",
9246
- sources.length
9247
- ] }),
9248
- sources.map((s) => /* @__PURE__ */ jsxs(
9249
- "a",
9250
- {
9251
- id: `source-${s.sourceId}`,
9252
- href: s.url,
9253
- target: "_blank",
9254
- rel: "noreferrer",
9255
- className: "group flex items-baseline gap-2 rounded-sm px-1 py-1 hover:bg-muted",
9256
- children: [
9257
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] font-semibold text-primary", children: s.sourceId }),
9258
- /* @__PURE__ */ jsxs("span", { className: "flex flex-col overflow-hidden", children: [
9259
- /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-foreground group-hover:underline", children: s.title || s.url }),
9260
- /* @__PURE__ */ jsx("span", { className: "truncate text-[10px] text-muted-foreground", children: hostname2(s.url) })
9261
- ] })
9262
- ]
9263
- },
9264
- s.sourceId
9265
- ))
9266
- ] });
9267
- }
9268
- function hostname2(url) {
9269
- try {
9270
- return new URL(url).hostname.replace(/^www\./, "");
9271
- } catch {
9272
- return url;
9273
- }
9274
- }
9275
9380
  var noop = () => {
9276
9381
  };
9277
9382
  var FileTreeContext = createContext({
@@ -9940,6 +10045,29 @@ function isScheduledRunSession(session) {
9940
10045
  session?.channel === "scheduled" || session?.config?.scheduled_session_id
9941
10046
  );
9942
10047
  }
10048
+ var _SUB_AGENT_CHANNELS = /* @__PURE__ */ new Set([
10049
+ "delegation",
10050
+ "worker",
10051
+ "task"
10052
+ ]);
10053
+ function isSubAgentSession(session) {
10054
+ if (!session) return false;
10055
+ if (session.parentId) return true;
10056
+ if (session.channel && _SUB_AGENT_CHANNELS.has(session.channel)) return true;
10057
+ return false;
10058
+ }
10059
+ function readOnlyReasonForSession(session) {
10060
+ if (isScheduledRunSession(session)) {
10061
+ return { readOnly: true, reason: "Scheduled run is read-only" };
10062
+ }
10063
+ if (isSubAgentSession(session)) {
10064
+ return {
10065
+ readOnly: true,
10066
+ reason: "Sub-task started by parent session \u2014 read-only"
10067
+ };
10068
+ }
10069
+ return { readOnly: false };
10070
+ }
9943
10071
 
9944
10072
  // src/runtime/events.ts
9945
10073
  var WORKSPACE_MUTATING_TOOLS = /* @__PURE__ */ new Set([
@@ -10062,7 +10190,13 @@ function applyAgentChatEvent(state, event) {
10062
10190
  name: event.data.name,
10063
10191
  kind: event.data.kind,
10064
10192
  version: event.data.version,
10065
- size: event.data.size
10193
+ size: event.data.size,
10194
+ // Propagated artifacts (a delegated child's report
10195
+ // surfacing on the parent's chat thread) carry this
10196
+ // field; without it the ArtifactBlock fetches via the
10197
+ // chat's own session id and 404s because the spec lives
10198
+ // under the writer's S3 prefix, not the parent's.
10199
+ originating_session_id: event.data.originating_session_id
10066
10200
  }
10067
10201
  }
10068
10202
  ]);
@@ -10449,7 +10583,7 @@ function applyLlmThinking(state, event) {
10449
10583
  } else {
10450
10584
  messages[idx] = {
10451
10585
  ...prev,
10452
- reasoning: (prev.reasoning ?? "") + reasoningText,
10586
+ reasoning: appendText(prev.reasoning, reasoningText),
10453
10587
  turnId: turnId ?? prev.turnId,
10454
10588
  iterationIndex: iterationIndex ?? prev.iterationIndex
10455
10589
  };
@@ -11384,16 +11518,24 @@ function AgentChat({
11384
11518
  useEffect(() => {
11385
11519
  setShowWorkspace(runtime.viewMode === "expert");
11386
11520
  }, [runtime.viewMode]);
11387
- const isReadOnlySession = isScheduledRunSession(runtime.session);
11388
- const effectiveDisabled = disabled || isReadOnlySession;
11389
- const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
11521
+ const readOnly = readOnlyReasonForSession(runtime.session);
11522
+ const effectiveDisabled = disabled || readOnly.readOnly;
11523
+ const disabledReason = readOnly.reason;
11524
+ const orchestratesDeepResearch = useMemo(
11525
+ () => runtime.messages.some(
11526
+ (m) => m.toolCalls?.some(
11527
+ (tc) => tc.toolName === "delegate_task" && delegateTaskTargets(tc.args).includes("deep-research")
11528
+ )
11529
+ ),
11530
+ [runtime.messages]
11531
+ );
11532
+ const hideTurnSummary = isSubAgentSession(runtime.session) || orchestratesDeepResearch;
11390
11533
  const browserState = runtime.state.browser;
11391
11534
  const browserAvailable = browserState !== null && browserState.status !== "closed" && !!sessionId;
11392
11535
  const browserVisible = browserAvailable && showBrowser;
11393
11536
  const workspaceAvailable = !!sessionId;
11394
11537
  const workspaceVisible = workspaceAvailable && showWorkspace;
11395
- const sourcesVisible = runtime.researchSources.length > 0;
11396
- const rightStackVisible = browserVisible || workspaceVisible || sourcesVisible;
11538
+ const rightStackVisible = browserVisible || workspaceVisible;
11397
11539
  const bothPanesVisible = browserVisible && workspaceVisible;
11398
11540
  useEffect(() => {
11399
11541
  onMessagesChange?.(runtime.messages);
@@ -11509,7 +11651,9 @@ function AgentChat({
11509
11651
  canShowWorkspace: workspaceAvailable,
11510
11652
  viewMode: runtime.viewMode,
11511
11653
  onViewModeChange: runtime.setViewMode,
11512
- deepResearchEnabled
11654
+ deepResearchEnabled,
11655
+ researchSources: runtime.researchSources,
11656
+ hideTurnSummary
11513
11657
  }
11514
11658
  )
11515
11659
  }
@@ -11569,20 +11713,6 @@ function AgentChat({
11569
11713
  }
11570
11714
  )
11571
11715
  }
11572
- ),
11573
- sourcesVisible && /* @__PURE__ */ jsx(
11574
- "div",
11575
- {
11576
- "data-testid": "research-sources-panel-frame",
11577
- className: cn(
11578
- "min-h-0 w-full overflow-auto",
11579
- // When other panes share the stack the sources
11580
- // panel takes a fixed minimum so the source rows
11581
- // stay readable; otherwise it fills the column.
11582
- browserVisible || workspaceVisible ? "max-h-1/3 border-t border-line" : "h-full"
11583
- ),
11584
- children: /* @__PURE__ */ jsx(ResearchSourcesPanel, { sources: runtime.researchSources })
11585
- }
11586
11716
  )
11587
11717
  ]
11588
11718
  }
@@ -11593,6 +11723,27 @@ function AgentChat({
11593
11723
  }
11594
11724
  );
11595
11725
  }
11726
+ function delegateTaskTargets(rawArgs) {
11727
+ if (!rawArgs) return [];
11728
+ let parsed;
11729
+ try {
11730
+ parsed = JSON.parse(rawArgs);
11731
+ } catch {
11732
+ return [];
11733
+ }
11734
+ if (!parsed || typeof parsed !== "object") return [];
11735
+ const out = [];
11736
+ const a = parsed;
11737
+ if (typeof a.agent_type === "string") out.push(a.agent_type);
11738
+ if (Array.isArray(a.goals)) {
11739
+ for (const g of a.goals) {
11740
+ if (g && typeof g === "object" && typeof g.agent_type === "string") {
11741
+ out.push(g.agent_type);
11742
+ }
11743
+ }
11744
+ }
11745
+ return out;
11746
+ }
11596
11747
  var DEFAULT_LIMIT = 50;
11597
11748
  function requireInboxAdapter(adapter) {
11598
11749
  if (!adapter.listInbox || !adapter.getInboxItem || !adapter.markInboxItemRead || !adapter.acknowledgeInboxItem || !adapter.respondGovernanceInboxItem || !adapter.openInboxStream) {
@@ -13738,7 +13889,7 @@ function mergeTreeNodes(groups) {
13738
13889
  }
13739
13890
  return Array.from(byId.values());
13740
13891
  }
13741
- function pruneDeletedSessionNodes(nodes, deletedSessionId) {
13892
+ function collectDeletedSessionIds(nodes, deletedSessionId) {
13742
13893
  const deletedIds = /* @__PURE__ */ new Set([deletedSessionId]);
13743
13894
  let changed = true;
13744
13895
  while (changed) {
@@ -13750,7 +13901,7 @@ function pruneDeletedSessionNodes(nodes, deletedSessionId) {
13750
13901
  }
13751
13902
  }
13752
13903
  }
13753
- return nodes.filter((node) => !deletedIds.has(node.id));
13904
+ return deletedIds;
13754
13905
  }
13755
13906
  function formatSessionTime(value) {
13756
13907
  const normalized = /[zZ]|[+-]\d{2}:?\d{2}$/.test(value) ? value : `${value}Z`;
@@ -13781,7 +13932,6 @@ function TreeNodeRow({
13781
13932
  activeSessionId,
13782
13933
  canStop,
13783
13934
  canDelete,
13784
- deletingSessionId,
13785
13935
  onSelect,
13786
13936
  onStop,
13787
13937
  onDelete
@@ -13849,18 +13999,14 @@ function TreeNodeRow({
13849
13999
  "button",
13850
14000
  {
13851
14001
  type: "button",
13852
- className: cn(
13853
- "p-2 md:p-1 rounded hover:bg-destructive/10 hover:text-destructive disabled:pointer-events-none transition-all",
13854
- deletingSessionId === entry.id ? "opacity-100" : "opacity-60 md:opacity-50 md:group-hover:opacity-100 md:focus-visible:opacity-100"
13855
- ),
14002
+ className: "p-2 md:p-1 rounded opacity-60 md:opacity-50 md:group-hover:opacity-100 md:focus-visible:opacity-100 hover:bg-destructive/10 hover:text-destructive transition-all",
13856
14003
  onClick: (e) => {
13857
14004
  e.stopPropagation();
13858
14005
  onDelete(entry.id);
13859
14006
  },
13860
14007
  "aria-label": "Delete session",
13861
14008
  title: "Delete session",
13862
- disabled: deletingSessionId === entry.id,
13863
- children: deletingSessionId === entry.id ? /* @__PURE__ */ jsx(Loader2Icon, { className: "w-3 h-3 animate-spin" }) : /* @__PURE__ */ jsx(Trash2Icon, { className: "w-3 h-3" })
14009
+ children: /* @__PURE__ */ jsx(Trash2Icon, { className: "w-3 h-3" })
13864
14010
  }
13865
14011
  )
13866
14012
  ]
@@ -13874,7 +14020,6 @@ function TreeNodeRow({
13874
14020
  activeSessionId,
13875
14021
  canStop,
13876
14022
  canDelete,
13877
- deletingSessionId,
13878
14023
  onSelect,
13879
14024
  onStop,
13880
14025
  onDelete
@@ -13899,10 +14044,8 @@ function SessionTreePanel({
13899
14044
  const [nodes, setNodes] = useState([]);
13900
14045
  const [error, setError] = useState(null);
13901
14046
  const [hasEverLoaded, setHasEverLoaded] = useState(false);
13902
- const [deletingSessionId, setDeletingSessionId] = useState(
13903
- null
13904
- );
13905
14047
  const mounted = useRef(true);
14048
+ const pendingDeleteIds = useRef(/* @__PURE__ */ new Set());
13906
14049
  const requestId = useRef(0);
13907
14050
  const lastFingerprint = useRef("");
13908
14051
  const resetContext = useRef(null);
@@ -13927,10 +14070,23 @@ function SessionTreePanel({
13927
14070
  sessionTreePromise
13928
14071
  ]);
13929
14072
  if (!mounted.current || currentRequestId !== requestId.current) return;
13930
- const nextNodes = mergeTreeNodes([
14073
+ let nextNodes = mergeTreeNodes([
13931
14074
  sessionList?.sessions.map(sessionToTreeNode) ?? [],
13932
14075
  sessionTree?.nodes ?? []
13933
14076
  ]);
14077
+ if (pendingDeleteIds.current.size > 0) {
14078
+ const stillReturned = /* @__PURE__ */ new Set();
14079
+ nextNodes = nextNodes.filter((node) => {
14080
+ if (!pendingDeleteIds.current.has(node.id)) return true;
14081
+ stillReturned.add(node.id);
14082
+ return false;
14083
+ });
14084
+ for (const pendingId of pendingDeleteIds.current) {
14085
+ if (!stillReturned.has(pendingId)) {
14086
+ pendingDeleteIds.current.delete(pendingId);
14087
+ }
14088
+ }
14089
+ }
13934
14090
  const fp = treeFingerprint(nextNodes);
13935
14091
  if (fp !== lastFingerprint.current) {
13936
14092
  lastFingerprint.current = fp;
@@ -13961,6 +14117,7 @@ function SessionTreePanel({
13961
14117
  setNodes([]);
13962
14118
  setHasEverLoaded(false);
13963
14119
  lastFingerprint.current = "";
14120
+ pendingDeleteIds.current.clear();
13964
14121
  }
13965
14122
  setError(null);
13966
14123
  void refetch();
@@ -13997,24 +14154,31 @@ function SessionTreePanel({
13997
14154
  );
13998
14155
  const handleDelete = useCallback(
13999
14156
  async (id) => {
14000
- if (!adapter.deleteSession || deletingSessionId) return;
14001
- setDeletingSessionId(id);
14157
+ if (!adapter.deleteSession) return;
14158
+ const deletedIds = collectDeletedSessionIds(nodes, id);
14159
+ for (const deletedId of deletedIds) {
14160
+ pendingDeleteIds.current.add(deletedId);
14161
+ }
14162
+ setNodes((current) => {
14163
+ const next = current.filter((node) => !deletedIds.has(node.id));
14164
+ lastFingerprint.current = treeFingerprint(next);
14165
+ return next;
14166
+ });
14167
+ onSessionDelete?.(id);
14002
14168
  try {
14003
14169
  await adapter.deleteSession({ sessionId: id });
14004
- setNodes((current) => {
14005
- const next = pruneDeletedSessionNodes(current, id);
14006
- lastFingerprint.current = treeFingerprint(next);
14007
- return next;
14008
- });
14009
- onSessionDelete?.(id);
14010
14170
  await refetch({ silent: true });
14011
14171
  } catch (e) {
14012
- setError(e instanceof Error ? e.message : "Failed to delete session");
14013
- } finally {
14014
- if (mounted.current) setDeletingSessionId(null);
14172
+ for (const deletedId of deletedIds) {
14173
+ pendingDeleteIds.current.delete(deletedId);
14174
+ }
14175
+ if (mounted.current) {
14176
+ setError(e instanceof Error ? e.message : "Failed to delete session");
14177
+ }
14178
+ await refetch({ silent: true });
14015
14179
  }
14016
14180
  },
14017
- [adapter, deletingSessionId, onSessionDelete, refetch]
14181
+ [adapter, nodes, onSessionDelete, refetch]
14018
14182
  );
14019
14183
  if (!hasEverLoaded) return null;
14020
14184
  if (nodes.length === 0) return null;
@@ -14035,7 +14199,6 @@ function SessionTreePanel({
14035
14199
  activeSessionId: activeSessionId ?? "",
14036
14200
  canStop: Boolean(adapter.stopSession),
14037
14201
  canDelete: Boolean(adapter.deleteSession),
14038
- deletingSessionId,
14039
14202
  onSelect: handleSelect,
14040
14203
  onStop: handleStop,
14041
14204
  onDelete: handleDelete