@invergent/agent-chat-react 2.5.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2428,6 +2428,37 @@ function MCPToolBlock({ tc }) {
2428
2428
  summary && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/60 truncate text-xs", children: summary })
2429
2429
  ] });
2430
2430
  }
2431
+ function ResearchMemoryBlock({ tc }) {
2432
+ const args = parseArgs(tc.args) ?? {};
2433
+ const result = tc.result ? parseArgs(tc.result) ?? {} : {};
2434
+ let detail = "";
2435
+ if (args.action === "add") {
2436
+ const host = args.url ? hostname(args.url) : "";
2437
+ const sid = result.source_id ?? "";
2438
+ detail = sid ? `recorded ${sid}${host ? ` \xB7 ${host}` : ""}` : host ? `recorded \xB7 ${host}` : "recorded";
2439
+ } else if (args.action === "retrieve") {
2440
+ const n = result.sources?.length ?? 0;
2441
+ detail = args.query ? `${n} source${n === 1 ? "" : "s"} for "${truncate2(args.query, 40)}"` : `${n} source${n === 1 ? "" : "s"}`;
2442
+ } else if (args.action === "list") {
2443
+ const n = result.sources?.length ?? 0;
2444
+ detail = `${n} source${n === 1 ? "" : "s"}`;
2445
+ }
2446
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm py-0.5", children: [
2447
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BookOpenIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
2448
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: "Research" }),
2449
+ detail && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: detail })
2450
+ ] });
2451
+ }
2452
+ function hostname(url) {
2453
+ try {
2454
+ return new URL(url).hostname.replace(/^www\./, "");
2455
+ } catch {
2456
+ return url;
2457
+ }
2458
+ }
2459
+ function truncate2(s, n) {
2460
+ return s.length > n ? `${s.slice(0, n)}\u2026` : s;
2461
+ }
2431
2462
  function VisionAnalyzeBlock({ tc }) {
2432
2463
  let image = "";
2433
2464
  try {
@@ -4143,21 +4174,15 @@ function ArtifactToolBlock({
4143
4174
  name && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground truncate", children: name })
4144
4175
  ] });
4145
4176
  }
4146
-
4147
- // src/components/chat/tools/delegate-tool.tsx
4148
4177
  init_utils();
4149
4178
  function firstLine(s) {
4150
4179
  const idx = s.indexOf("\n");
4151
4180
  return idx === -1 ? s : s.slice(0, idx);
4152
4181
  }
4153
4182
  function DelegateToolBlock({ tc }) {
4154
- const [expanded, setExpanded] = react.useState(false);
4155
4183
  const args = parseArgs(tc.args);
4156
- const goal = args?.goal ?? "";
4157
- const context = args?.context ?? "";
4184
+ const goal = firstLine(args?.goal ?? "").trim();
4158
4185
  const agentType = args?.agent_type;
4159
- const model = args?.model;
4160
- const summary = firstLine(goal).trim();
4161
4186
  let resultError = null;
4162
4187
  if (tc.result) {
4163
4188
  try {
@@ -4166,84 +4191,29 @@ function DelegateToolBlock({ tc }) {
4166
4191
  } catch {
4167
4192
  }
4168
4193
  }
4169
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
4170
- /* @__PURE__ */ jsxRuntime.jsxs(
4171
- "button",
4194
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm py-0.5", children: [
4195
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GitBranchIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
4196
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: "Delegate" }),
4197
+ agentType && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground/70 text-xs", children: [
4198
+ "\xB7 ",
4199
+ agentType
4200
+ ] }),
4201
+ goal && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground/70 truncate text-xs italic", children: [
4202
+ "\xB7 \u201C",
4203
+ goal,
4204
+ "\u201D"
4205
+ ] }),
4206
+ resultError && /* @__PURE__ */ jsxRuntime.jsx(
4207
+ "span",
4172
4208
  {
4173
- type: "button",
4174
- onClick: () => setExpanded(!expanded),
4175
4209
  className: cn(
4176
- "flex w-full items-center gap-1.5 rounded-md px-2 py-1",
4177
- "text-sm text-muted-foreground hover:bg-muted/50 transition-colors"
4210
+ "shrink-0 text-xs",
4211
+ "text-destructive"
4178
4212
  ),
4179
- children: [
4180
- /* @__PURE__ */ jsxRuntime.jsx(
4181
- lucideReact.ChevronRightIcon,
4182
- {
4183
- className: cn(
4184
- "size-3 shrink-0 transition-transform duration-150",
4185
- expanded && "rotate-90"
4186
- )
4187
- }
4188
- ),
4189
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GitBranchIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
4190
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground/80", children: "delegate_task" }),
4191
- agentType && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground", children: [
4192
- "\xB7 ",
4193
- agentType
4194
- ] }),
4195
- summary && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground truncate min-w-0", children: [
4196
- "\xB7 ",
4197
- summary
4198
- ] })
4199
- ]
4213
+ title: resultError,
4214
+ children: "\xB7 failed"
4200
4215
  }
4201
- ),
4202
- expanded && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-6 mt-0.5 space-y-1.5 text-sm font-mono", children: [
4203
- (agentType || model) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-x-3 gap-y-0.5 text-xs text-muted-foreground", children: [
4204
- agentType && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
4205
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/70", children: "agent:" }),
4206
- " ",
4207
- agentType
4208
- ] }),
4209
- model && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
4210
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/70", children: "model:" }),
4211
- " ",
4212
- model
4213
- ] })
4214
- ] }),
4215
- goal && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded bg-muted/40 px-2 py-1", children: [
4216
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground/70", children: "Goal" }),
4217
- /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "mt-0.5 whitespace-pre-wrap break-words text-foreground/90", children: goal })
4218
- ] }),
4219
- context && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded bg-muted/40 px-2 py-1", children: [
4220
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground/70", children: "Context" }),
4221
- /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "mt-0.5 whitespace-pre-wrap break-words text-muted-foreground", children: truncate(context, 2e3) })
4222
- ] }),
4223
- tc.status === "running" && !tc.result && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground italic", children: "Sub-agent running\u2026" }),
4224
- tc.result && /* @__PURE__ */ jsxRuntime.jsxs(
4225
- "div",
4226
- {
4227
- className: cn(
4228
- "rounded px-2 py-1 max-h-64 overflow-y-auto",
4229
- resultError ? "bg-destructive/10" : "bg-muted/40"
4230
- ),
4231
- children: [
4232
- /* @__PURE__ */ jsxRuntime.jsx(
4233
- "div",
4234
- {
4235
- className: cn(
4236
- "text-[10px] uppercase tracking-wide",
4237
- resultError ? "text-destructive/80" : "text-emerald-600 dark:text-emerald-400"
4238
- ),
4239
- children: resultError ? "Error" : "Sub-agent response"
4240
- }
4241
- ),
4242
- /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "mt-0.5 whitespace-pre-wrap break-words text-foreground/90", children: truncate(resultError ?? tc.result, 4e3) })
4243
- ]
4244
- }
4245
- )
4246
- ] })
4216
+ )
4247
4217
  ] });
4248
4218
  }
4249
4219
  init_utils();
@@ -4486,38 +4456,6 @@ function ResearchOutlineBlock({ tc }) {
4486
4456
  outline ? /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap font-mono text-[11px] leading-snug", children: outline }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "updated" })
4487
4457
  ] });
4488
4458
  }
4489
- function ResearchMemoryBlock({ tc }) {
4490
- const args = parseArgs(tc.args) ?? {};
4491
- const result = tc.result ? parseArgs(tc.result) ?? {} : {};
4492
- let label;
4493
- if (args.action === "add") {
4494
- const host = args.url ? hostname(args.url) : "";
4495
- label = `Recorded source ${result.source_id ?? ""}${host ? ` \xB7 ${host}` : ""}`;
4496
- } else if (args.action === "retrieve") {
4497
- const n = result.sources?.length ?? 0;
4498
- label = `Retrieved ${n} source${n === 1 ? "" : "s"}${args.query ? ` for "${truncate2(args.query)}"` : ""}`;
4499
- } else if (args.action === "list") {
4500
- const n = result.sources?.length ?? 0;
4501
- label = `Listed ${n} source${n === 1 ? "" : "s"}`;
4502
- } else {
4503
- label = "research_memory";
4504
- }
4505
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-muted-foreground", children: [
4506
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: "research" }),
4507
- " ",
4508
- label
4509
- ] });
4510
- }
4511
- function hostname(url) {
4512
- try {
4513
- return new URL(url).hostname.replace(/^www\./, "");
4514
- } catch {
4515
- return url;
4516
- }
4517
- }
4518
- function truncate2(s, n = 40) {
4519
- return s.length > n ? `${s.slice(0, n)}\u2026` : s;
4520
- }
4521
4459
  function ToolCallBlock({
4522
4460
  tc,
4523
4461
  resolvedArtifactName,
@@ -4627,12 +4565,14 @@ function WebSearchGroupBlock({ tcs }) {
4627
4565
  /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-1 ml-5 space-y-0.5", children: tcs.map((tc) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(WebSearchRow, { tc }) }, tc.id)) })
4628
4566
  ] });
4629
4567
  }
4568
+ init_utils();
4569
+ var COLLAPSED_MAX_HEIGHT_PX = 160;
4630
4570
  var ChatMessage = react.memo(function ChatMessage2({
4631
4571
  message,
4632
4572
  onFileSelect
4633
4573
  }) {
4634
4574
  return /* @__PURE__ */ jsxRuntime.jsx(Message, { from: "user", children: /* @__PURE__ */ jsxRuntime.jsxs(MessageContent, { children: [
4635
- message.content,
4575
+ /* @__PURE__ */ jsxRuntime.jsx(CollapsibleMessageBody, { content: message.content }),
4636
4576
  message.images && message.images.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 mt-2", children: message.images.map((img, i) => /* @__PURE__ */ jsxRuntime.jsx(
4637
4577
  "img",
4638
4578
  {
@@ -4652,6 +4592,48 @@ var ChatMessage = react.memo(function ChatMessage2({
4652
4592
  )) })
4653
4593
  ] }) });
4654
4594
  });
4595
+ function CollapsibleMessageBody({ content }) {
4596
+ const ref = react.useRef(null);
4597
+ const [overflowing, setOverflowing] = react.useState(false);
4598
+ const [expanded, setExpanded] = react.useState(false);
4599
+ react.useLayoutEffect(() => {
4600
+ const el = ref.current;
4601
+ if (!el) return;
4602
+ const measure = () => {
4603
+ setOverflowing(el.scrollHeight > el.clientHeight + 1);
4604
+ };
4605
+ measure();
4606
+ const observer = new ResizeObserver(measure);
4607
+ observer.observe(el);
4608
+ return () => observer.disconnect();
4609
+ }, [content]);
4610
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
4611
+ /* @__PURE__ */ jsxRuntime.jsx(
4612
+ "div",
4613
+ {
4614
+ ref,
4615
+ className: cn(
4616
+ "whitespace-pre-wrap wrap-break-word",
4617
+ !expanded && "overflow-hidden"
4618
+ ),
4619
+ style: !expanded ? { maxHeight: COLLAPSED_MAX_HEIGHT_PX } : void 0,
4620
+ children: content
4621
+ }
4622
+ ),
4623
+ (overflowing || expanded) && /* @__PURE__ */ jsxRuntime.jsx(
4624
+ "button",
4625
+ {
4626
+ type: "button",
4627
+ onClick: () => setExpanded((prev) => !prev),
4628
+ className: cn(
4629
+ "mt-2 text-xs font-bold text-primary",
4630
+ "hover:underline focus:outline-none focus-visible:underline"
4631
+ ),
4632
+ children: expanded ? "Show less..." : "Show more..."
4633
+ }
4634
+ )
4635
+ ] });
4636
+ }
4655
4637
  function iconForMime(mime) {
4656
4638
  if (!mime) return lucideReact.File;
4657
4639
  if (mime.startsWith("image/")) return lucideReact.FileImage;
@@ -6141,10 +6123,10 @@ function ChatComposerInner({
6141
6123
  }
6142
6124
  }, [buttonMenuOpen, menuDismissed, textInput.value]);
6143
6125
  react.useEffect(() => {
6144
- if (textInput.value.startsWith("/")) {
6126
+ if (!buttonMenuOpen && textInput.value.startsWith("/")) {
6145
6127
  setMenuMode("all");
6146
6128
  }
6147
- }, [textInput.value]);
6129
+ }, [buttonMenuOpen, textInput.value]);
6148
6130
  react.useEffect(() => {
6149
6131
  if (showSlashMenu && (menuMode === "skills" || menuMode === "all")) {
6150
6132
  adapter.listSlashCommands?.().then(setAdapterCommands).catch(() => {
@@ -6316,7 +6298,7 @@ function ChatComposerInner({
6316
6298
  },
6317
6299
  children: [
6318
6300
  /* @__PURE__ */ jsxRuntime.jsx(AttachmentPreviewStrip, {}),
6319
- viewMode === "expert" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2 px-1 pb-2", children: [
6301
+ viewMode === "expert" && !disabled && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2 px-1 pb-2", children: [
6320
6302
  /* @__PURE__ */ jsxRuntime.jsxs(
6321
6303
  Button,
6322
6304
  {
@@ -6391,7 +6373,7 @@ function ChatComposerInner({
6391
6373
  ) }),
6392
6374
  /* @__PURE__ */ jsxRuntime.jsxs(PromptInputFooter, { children: [
6393
6375
  /* @__PURE__ */ jsxRuntime.jsxs(PromptInputTools, { children: [
6394
- /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: addMenuOpen, onOpenChange: setAddMenuOpen, children: [
6376
+ !disabled && /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: addMenuOpen, onOpenChange: setAddMenuOpen, children: [
6395
6377
  /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(PromptInputButton, { "aria-label": "Add", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlusIcon, { className: "size-4" }) }) }),
6396
6378
  /* @__PURE__ */ jsxRuntime.jsx(
6397
6379
  PopoverContent,
@@ -6463,7 +6445,7 @@ function ChatComposerInner({
6463
6445
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderIcon, { className: "size-4" })
6464
6446
  }
6465
6447
  ),
6466
- tokenUsage && tokenUsage.contextWindow > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
6448
+ !disabled && tokenUsage && tokenUsage.contextWindow > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
6467
6449
  Context,
6468
6450
  {
6469
6451
  usedTokens: tokenUsage.totalTokens,
@@ -6551,7 +6533,7 @@ function ChatComposerInner({
6551
6533
  ]
6552
6534
  }
6553
6535
  ),
6554
- /* @__PURE__ */ jsxRuntime.jsx(
6536
+ !disabled && /* @__PURE__ */ jsxRuntime.jsx(
6555
6537
  PromptInputSubmit,
6556
6538
  {
6557
6539
  status,
@@ -6689,6 +6671,83 @@ function ChatComposerInner({
6689
6671
  );
6690
6672
  }
6691
6673
 
6674
+ // src/components/research/research-sources-panel.tsx
6675
+ init_utils();
6676
+ function ResearchSourcesPanel({
6677
+ sources
6678
+ }) {
6679
+ const [expanded, setExpanded] = react.useState(false);
6680
+ const rootRef = react.useRef(null);
6681
+ react.useEffect(() => {
6682
+ const root = rootRef.current;
6683
+ if (!root) return;
6684
+ const handler = (event) => {
6685
+ const target = event.target;
6686
+ if (target?.closest('[id^="source-"]')) {
6687
+ setExpanded(true);
6688
+ }
6689
+ };
6690
+ document.addEventListener("scroll-to-source", handler, true);
6691
+ return () => document.removeEventListener("scroll-to-source", handler, true);
6692
+ }, []);
6693
+ if (sources.length === 0) return null;
6694
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6695
+ "div",
6696
+ {
6697
+ ref: rootRef,
6698
+ className: "rounded-md border border-line bg-card",
6699
+ children: [
6700
+ /* @__PURE__ */ jsxRuntime.jsxs(
6701
+ "button",
6702
+ {
6703
+ type: "button",
6704
+ onClick: () => setExpanded((prev) => !prev),
6705
+ "aria-expanded": expanded,
6706
+ className: "flex w-full items-center gap-1.5 px-2 py-1.5 text-left hover:bg-muted/40",
6707
+ children: [
6708
+ /* @__PURE__ */ jsxRuntime.jsx(
6709
+ lucideReact.ChevronRightIcon,
6710
+ {
6711
+ className: cn(
6712
+ "size-3 shrink-0 text-muted-foreground transition-transform duration-150",
6713
+ expanded && "rotate-90"
6714
+ )
6715
+ }
6716
+ ),
6717
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: [
6718
+ "Sources \xB7 ",
6719
+ sources.length
6720
+ ] })
6721
+ ]
6722
+ }
6723
+ ),
6724
+ expanded && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "max-h-35 overflow-y-auto border-t border-line py-1", children: sources.map((s) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
6725
+ "a",
6726
+ {
6727
+ id: `source-${s.sourceId}`,
6728
+ href: s.url,
6729
+ target: "_blank",
6730
+ rel: "noreferrer",
6731
+ className: "group flex items-baseline gap-2 px-2 py-1 hover:bg-muted/60",
6732
+ children: [
6733
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-[10px] font-semibold text-primary tabular-nums", children: s.sourceId }),
6734
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-foreground group-hover:underline", children: s.title || s.url }),
6735
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto shrink-0 truncate text-[10px] text-muted-foreground max-w-[40%]", children: hostname2(s.url) })
6736
+ ]
6737
+ }
6738
+ ) }, s.sourceId)) })
6739
+ ]
6740
+ }
6741
+ );
6742
+ }
6743
+ function hostname2(url) {
6744
+ try {
6745
+ return new URL(url).hostname.replace(/^www\./, "");
6746
+ } catch {
6747
+ return url;
6748
+ }
6749
+ }
6750
+
6692
6751
  // src/components/chat/turn-feedback.tsx
6693
6752
  init_utils();
6694
6753
  var MAX_REASON_LENGTH2 = 500;
@@ -7066,8 +7125,23 @@ var ArtifactContent = ({
7066
7125
  className,
7067
7126
  ...props
7068
7127
  }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex-1 overflow-auto p-4", className), ...props });
7069
- function ArtifactMarkdown({ spec }) {
7070
- return /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { children: spec.content ?? "" });
7128
+
7129
+ // src/components/chat/artifacts/artifact-markdown.tsx
7130
+ init_utils();
7131
+ function ArtifactMarkdown({
7132
+ spec,
7133
+ fill = false
7134
+ }) {
7135
+ return /* @__PURE__ */ jsxRuntime.jsx(
7136
+ "div",
7137
+ {
7138
+ className: cn(
7139
+ "px-4 py-3",
7140
+ !fill && "max-h-112 overflow-y-auto"
7141
+ ),
7142
+ children: /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { children: spec.content ?? "" })
7143
+ }
7144
+ );
7071
7145
  }
7072
7146
  function formatCell(value) {
7073
7147
  if (value == null) return "";
@@ -7440,7 +7514,7 @@ function ArtifactBody({
7440
7514
  }
7441
7515
  switch (payload.kind) {
7442
7516
  case "markdown":
7443
- return /* @__PURE__ */ jsxRuntime.jsx(ArtifactMarkdown, { spec: payload.spec });
7517
+ return /* @__PURE__ */ jsxRuntime.jsx(ArtifactMarkdown, { spec: payload.spec, fill });
7444
7518
  case "table":
7445
7519
  return /* @__PURE__ */ jsxRuntime.jsx(ArtifactTable, { spec: payload.spec });
7446
7520
  case "chart":
@@ -8341,17 +8415,15 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
8341
8415
  }];
8342
8416
  }
8343
8417
  if (msg.systemKind === "artifact") {
8344
- const { artifactId, name, kind, version } = unpackArtifactMeta(
8345
- msg.systemMeta,
8346
- msg.content
8347
- );
8418
+ const { artifactId, name, kind, version, originatingSessionId } = unpackArtifactMeta(msg.systemMeta, msg.content);
8348
8419
  return [{
8349
8420
  kind: "artifact",
8350
8421
  key: msg.id,
8351
8422
  artifactId,
8352
8423
  name,
8353
8424
  artifactKind: kind,
8354
- version
8425
+ version,
8426
+ originatingSessionId
8355
8427
  }];
8356
8428
  }
8357
8429
  if (msg.systemKind === "browser_marker" || msg.systemKind === "browser_marker_warning") {
@@ -8523,7 +8595,8 @@ function unpackArtifactMeta(systemMeta, fallbackName) {
8523
8595
  artifactId: meta.artifact_id ?? "",
8524
8596
  name: meta.name ?? fallbackName,
8525
8597
  kind: meta.kind ?? "markdown",
8526
- version: meta.version ?? 1
8598
+ version: meta.version ?? 1,
8599
+ originatingSessionId: meta.originating_session_id ?? null
8527
8600
  };
8528
8601
  }
8529
8602
  function OrphanSystemMarker({
@@ -8549,7 +8622,7 @@ function OrphanSystemMarker({
8549
8622
  return /* @__PURE__ */ jsxRuntime.jsx(
8550
8623
  ArtifactBlock,
8551
8624
  {
8552
- sessionId,
8625
+ sessionId: unpacked.originatingSessionId ?? sessionId,
8553
8626
  artifactId: unpacked.artifactId,
8554
8627
  name: unpacked.name,
8555
8628
  kind: unpacked.kind,
@@ -8582,25 +8655,32 @@ function OrphanSystemMarker({
8582
8655
  function cancelledToolLabel(toolName) {
8583
8656
  const map = {
8584
8657
  terminal: "Command",
8585
- execute_code: "Execute Code",
8658
+ execute_code: "Execute code",
8586
8659
  read_file: "Read",
8587
8660
  write_file: "Write",
8588
8661
  patch: "Patch",
8589
- search_files: "Search Files",
8590
- list_files: "List Files",
8591
- web_search: "Web Search",
8592
- web_extract: "Web Fetch",
8593
- web_crawl: "Web Crawl",
8594
- session_search: "Session Search",
8662
+ search_files: "Search files",
8663
+ list_files: "List files",
8664
+ web_search: "Web search",
8665
+ web_extract: "Web fetch",
8666
+ web_crawl: "Web crawl",
8667
+ session_search: "Session search",
8595
8668
  memory: "Memory",
8596
8669
  todo: "Todo",
8597
8670
  skills_list: "Skills",
8598
8671
  skill_view: "Skill",
8599
8672
  consult_expert: "Expert",
8600
- delegate_task: "Delegate",
8673
+ delegate_task: "Delegate task",
8601
8674
  ask_user_question: "Ask User Question",
8602
8675
  process: "Process",
8603
- create_artifact: "Artifact"
8676
+ create_artifact: "Create artifact",
8677
+ // ``Research memory`` reads as a noun ("memory of research") and
8678
+ // makes the shimmer awkward ("Running Research memory…"). The
8679
+ // one-liner renderer shows just ``Research`` as the label, so
8680
+ // mirror that here so the live shimmer matches the row that
8681
+ // replaces it on completion.
8682
+ research_memory: "Research",
8683
+ research_outline: "Research"
8604
8684
  };
8605
8685
  return map[toolName] ?? toolName;
8606
8686
  }
@@ -8729,7 +8809,7 @@ function TimelineEntryItem({
8729
8809
  /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: sessionId ? /* @__PURE__ */ jsxRuntime.jsx(
8730
8810
  ArtifactBlock,
8731
8811
  {
8732
- sessionId,
8812
+ sessionId: entry.originatingSessionId ?? sessionId,
8733
8813
  artifactId: entry.artifactId,
8734
8814
  name: entry.name,
8735
8815
  kind: entry.artifactKind,
@@ -8870,8 +8950,15 @@ function extractToolDetail(tc) {
8870
8950
  case "web_search":
8871
8951
  case "web_crawl":
8872
8952
  return stringArg("query");
8873
- case "web_extract":
8874
- return stringArg("url");
8953
+ case "web_extract": {
8954
+ const url = stringArg("url");
8955
+ if (!url) return null;
8956
+ try {
8957
+ return new URL(url).hostname.replace(/^www\./, "");
8958
+ } catch {
8959
+ return truncate3(url, 40);
8960
+ }
8961
+ }
8875
8962
  case "skill_view":
8876
8963
  case "skill_manage":
8877
8964
  return stringArg("name") ?? stringArg("skill");
@@ -8881,6 +8968,40 @@ function extractToolDetail(tc) {
8881
8968
  return stringArg("name") ?? stringArg("task") ?? stringArg("title");
8882
8969
  case "memory":
8883
8970
  return stringArg("action") ?? stringArg("key");
8971
+ case "research_memory": {
8972
+ const action = stringArg("action");
8973
+ if (action === "add") {
8974
+ const title = stringArg("title");
8975
+ if (title) return truncate3(title, 60);
8976
+ const url = stringArg("url");
8977
+ if (url) {
8978
+ try {
8979
+ return new URL(url).hostname.replace(/^www\./, "");
8980
+ } catch {
8981
+ return truncate3(url, 40);
8982
+ }
8983
+ }
8984
+ return null;
8985
+ }
8986
+ if (action === "retrieve") {
8987
+ return stringArg("query");
8988
+ }
8989
+ return action;
8990
+ }
8991
+ case "research_outline": {
8992
+ const action = stringArg("action");
8993
+ if (action === "set") {
8994
+ const outline = stringArg("outline");
8995
+ if (outline) {
8996
+ const sections = outline.split(/\r?\n/).filter((line) => /^#{2,6}\s+\S/.test(line)).length;
8997
+ if (sections > 0) {
8998
+ return `${sections} ${sections === 1 ? "section" : "sections"}`;
8999
+ }
9000
+ }
9001
+ return "outline";
9002
+ }
9003
+ return action;
9004
+ }
8884
9005
  default:
8885
9006
  return null;
8886
9007
  }
@@ -8966,7 +9087,9 @@ var _TOOL_ROW_ICON = {
8966
9087
  delegate_task: lucideReact.ArrowRight,
8967
9088
  create_artifact: lucideReact.FileTextIcon,
8968
9089
  memory: lucideReact.PenLineIcon,
8969
- todo: lucideReact.ListIcon
9090
+ todo: lucideReact.ListIcon,
9091
+ research_memory: lucideReact.BookOpenIcon,
9092
+ research_outline: lucideReact.ListIcon
8970
9093
  };
8971
9094
  function _toolRowIcon(toolName) {
8972
9095
  return _TOOL_ROW_ICON[toolName] ?? lucideReact.WrenchIcon;
@@ -9012,6 +9135,31 @@ function _toolRowLabel(tc) {
9012
9135
  return detail ? `Memory ${detail}` : "Updated memory";
9013
9136
  case "todo":
9014
9137
  return detail ? `Todo ${detail}` : "Updated todo list";
9138
+ case "research_memory": {
9139
+ const args = parseArgs(tc.args);
9140
+ const action = args?.action;
9141
+ if (action === "add") {
9142
+ return detail ? `Stored source "${detail}"` : "Stored a source";
9143
+ }
9144
+ if (action === "retrieve") {
9145
+ return detail ? `Retrieved sources for "${detail}"` : "Retrieved sources";
9146
+ }
9147
+ if (action === "list") {
9148
+ return "Listed sources";
9149
+ }
9150
+ return "Updated research memory";
9151
+ }
9152
+ case "research_outline": {
9153
+ const args = parseArgs(tc.args);
9154
+ const action = args?.action;
9155
+ if (action === "set") {
9156
+ return detail ? `Updated outline (${detail})` : "Updated outline";
9157
+ }
9158
+ if (action === "get") {
9159
+ return "Read outline";
9160
+ }
9161
+ return "Touched outline";
9162
+ }
9015
9163
  default:
9016
9164
  return cancelledToolLabel(tc.toolName);
9017
9165
  }
@@ -9185,7 +9333,8 @@ function SimpleAssistantGroup({
9185
9333
  sessionId,
9186
9334
  artifactFallbacks,
9187
9335
  onFileSelect,
9188
- onRetry
9336
+ onRetry,
9337
+ hideTurnSummary = false
9189
9338
  }) {
9190
9339
  const assistantMessages = messages.filter((m) => m.role === "assistant");
9191
9340
  const tail = assistantMessages[assistantMessages.length - 1];
@@ -9253,7 +9402,7 @@ function SimpleAssistantGroup({
9253
9402
  tail
9254
9403
  }
9255
9404
  ),
9256
- effectiveTurnSummary && /* @__PURE__ */ jsxRuntime.jsx(
9405
+ effectiveTurnSummary && !hideTurnSummary && /* @__PURE__ */ jsxRuntime.jsx(
9257
9406
  TurnSummaryCard,
9258
9407
  {
9259
9408
  summary: effectiveTurnSummary,
@@ -9274,7 +9423,8 @@ function AssistantGroup({
9274
9423
  artifactFallbacks,
9275
9424
  onFileSelect,
9276
9425
  onRetry,
9277
- viewMode = "simple"
9426
+ viewMode = "simple",
9427
+ hideTurnSummary = false
9278
9428
  }) {
9279
9429
  if (viewMode === "simple") {
9280
9430
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -9285,7 +9435,8 @@ function AssistantGroup({
9285
9435
  sessionId,
9286
9436
  artifactFallbacks,
9287
9437
  onFileSelect,
9288
- onRetry
9438
+ onRetry,
9439
+ hideTurnSummary
9289
9440
  }
9290
9441
  );
9291
9442
  }
@@ -9392,7 +9543,9 @@ function ChatThread({
9392
9543
  canShowWorkspace = false,
9393
9544
  viewMode = "simple",
9394
9545
  onViewModeChange,
9395
- deepResearchEnabled = false
9546
+ deepResearchEnabled = false,
9547
+ researchSources = [],
9548
+ hideTurnSummary = false
9396
9549
  }) {
9397
9550
  const groups = react.useMemo(() => groupMessages(messages), [messages]);
9398
9551
  const awaitingInput = react.useMemo(() => isAwaitingUserInput(messages), [messages]);
@@ -9490,7 +9643,8 @@ function ChatThread({
9490
9643
  artifactFallbacks,
9491
9644
  onFileSelect,
9492
9645
  onRetry: groupRetry,
9493
- viewMode
9646
+ viewMode,
9647
+ hideTurnSummary
9494
9648
  },
9495
9649
  group.messages[0].id
9496
9650
  );
@@ -9501,14 +9655,8 @@ function ChatThread({
9501
9655
  ] }),
9502
9656
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
9503
9657
  retryIndicator && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(RetryBanner, { indicator: retryIndicator }) }),
9504
- composerDisabled && composerDisabledReason ? /* @__PURE__ */ jsxRuntime.jsx(
9505
- "div",
9506
- {
9507
- className: "rounded border border-line bg-muted/40 px-3 py-2 text-sm text-foreground/60",
9508
- role: "status",
9509
- children: composerDisabledReason
9510
- }
9511
- ) : /* @__PURE__ */ jsxRuntime.jsx(
9658
+ researchSources.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(ResearchSourcesPanel, { sources: researchSources }) }),
9659
+ /* @__PURE__ */ jsxRuntime.jsx(
9512
9660
  ChatComposer,
9513
9661
  {
9514
9662
  onSend,
@@ -9532,44 +9680,6 @@ function ChatThread({
9532
9680
  ] })
9533
9681
  ] });
9534
9682
  }
9535
- function ResearchSourcesPanel({
9536
- sources
9537
- }) {
9538
- if (sources.length === 0) {
9539
- return /* @__PURE__ */ jsxRuntime.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." });
9540
- }
9541
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 p-2", children: [
9542
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-1 pb-1 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: [
9543
- "Sources \xB7 ",
9544
- sources.length
9545
- ] }),
9546
- sources.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
9547
- "a",
9548
- {
9549
- id: `source-${s.sourceId}`,
9550
- href: s.url,
9551
- target: "_blank",
9552
- rel: "noreferrer",
9553
- className: "group flex items-baseline gap-2 rounded-sm px-1 py-1 hover:bg-muted",
9554
- children: [
9555
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-[10px] font-semibold text-primary", children: s.sourceId }),
9556
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex flex-col overflow-hidden", children: [
9557
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-foreground group-hover:underline", children: s.title || s.url }),
9558
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-[10px] text-muted-foreground", children: hostname2(s.url) })
9559
- ] })
9560
- ]
9561
- },
9562
- s.sourceId
9563
- ))
9564
- ] });
9565
- }
9566
- function hostname2(url) {
9567
- try {
9568
- return new URL(url).hostname.replace(/^www\./, "");
9569
- } catch {
9570
- return url;
9571
- }
9572
- }
9573
9683
 
9574
9684
  // src/components/ai-elements/file-tree.tsx
9575
9685
  init_utils();
@@ -10247,6 +10357,29 @@ function isScheduledRunSession(session) {
10247
10357
  session?.channel === "scheduled" || session?.config?.scheduled_session_id
10248
10358
  );
10249
10359
  }
10360
+ var _SUB_AGENT_CHANNELS = /* @__PURE__ */ new Set([
10361
+ "delegation",
10362
+ "worker",
10363
+ "task"
10364
+ ]);
10365
+ function isSubAgentSession(session) {
10366
+ if (!session) return false;
10367
+ if (session.parentId) return true;
10368
+ if (session.channel && _SUB_AGENT_CHANNELS.has(session.channel)) return true;
10369
+ return false;
10370
+ }
10371
+ function readOnlyReasonForSession(session) {
10372
+ if (isScheduledRunSession(session)) {
10373
+ return { readOnly: true, reason: "Scheduled run is read-only" };
10374
+ }
10375
+ if (isSubAgentSession(session)) {
10376
+ return {
10377
+ readOnly: true,
10378
+ reason: "Sub-task started by parent session \u2014 read-only"
10379
+ };
10380
+ }
10381
+ return { readOnly: false };
10382
+ }
10250
10383
 
10251
10384
  // src/runtime/events.ts
10252
10385
  var WORKSPACE_MUTATING_TOOLS = /* @__PURE__ */ new Set([
@@ -10369,7 +10502,13 @@ function applyAgentChatEvent(state, event) {
10369
10502
  name: event.data.name,
10370
10503
  kind: event.data.kind,
10371
10504
  version: event.data.version,
10372
- size: event.data.size
10505
+ size: event.data.size,
10506
+ // Propagated artifacts (a delegated child's report
10507
+ // surfacing on the parent's chat thread) carry this
10508
+ // field; without it the ArtifactBlock fetches via the
10509
+ // chat's own session id and 404s because the spec lives
10510
+ // under the writer's S3 prefix, not the parent's.
10511
+ originating_session_id: event.data.originating_session_id
10373
10512
  }
10374
10513
  }
10375
10514
  ]);
@@ -10756,7 +10895,7 @@ function applyLlmThinking(state, event) {
10756
10895
  } else {
10757
10896
  messages[idx] = {
10758
10897
  ...prev,
10759
- reasoning: (prev.reasoning ?? "") + reasoningText,
10898
+ reasoning: appendText(prev.reasoning, reasoningText),
10760
10899
  turnId: turnId ?? prev.turnId,
10761
10900
  iterationIndex: iterationIndex ?? prev.iterationIndex
10762
10901
  };
@@ -11691,16 +11830,24 @@ function AgentChat({
11691
11830
  react.useEffect(() => {
11692
11831
  setShowWorkspace(runtime.viewMode === "expert");
11693
11832
  }, [runtime.viewMode]);
11694
- const isReadOnlySession = isScheduledRunSession(runtime.session);
11695
- const effectiveDisabled = disabled || isReadOnlySession;
11696
- const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
11833
+ const readOnly = readOnlyReasonForSession(runtime.session);
11834
+ const effectiveDisabled = disabled || readOnly.readOnly;
11835
+ const disabledReason = readOnly.reason;
11836
+ const orchestratesDeepResearch = react.useMemo(
11837
+ () => runtime.messages.some(
11838
+ (m) => m.toolCalls?.some(
11839
+ (tc) => tc.toolName === "delegate_task" && delegateTaskTargets(tc.args).includes("deep-research")
11840
+ )
11841
+ ),
11842
+ [runtime.messages]
11843
+ );
11844
+ const hideTurnSummary = isSubAgentSession(runtime.session) || orchestratesDeepResearch;
11697
11845
  const browserState = runtime.state.browser;
11698
11846
  const browserAvailable = browserState !== null && browserState.status !== "closed" && !!sessionId;
11699
11847
  const browserVisible = browserAvailable && showBrowser;
11700
11848
  const workspaceAvailable = !!sessionId;
11701
11849
  const workspaceVisible = workspaceAvailable && showWorkspace;
11702
- const sourcesVisible = runtime.researchSources.length > 0;
11703
- const rightStackVisible = browserVisible || workspaceVisible || sourcesVisible;
11850
+ const rightStackVisible = browserVisible || workspaceVisible;
11704
11851
  const bothPanesVisible = browserVisible && workspaceVisible;
11705
11852
  react.useEffect(() => {
11706
11853
  onMessagesChange?.(runtime.messages);
@@ -11816,7 +11963,9 @@ function AgentChat({
11816
11963
  canShowWorkspace: workspaceAvailable,
11817
11964
  viewMode: runtime.viewMode,
11818
11965
  onViewModeChange: runtime.setViewMode,
11819
- deepResearchEnabled
11966
+ deepResearchEnabled,
11967
+ researchSources: runtime.researchSources,
11968
+ hideTurnSummary
11820
11969
  }
11821
11970
  )
11822
11971
  }
@@ -11876,20 +12025,6 @@ function AgentChat({
11876
12025
  }
11877
12026
  )
11878
12027
  }
11879
- ),
11880
- sourcesVisible && /* @__PURE__ */ jsxRuntime.jsx(
11881
- "div",
11882
- {
11883
- "data-testid": "research-sources-panel-frame",
11884
- className: cn(
11885
- "min-h-0 w-full overflow-auto",
11886
- // When other panes share the stack the sources
11887
- // panel takes a fixed minimum so the source rows
11888
- // stay readable; otherwise it fills the column.
11889
- browserVisible || workspaceVisible ? "max-h-1/3 border-t border-line" : "h-full"
11890
- ),
11891
- children: /* @__PURE__ */ jsxRuntime.jsx(ResearchSourcesPanel, { sources: runtime.researchSources })
11892
- }
11893
12028
  )
11894
12029
  ]
11895
12030
  }
@@ -11900,6 +12035,27 @@ function AgentChat({
11900
12035
  }
11901
12036
  );
11902
12037
  }
12038
+ function delegateTaskTargets(rawArgs) {
12039
+ if (!rawArgs) return [];
12040
+ let parsed;
12041
+ try {
12042
+ parsed = JSON.parse(rawArgs);
12043
+ } catch {
12044
+ return [];
12045
+ }
12046
+ if (!parsed || typeof parsed !== "object") return [];
12047
+ const out = [];
12048
+ const a = parsed;
12049
+ if (typeof a.agent_type === "string") out.push(a.agent_type);
12050
+ if (Array.isArray(a.goals)) {
12051
+ for (const g of a.goals) {
12052
+ if (g && typeof g === "object" && typeof g.agent_type === "string") {
12053
+ out.push(g.agent_type);
12054
+ }
12055
+ }
12056
+ }
12057
+ return out;
12058
+ }
11903
12059
  init_utils();
11904
12060
  var DEFAULT_LIMIT = 50;
11905
12061
  function requireInboxAdapter(adapter) {
@@ -14052,7 +14208,7 @@ function mergeTreeNodes(groups) {
14052
14208
  }
14053
14209
  return Array.from(byId.values());
14054
14210
  }
14055
- function pruneDeletedSessionNodes(nodes, deletedSessionId) {
14211
+ function collectDeletedSessionIds(nodes, deletedSessionId) {
14056
14212
  const deletedIds = /* @__PURE__ */ new Set([deletedSessionId]);
14057
14213
  let changed = true;
14058
14214
  while (changed) {
@@ -14064,7 +14220,7 @@ function pruneDeletedSessionNodes(nodes, deletedSessionId) {
14064
14220
  }
14065
14221
  }
14066
14222
  }
14067
- return nodes.filter((node) => !deletedIds.has(node.id));
14223
+ return deletedIds;
14068
14224
  }
14069
14225
  function formatSessionTime(value) {
14070
14226
  const normalized = /[zZ]|[+-]\d{2}:?\d{2}$/.test(value) ? value : `${value}Z`;
@@ -14095,7 +14251,6 @@ function TreeNodeRow({
14095
14251
  activeSessionId,
14096
14252
  canStop,
14097
14253
  canDelete,
14098
- deletingSessionId,
14099
14254
  onSelect,
14100
14255
  onStop,
14101
14256
  onDelete
@@ -14163,18 +14318,14 @@ function TreeNodeRow({
14163
14318
  "button",
14164
14319
  {
14165
14320
  type: "button",
14166
- className: cn(
14167
- "p-2 md:p-1 rounded hover:bg-destructive/10 hover:text-destructive disabled:pointer-events-none transition-all",
14168
- deletingSessionId === entry.id ? "opacity-100" : "opacity-60 md:opacity-50 md:group-hover:opacity-100 md:focus-visible:opacity-100"
14169
- ),
14321
+ 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",
14170
14322
  onClick: (e) => {
14171
14323
  e.stopPropagation();
14172
14324
  onDelete(entry.id);
14173
14325
  },
14174
14326
  "aria-label": "Delete session",
14175
14327
  title: "Delete session",
14176
- disabled: deletingSessionId === entry.id,
14177
- children: deletingSessionId === entry.id ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "w-3 h-3 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2Icon, { className: "w-3 h-3" })
14328
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2Icon, { className: "w-3 h-3" })
14178
14329
  }
14179
14330
  )
14180
14331
  ]
@@ -14188,7 +14339,6 @@ function TreeNodeRow({
14188
14339
  activeSessionId,
14189
14340
  canStop,
14190
14341
  canDelete,
14191
- deletingSessionId,
14192
14342
  onSelect,
14193
14343
  onStop,
14194
14344
  onDelete
@@ -14213,10 +14363,8 @@ function SessionTreePanel({
14213
14363
  const [nodes, setNodes] = react.useState([]);
14214
14364
  const [error, setError] = react.useState(null);
14215
14365
  const [hasEverLoaded, setHasEverLoaded] = react.useState(false);
14216
- const [deletingSessionId, setDeletingSessionId] = react.useState(
14217
- null
14218
- );
14219
14366
  const mounted = react.useRef(true);
14367
+ const pendingDeleteIds = react.useRef(/* @__PURE__ */ new Set());
14220
14368
  const requestId = react.useRef(0);
14221
14369
  const lastFingerprint = react.useRef("");
14222
14370
  const resetContext = react.useRef(null);
@@ -14241,10 +14389,23 @@ function SessionTreePanel({
14241
14389
  sessionTreePromise
14242
14390
  ]);
14243
14391
  if (!mounted.current || currentRequestId !== requestId.current) return;
14244
- const nextNodes = mergeTreeNodes([
14392
+ let nextNodes = mergeTreeNodes([
14245
14393
  sessionList?.sessions.map(sessionToTreeNode) ?? [],
14246
14394
  sessionTree?.nodes ?? []
14247
14395
  ]);
14396
+ if (pendingDeleteIds.current.size > 0) {
14397
+ const stillReturned = /* @__PURE__ */ new Set();
14398
+ nextNodes = nextNodes.filter((node) => {
14399
+ if (!pendingDeleteIds.current.has(node.id)) return true;
14400
+ stillReturned.add(node.id);
14401
+ return false;
14402
+ });
14403
+ for (const pendingId of pendingDeleteIds.current) {
14404
+ if (!stillReturned.has(pendingId)) {
14405
+ pendingDeleteIds.current.delete(pendingId);
14406
+ }
14407
+ }
14408
+ }
14248
14409
  const fp = treeFingerprint(nextNodes);
14249
14410
  if (fp !== lastFingerprint.current) {
14250
14411
  lastFingerprint.current = fp;
@@ -14275,6 +14436,7 @@ function SessionTreePanel({
14275
14436
  setNodes([]);
14276
14437
  setHasEverLoaded(false);
14277
14438
  lastFingerprint.current = "";
14439
+ pendingDeleteIds.current.clear();
14278
14440
  }
14279
14441
  setError(null);
14280
14442
  void refetch();
@@ -14311,24 +14473,31 @@ function SessionTreePanel({
14311
14473
  );
14312
14474
  const handleDelete = react.useCallback(
14313
14475
  async (id) => {
14314
- if (!adapter.deleteSession || deletingSessionId) return;
14315
- setDeletingSessionId(id);
14476
+ if (!adapter.deleteSession) return;
14477
+ const deletedIds = collectDeletedSessionIds(nodes, id);
14478
+ for (const deletedId of deletedIds) {
14479
+ pendingDeleteIds.current.add(deletedId);
14480
+ }
14481
+ setNodes((current) => {
14482
+ const next = current.filter((node) => !deletedIds.has(node.id));
14483
+ lastFingerprint.current = treeFingerprint(next);
14484
+ return next;
14485
+ });
14486
+ onSessionDelete?.(id);
14316
14487
  try {
14317
14488
  await adapter.deleteSession({ sessionId: id });
14318
- setNodes((current) => {
14319
- const next = pruneDeletedSessionNodes(current, id);
14320
- lastFingerprint.current = treeFingerprint(next);
14321
- return next;
14322
- });
14323
- onSessionDelete?.(id);
14324
14489
  await refetch({ silent: true });
14325
14490
  } catch (e) {
14326
- setError(e instanceof Error ? e.message : "Failed to delete session");
14327
- } finally {
14328
- if (mounted.current) setDeletingSessionId(null);
14491
+ for (const deletedId of deletedIds) {
14492
+ pendingDeleteIds.current.delete(deletedId);
14493
+ }
14494
+ if (mounted.current) {
14495
+ setError(e instanceof Error ? e.message : "Failed to delete session");
14496
+ }
14497
+ await refetch({ silent: true });
14329
14498
  }
14330
14499
  },
14331
- [adapter, deletingSessionId, onSessionDelete, refetch]
14500
+ [adapter, nodes, onSessionDelete, refetch]
14332
14501
  );
14333
14502
  if (!hasEverLoaded) return null;
14334
14503
  if (nodes.length === 0) return null;
@@ -14349,7 +14518,6 @@ function SessionTreePanel({
14349
14518
  activeSessionId: activeSessionId ?? "",
14350
14519
  canStop: Boolean(adapter.stopSession),
14351
14520
  canDelete: Boolean(adapter.deleteSession),
14352
- deletingSessionId,
14353
14521
  onSelect: handleSelect,
14354
14522
  onStop: handleStop,
14355
14523
  onDelete: handleDelete