@blade-hq/agent-react 2610.0.0-beta.32 → 2610.0.0-beta.34

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
@@ -17,6 +17,9 @@ function useBladeClient() {
17
17
  }
18
18
  return client;
19
19
  }
20
+ function useOptionalBladeClient() {
21
+ return useContext(BladeClientContext);
22
+ }
20
23
 
21
24
  // src/hooks/use-agent-session.ts
22
25
  import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
@@ -1141,7 +1144,7 @@ var X = createLucideIcon("X", [
1141
1144
  ]);
1142
1145
 
1143
1146
  // src/components/AgentChat.tsx
1144
- import { useCallback as useCallback8, useEffect as useEffect12, useMemo as useMemo8, useState as useState14 } from "react";
1147
+ import { useCallback as useCallback8, useEffect as useEffect13, useMemo as useMemo8, useState as useState15 } from "react";
1145
1148
 
1146
1149
  // src/lib/utils.ts
1147
1150
  function cn(...inputs) {
@@ -1610,7 +1613,8 @@ function CurrentPlanPanel({
1610
1613
  import { chatErrorForDisplay as chatErrorForDisplay2 } from "@blade-hq/agent-client";
1611
1614
 
1612
1615
  // src/components/ChatInput.tsx
1613
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1616
+ import { useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
1617
+ import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1614
1618
  function isImeCompositionKey(event) {
1615
1619
  return event.isComposing || event.keyCode === 229;
1616
1620
  }
@@ -1621,16 +1625,55 @@ function ChatInput({
1621
1625
  value,
1622
1626
  onValueChange,
1623
1627
  onSend,
1628
+ onAppend,
1624
1629
  onStop,
1625
1630
  isStreaming,
1626
1631
  isStopping = false,
1627
1632
  placeholder = "\u8F93\u5165\u6D88\u606F\u2026",
1628
- className
1633
+ className,
1634
+ queueKey
1629
1635
  }) {
1630
1636
  const trimmed = value.trim();
1631
- const canSend = trimmed.length > 0 && !isStreaming;
1637
+ const [sendMode, setSendMode] = useState5("direct");
1638
+ const [promptQueue, setPromptQueue] = useState5([]);
1639
+ const queueSendingRef = useRef5(false);
1640
+ const queueBlockedRef = useRef5(false);
1641
+ const previousQueueKeyRef = useRef5(queueKey);
1642
+ useEffect5(() => {
1643
+ if (previousQueueKeyRef.current === queueKey) return;
1644
+ previousQueueKeyRef.current = queueKey;
1645
+ setPromptQueue([]);
1646
+ queueBlockedRef.current = false;
1647
+ }, [queueKey]);
1648
+ const canSend = trimmed.length > 0 && (!isStreaming || sendMode === "queue");
1649
+ useEffect5(() => {
1650
+ if (isStreaming) {
1651
+ queueBlockedRef.current = false;
1652
+ return;
1653
+ }
1654
+ if (queueBlockedRef.current || promptQueue.length === 0 || queueSendingRef.current) return;
1655
+ const next = promptQueue[0];
1656
+ queueSendingRef.current = true;
1657
+ Promise.resolve().then(() => onSend(next)).then((accepted) => {
1658
+ if (accepted) setPromptQueue((current) => current.slice(1));
1659
+ else queueBlockedRef.current = true;
1660
+ }).finally(() => {
1661
+ queueSendingRef.current = false;
1662
+ });
1663
+ }, [isStreaming, onSend, promptQueue]);
1632
1664
  const handleSend = async () => {
1633
1665
  if (!canSend) return;
1666
+ if (isStreaming && sendMode === "queue") {
1667
+ setPromptQueue((current) => [...current, trimmed]);
1668
+ onValueChange("");
1669
+ return;
1670
+ }
1671
+ if (isStreaming && sendMode === "direct") {
1672
+ if (!onAppend) return;
1673
+ onAppend(trimmed);
1674
+ onValueChange("");
1675
+ return;
1676
+ }
1634
1677
  const accepted = await onSend(trimmed);
1635
1678
  if (!accepted) return;
1636
1679
  onValueChange("");
@@ -1646,61 +1689,73 @@ function ChatInput({
1646
1689
  void handleSend();
1647
1690
  }
1648
1691
  };
1649
- return /* @__PURE__ */ jsx5("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: /* @__PURE__ */ jsxs4("div", { className: "blade-chat-input-inner mx-auto flex max-w-[748px] items-end gap-2 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-2", children: [
1650
- /* @__PURE__ */ jsx5(
1651
- "textarea",
1652
- {
1653
- value,
1654
- onChange: (event) => onValueChange(event.target.value),
1655
- onKeyDown: handleKeyDown,
1656
- onInput: (event) => {
1657
- const el = event.currentTarget;
1658
- el.style.height = "auto";
1659
- el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
1660
- },
1661
- rows: 1,
1662
- placeholder,
1663
- "aria-label": "\u804A\u5929\u8F93\u5165",
1664
- className: "blade-chat-textarea max-h-48 min-h-[28px] flex-1 resize-none bg-transparent py-1 text-sm leading-6 text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.6)]"
1665
- }
1666
- ),
1667
- isStreaming ? /* @__PURE__ */ jsx5(
1668
- "button",
1669
- {
1670
- type: "button",
1671
- onClick: onStop,
1672
- disabled: isStopping,
1673
- "aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
1674
- title: isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
1675
- className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--muted))] text-[hsl(var(--foreground))] transition-opacity hover:opacity-90 disabled:opacity-60",
1676
- children: isStopping ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(Square, { size: 12, fill: "currentColor" })
1677
- }
1678
- ) : /* @__PURE__ */ jsx5(
1679
- "button",
1680
- {
1681
- type: "button",
1682
- onClick: handleSend,
1683
- disabled: !canSend,
1684
- "aria-label": "\u53D1\u9001\u6D88\u606F",
1685
- title: "\u53D1\u9001\u6D88\u606F",
1686
- className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",
1687
- children: /* @__PURE__ */ jsx5(ArrowUp, { size: 15 })
1688
- }
1689
- )
1690
- ] }) });
1692
+ return /* @__PURE__ */ jsxs4("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: [
1693
+ /* @__PURE__ */ jsxs4("div", { className: "mx-auto mb-2 flex max-w-[748px] items-center justify-between px-1 text-xs text-[hsl(var(--muted-foreground))]", children: [
1694
+ /* @__PURE__ */ jsxs4("fieldset", { className: "flex items-center gap-1 rounded-md border border-[hsl(var(--border))] p-0.5", children: [
1695
+ /* @__PURE__ */ jsx5("legend", { className: "sr-only", children: "\u53D1\u9001\u65B9\u5F0F" }),
1696
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setSendMode("direct"), "aria-pressed": sendMode === "direct", disabled: isStreaming && !onAppend, className: `rounded px-2 py-1 ${sendMode === "direct" ? "bg-[hsl(var(--accent))] text-[hsl(var(--foreground))]" : ""}`, children: "\u76F4\u63A5\u63D2\u5165" }),
1697
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setSendMode("queue"), "aria-pressed": sendMode === "queue", className: `rounded px-2 py-1 ${sendMode === "queue" ? "bg-[hsl(var(--accent))] text-[hsl(var(--foreground))]" : ""}`, children: "\u6392\u961F\u6267\u884C" })
1698
+ ] }),
1699
+ promptQueue.length > 0 ? /* @__PURE__ */ jsxs4("details", { className: "relative", children: [
1700
+ /* @__PURE__ */ jsxs4("summary", { className: "cursor-pointer list-none rounded px-2 py-1 hover:bg-[hsl(var(--accent))]", children: [
1701
+ "\u5F85\u6267\u884C ",
1702
+ promptQueue.length,
1703
+ " \u6761"
1704
+ ] }),
1705
+ /* @__PURE__ */ jsx5("div", { className: "absolute bottom-full right-0 z-20 mb-2 w-64 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-2 shadow-lg", children: promptQueue.map((item, index) => /* @__PURE__ */ jsxs4("div", { className: "flex gap-2 border-b border-[hsl(var(--border))] py-2 last:border-0", children: [
1706
+ /* @__PURE__ */ jsx5("span", { className: "min-w-0 flex-1 truncate", children: item }),
1707
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setPromptQueue((current) => current.filter((_, i) => i !== index)), children: "\u53D6\u6D88" })
1708
+ ] }, `${index}-${item}`)) })
1709
+ ] }) : null
1710
+ ] }),
1711
+ /* @__PURE__ */ jsxs4("div", { className: "blade-chat-input-inner mx-auto flex max-w-[748px] items-end gap-2 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-2", children: [
1712
+ /* @__PURE__ */ jsx5(
1713
+ "textarea",
1714
+ {
1715
+ value,
1716
+ onChange: (event) => onValueChange(event.target.value),
1717
+ onKeyDown: handleKeyDown,
1718
+ onInput: (event) => {
1719
+ const el = event.currentTarget;
1720
+ el.style.height = "auto";
1721
+ el.style.height = `${Math.min(el.scrollHeight, 192)}px`;
1722
+ },
1723
+ rows: 1,
1724
+ placeholder,
1725
+ "aria-label": "\u804A\u5929\u8F93\u5165",
1726
+ className: "blade-chat-textarea max-h-48 min-h-[28px] flex-1 resize-none bg-transparent py-1 text-sm leading-6 text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.6)]"
1727
+ }
1728
+ ),
1729
+ isStreaming ? /* @__PURE__ */ jsxs4(Fragment, { children: [
1730
+ sendMode === "queue" ? /* @__PURE__ */ jsx5("button", { type: "button", onClick: handleSend, disabled: !canSend, "aria-label": "\u52A0\u5165\u6392\u961F", title: "\u52A0\u5165\u6392\u961F", className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] disabled:opacity-40", children: /* @__PURE__ */ jsx5(ArrowUp, { size: 15 }) }) : null,
1731
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: onStop, disabled: isStopping, "aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D", title: isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D", className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--muted))] text-[hsl(var(--foreground))] transition-opacity hover:opacity-90 disabled:opacity-60", children: isStopping ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(Square, { size: 12, fill: "currentColor" }) })
1732
+ ] }) : /* @__PURE__ */ jsx5(
1733
+ "button",
1734
+ {
1735
+ type: "button",
1736
+ onClick: handleSend,
1737
+ disabled: !canSend,
1738
+ "aria-label": "\u53D1\u9001\u6D88\u606F",
1739
+ title: "\u53D1\u9001\u6D88\u606F",
1740
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",
1741
+ children: /* @__PURE__ */ jsx5(ArrowUp, { size: 15 })
1742
+ }
1743
+ )
1744
+ ] })
1745
+ ] });
1691
1746
  }
1692
1747
 
1693
1748
  // src/components/ConnectionBanner.tsx
1694
- import { useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
1749
+ import { useEffect as useEffect6, useRef as useRef6, useState as useState6 } from "react";
1695
1750
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1696
1751
  var CONNECTION_NOTICE_DELAY_MS = 3e3;
1697
1752
  var CONNECTION_ERROR_DELAY_MS = 15e3;
1698
1753
  function useConnectionNoticePhase(connected) {
1699
- const [phase, setPhase] = useState5("hidden");
1700
- const connectedRef = useRef5(connected);
1701
- const timersRef = useRef5([]);
1754
+ const [phase, setPhase] = useState6("hidden");
1755
+ const connectedRef = useRef6(connected);
1756
+ const timersRef = useRef6([]);
1702
1757
  connectedRef.current = connected;
1703
- useEffect5(() => {
1758
+ useEffect6(() => {
1704
1759
  const clearTimers = () => {
1705
1760
  for (const timer of timersRef.current) clearTimeout(timer);
1706
1761
  timersRef.current = [];
@@ -1740,7 +1795,7 @@ function useConnectionNoticePhase(connected) {
1740
1795
  return phase;
1741
1796
  }
1742
1797
  function ConnectionBanner({ connection, className }) {
1743
- const hasConnectedRef = useRef5(connection === "connected" || connection === "reconnecting");
1798
+ const hasConnectedRef = useRef6(connection === "connected" || connection === "reconnecting");
1744
1799
  if (connection === "connected") hasConnectedRef.current = true;
1745
1800
  const connected = connection === "connected";
1746
1801
  const phase = useConnectionNoticePhase(connected);
@@ -1767,10 +1822,10 @@ function ConnectionBanner({ connection, className }) {
1767
1822
 
1768
1823
  // src/components/MessageList.tsx
1769
1824
  import { isHiddenInternalMessage } from "@blade-hq/agent-client";
1770
- import { useCallback as useCallback7, useEffect as useEffect11, useMemo as useMemo7, useRef as useRef12, useState as useState13 } from "react";
1825
+ import { useCallback as useCallback7, useEffect as useEffect12, useMemo as useMemo7, useRef as useRef13, useState as useState14 } from "react";
1771
1826
 
1772
1827
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
1773
- import { useCallback as useCallback4, useMemo as useMemo3, useRef as useRef6, useState as useState6 } from "react";
1828
+ import { useCallback as useCallback4, useMemo as useMemo3, useRef as useRef7, useState as useState7 } from "react";
1774
1829
  var DEFAULT_SPRING_ANIMATION = {
1775
1830
  /**
1776
1831
  * A value from 0 to 1, on how much to damp the animation.
@@ -1807,10 +1862,10 @@ globalThis.document?.addEventListener("click", () => {
1807
1862
  mouseDown = false;
1808
1863
  });
1809
1864
  var useStickToBottom = (options = {}) => {
1810
- const [escapedFromLock, updateEscapedFromLock] = useState6(false);
1811
- const [isAtBottom, updateIsAtBottom] = useState6(options.initial !== false);
1812
- const [isNearBottom, setIsNearBottom] = useState6(false);
1813
- const optionsRef = useRef6(null);
1865
+ const [escapedFromLock, updateEscapedFromLock] = useState7(false);
1866
+ const [isAtBottom, updateIsAtBottom] = useState7(options.initial !== false);
1867
+ const [isNearBottom, setIsNearBottom] = useState7(false);
1868
+ const optionsRef = useRef7(null);
1814
1869
  optionsRef.current = options;
1815
1870
  const isSelecting = useCallback4(() => {
1816
1871
  if (!mouseDown) {
@@ -2114,11 +2169,11 @@ function mergeAnimations(...animations) {
2114
2169
 
2115
2170
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
2116
2171
  import * as React from "react";
2117
- import { createContext as createContext2, useContext as useContext2, useEffect as useEffect6, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef7 } from "react";
2172
+ import { createContext as createContext2, useContext as useContext2, useEffect as useEffect7, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef8 } from "react";
2118
2173
  var StickToBottomContext = createContext2(null);
2119
- var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect6;
2174
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect7;
2120
2175
  function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
2121
- const customTargetScrollTop = useRef7(null);
2176
+ const customTargetScrollTop = useRef8(null);
2122
2177
  const targetScrollTop = React.useCallback((target, elements) => {
2123
2178
  const get = context?.targetScrollTop ?? currentTargetScrollTop;
2124
2179
  return get?.(target, elements) ?? target;
@@ -2200,10 +2255,10 @@ import {
2200
2255
  getTextContent,
2201
2256
  normalizeMessageContent
2202
2257
  } from "@blade-hq/agent-client";
2203
- import { useEffect as useEffect9, useRef as useRef10, useState as useState11 } from "react";
2258
+ import { useEffect as useEffect10, useRef as useRef11, useState as useState12 } from "react";
2204
2259
 
2205
2260
  // src/components/AgentLoopBlock.tsx
2206
- import { useState as useState7 } from "react";
2261
+ import { useState as useState8 } from "react";
2207
2262
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2208
2263
  function parseAgentDescription(argumentsJson) {
2209
2264
  try {
@@ -2214,7 +2269,7 @@ function parseAgentDescription(argumentsJson) {
2214
2269
  }
2215
2270
  }
2216
2271
  function AgentLoopBlock({ toolCall }) {
2217
- const [expanded, setExpanded] = useState7(false);
2272
+ const [expanded, setExpanded] = useState8(false);
2218
2273
  const description = parseAgentDescription(toolCall.arguments);
2219
2274
  const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
2220
2275
  const failed = toolCall.status === "error" || toolCall.status === "cancelled";
@@ -2248,8 +2303,9 @@ function AgentLoopBlock({ toolCall }) {
2248
2303
  ChevronRight,
2249
2304
  {
2250
2305
  size: 14,
2306
+ style: { transitionDuration: "260ms", transitionTimingFunction: "cubic-bezier(0.25, 0.1, 0.25, 1)" },
2251
2307
  className: cn(
2252
- "shrink-0 transition-transform duration-300",
2308
+ "shrink-0 transition-transform",
2253
2309
  expanded && "rotate-90"
2254
2310
  ),
2255
2311
  "aria-hidden": "true"
@@ -2264,10 +2320,10 @@ function AgentLoopBlock({ toolCall }) {
2264
2320
 
2265
2321
  // src/components/MarkdownContent.tsx
2266
2322
  import {
2267
- useEffect as useEffect7,
2323
+ useEffect as useEffect8,
2268
2324
  useMemo as useMemo5,
2269
- useRef as useRef8,
2270
- useState as useState8
2325
+ useRef as useRef9,
2326
+ useState as useState9
2271
2327
  } from "react";
2272
2328
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2273
2329
  var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
@@ -2284,10 +2340,10 @@ function normalizeAdjacentUrlFormatting(value) {
2284
2340
  return normalized.replace(/blade-url-protected-(\d+)-marker/g, (_, index) => protectedSegments[Number(index)]);
2285
2341
  }
2286
2342
  function CodeBlockPre({ children, node: _node, ...props }) {
2287
- const preRef = useRef8(null);
2288
- const [copied, setCopied] = useState8(false);
2289
- const [language, setLanguage] = useState8("");
2290
- useEffect7(() => {
2343
+ const preRef = useRef9(null);
2344
+ const [copied, setCopied] = useState9(false);
2345
+ const [language, setLanguage] = useState9("");
2346
+ useEffect8(() => {
2291
2347
  const codeEl = preRef.current?.querySelector("code");
2292
2348
  setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
2293
2349
  }, []);
@@ -2358,10 +2414,10 @@ function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
2358
2414
  }
2359
2415
 
2360
2416
  // src/components/ToolCallBlock.tsx
2361
- import { useState as useState10 } from "react";
2417
+ import { useState as useState11 } from "react";
2362
2418
 
2363
2419
  // src/components/AskUserQuestionBlock.tsx
2364
- import { useEffect as useEffect8, useMemo as useMemo6, useRef as useRef9, useState as useState9 } from "react";
2420
+ import { useEffect as useEffect9, useMemo as useMemo6, useRef as useRef10, useState as useState10 } from "react";
2365
2421
  import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2366
2422
  var CUSTOM_TEXTAREA_MAX_HEIGHT = 160;
2367
2423
  function resizeCustomTextarea(textarea) {
@@ -2370,12 +2426,12 @@ function resizeCustomTextarea(textarea) {
2370
2426
  textarea.style.overflowY = textarea.scrollHeight > CUSTOM_TEXTAREA_MAX_HEIGHT ? "auto" : "hidden";
2371
2427
  }
2372
2428
  function useAutoResizeTextarea(value) {
2373
- const textareaRef = useRef9(null);
2374
- useEffect8(() => {
2429
+ const textareaRef = useRef10(null);
2430
+ useEffect9(() => {
2375
2431
  const textarea = textareaRef.current;
2376
2432
  if (textarea?.value === value) resizeCustomTextarea(textarea);
2377
2433
  }, [value]);
2378
- useEffect8(() => {
2434
+ useEffect9(() => {
2379
2435
  const textarea = textareaRef.current;
2380
2436
  if (!textarea || typeof ResizeObserver === "undefined") return;
2381
2437
  let previousWidth = textarea.clientWidth;
@@ -2400,12 +2456,12 @@ function AskUserQuestionBlock({
2400
2456
  answerData,
2401
2457
  onAnswer
2402
2458
  }) {
2403
- const [selections, setSelections] = useState9(/* @__PURE__ */ new Map());
2404
- const [customTexts, setCustomTexts] = useState9(/* @__PURE__ */ new Map());
2405
- const [usingCustom, setUsingCustom] = useState9(/* @__PURE__ */ new Set());
2406
- const [note, setNote] = useState9("");
2407
- const [submitted, setSubmitted] = useState9(false);
2408
- useEffect8(() => {
2459
+ const [selections, setSelections] = useState10(/* @__PURE__ */ new Map());
2460
+ const [customTexts, setCustomTexts] = useState10(/* @__PURE__ */ new Map());
2461
+ const [usingCustom, setUsingCustom] = useState10(/* @__PURE__ */ new Set());
2462
+ const [note, setNote] = useState10("");
2463
+ const [submitted, setSubmitted] = useState10(false);
2464
+ useEffect9(() => {
2409
2465
  if (sessionStatus === "failed" || sessionStatus === "interrupted") {
2410
2466
  setSubmitted(false);
2411
2467
  }
@@ -2794,7 +2850,7 @@ function normalizeOptionItem(value) {
2794
2850
  }
2795
2851
 
2796
2852
  // src/components/ToolCallBlock.tsx
2797
- import { Fragment, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2853
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2798
2854
  function resolveAskQuestionState({
2799
2855
  toolStatus,
2800
2856
  hasAnswerData,
@@ -2816,12 +2872,12 @@ function ToolCallBlock({
2816
2872
  isActiveQuestion,
2817
2873
  renderer
2818
2874
  }) {
2819
- const [expanded, setExpanded] = useState10(false);
2875
+ const [expanded, setExpanded] = useState11(false);
2820
2876
  const normalizedName = formatToolName(toolCall.name);
2821
2877
  if (renderer) {
2822
2878
  const custom = renderer(toolCall);
2823
2879
  if (custom !== null && custom !== void 0) {
2824
- return /* @__PURE__ */ jsx11(Fragment, { children: custom });
2880
+ return /* @__PURE__ */ jsx11(Fragment2, { children: custom });
2825
2881
  }
2826
2882
  }
2827
2883
  if (normalizedName === "AskUserQuestion") {
@@ -2904,7 +2960,7 @@ function ToolCallBlock({
2904
2960
  /* @__PURE__ */ jsx11("div", { className: "mb-3 font-mono text-[11px] text-[hsl(var(--foreground))]", children: normalizedName }),
2905
2961
  /* @__PURE__ */ jsx11("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u53C2\u6570" }),
2906
2962
  /* @__PURE__ */ jsx11("pre", { className: "overflow-x-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolArgs(toolCall.arguments) }),
2907
- toolCall.result != null && /* @__PURE__ */ jsxs9(Fragment, { children: [
2963
+ toolCall.result != null && /* @__PURE__ */ jsxs9(Fragment2, { children: [
2908
2964
  /* @__PURE__ */ jsx11("div", { className: "mb-1 mt-3 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
2909
2965
  /* @__PURE__ */ jsx11("pre", { className: "max-h-[400px] overflow-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolResult(toolCall.result) })
2910
2966
  ] })
@@ -2926,7 +2982,7 @@ function buildAskUserPayload(argumentsJson) {
2926
2982
  // src/components/AssistantTurnBlock.tsx
2927
2983
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2928
2984
  function ThinkingBlock({ reasoning, isStreaming }) {
2929
- const [open, setOpen] = useState11(false);
2985
+ const [open, setOpen] = useState12(false);
2930
2986
  if (!isStreaming) return null;
2931
2987
  return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-thinking text-xs", children: [
2932
2988
  /* @__PURE__ */ jsxs10(
@@ -3196,12 +3252,12 @@ function AssistantTurnBlock({
3196
3252
  )
3197
3253
  );
3198
3254
  const activeQuestionId = questionToolCalls.filter((toolCall) => toolCall.status === "pending").at(-1)?.id;
3199
- const [displayMode, setDisplayMode] = useState11(
3255
+ const [displayMode, setDisplayMode] = useState12(
3200
3256
  () => isStreaming || hasActionableToolCall ? "detail" : "compact"
3201
3257
  );
3202
- const userSelectedDisplayModeRef = useRef10(false);
3203
- const wasStreamingRef = useRef10(isStreaming);
3204
- useEffect9(() => {
3258
+ const userSelectedDisplayModeRef = useRef11(false);
3259
+ const wasStreamingRef = useRef11(isStreaming);
3260
+ useEffect10(() => {
3205
3261
  if (wasStreamingRef.current && !isStreaming && !userSelectedDisplayModeRef.current) {
3206
3262
  setDisplayMode(hasActionableToolCall ? "detail" : "compact");
3207
3263
  }
@@ -3209,11 +3265,11 @@ function AssistantTurnBlock({
3209
3265
  }, [hasActionableToolCall, isStreaming]);
3210
3266
  const effectiveMode = resolveTurnDisplayMode({ isStreaming, displayMode });
3211
3267
  const executionDurationMs = getExecutionDurationMs({ messages, isStreaming });
3212
- const [clock, setClock] = useState11(() => Date.now());
3268
+ const [clock, setClock] = useState12(() => Date.now());
3213
3269
  const hasLiveStartTime = messages.some(
3214
3270
  (message) => message.timestamp != null && Number.isFinite(Date.parse(message.timestamp))
3215
3271
  );
3216
- useEffect9(() => {
3272
+ useEffect10(() => {
3217
3273
  if (!isStreaming || !hasLiveStartTime) return;
3218
3274
  const timer = window.setInterval(() => setClock(Date.now()), 1e3);
3219
3275
  return () => window.clearInterval(timer);
@@ -3294,8 +3350,9 @@ function AssistantTurnBlock({
3294
3350
  ChevronRight,
3295
3351
  {
3296
3352
  size: 14,
3353
+ style: { transitionDuration: "260ms", transitionTimingFunction: "cubic-bezier(0.25, 0.1, 0.25, 1)" },
3297
3354
  className: cn(
3298
- "shrink-0 transition-transform duration-300",
3355
+ "shrink-0 transition-transform",
3299
3356
  effectiveMode === "detail" && "rotate-90"
3300
3357
  ),
3301
3358
  "aria-hidden": "true"
@@ -3387,7 +3444,7 @@ function collectMemoryRefs(messages) {
3387
3444
  return [...refs.values()];
3388
3445
  }
3389
3446
  function MemoryRefsHint({ refs }) {
3390
- const [expanded, setExpanded] = useState11(false);
3447
+ const [expanded, setExpanded] = useState12(false);
3391
3448
  const label = refs.some((ref) => ref.skill_name) ? "\u53C2\u8003\u4E86\u8BE5\u6280\u80FD\u7684\u5386\u53F2\u7ECF\u9A8C" : "\u53C2\u8003\u4E86\u5386\u53F2\u7ECF\u9A8C";
3392
3449
  return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-memory-refs ml-1 w-full max-w-[680px]", children: [
3393
3450
  /* @__PURE__ */ jsxs10("button", { type: "button", onClick: () => setExpanded((value) => !value), className: "inline-flex h-8 items-center gap-1.5 rounded-lg border border-[hsl(var(--primary)/0.22)] bg-[hsl(var(--primary)/0.07)] px-3 text-xs font-medium text-[hsl(var(--primary))]", children: [
@@ -3522,8 +3579,8 @@ var RenderErrorBoundary = class extends Component {
3522
3579
  };
3523
3580
 
3524
3581
  // src/components/PostChatFollowupBlock.tsx
3525
- import { useCallback as useCallback6, useEffect as useEffect10, useRef as useRef11, useState as useState12 } from "react";
3526
- import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
3582
+ import { useCallback as useCallback6, useEffect as useEffect11, useRef as useRef12, useState as useState13 } from "react";
3583
+ import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
3527
3584
  function emitInteraction(callback, event) {
3528
3585
  try {
3529
3586
  callback?.(event);
@@ -3542,7 +3599,7 @@ function ArtifactCard({
3542
3599
  onArtifactOpened
3543
3600
  }) {
3544
3601
  const client = useBladeClient();
3545
- const [downloading, setDownloading] = useState12(false);
3602
+ const [downloading, setDownloading] = useState13(false);
3546
3603
  const name = artifact.label || basename(artifact.target);
3547
3604
  if (artifact.kind === "link") {
3548
3605
  return /* @__PURE__ */ jsxs12(
@@ -3648,15 +3705,15 @@ function ResultFeedback({
3648
3705
  onFeedbackSaved
3649
3706
  }) {
3650
3707
  const client = useBladeClient();
3651
- const [saved, setSaved] = useState12(savedFeedback ?? null);
3652
- const [helpful, setHelpful] = useState12(savedFeedback?.helpful ?? null);
3653
- const [reason, setReason] = useState12(savedFeedback?.reason ?? null);
3654
- const [saving, setSaving] = useState12(false);
3655
- const [saveError, setSaveError] = useState12(false);
3656
- const reportedShown = useRef11(false);
3657
- const latestChoice = useRef11(null);
3708
+ const [saved, setSaved] = useState13(savedFeedback ?? null);
3709
+ const [helpful, setHelpful] = useState13(savedFeedback?.helpful ?? null);
3710
+ const [reason, setReason] = useState13(savedFeedback?.reason ?? null);
3711
+ const [saving, setSaving] = useState13(false);
3712
+ const [saveError, setSaveError] = useState13(false);
3713
+ const reportedShown = useRef12(false);
3714
+ const latestChoice = useRef12(null);
3658
3715
  const eligible = followup.feedback_eligible === true && Boolean(sessionId) && !isViewer;
3659
- useEffect10(() => {
3716
+ useEffect11(() => {
3660
3717
  if (!eligible || reportedShown.current) return;
3661
3718
  reportedShown.current = true;
3662
3719
  emitInteraction(onInteraction, {
@@ -3665,7 +3722,7 @@ function ResultFeedback({
3665
3722
  assistantEntryId: followup.assistant_entry_id
3666
3723
  });
3667
3724
  }, [eligible, followup.assistant_entry_id, onInteraction, sessionId]);
3668
- useEffect10(() => {
3725
+ useEffect11(() => {
3669
3726
  if (!savedFeedback || latestChoice.current) return;
3670
3727
  setSaved(savedFeedback);
3671
3728
  setHelpful(savedFeedback.helpful);
@@ -3777,14 +3834,14 @@ function PostChatFollowupBlock({
3777
3834
  savedFeedback,
3778
3835
  onFeedbackSaved
3779
3836
  }) {
3780
- const [expanded, setExpanded] = useState12(false);
3781
- const adopted = useRef11(/* @__PURE__ */ new Set());
3782
- const reportedSuggestions = useRef11(false);
3783
- const reportedArtifacts = useRef11(/* @__PURE__ */ new Set());
3784
- const openedArtifacts = useRef11(/* @__PURE__ */ new Set());
3837
+ const [expanded, setExpanded] = useState13(false);
3838
+ const adopted = useRef12(/* @__PURE__ */ new Set());
3839
+ const reportedSuggestions = useRef12(false);
3840
+ const reportedArtifacts = useRef12(/* @__PURE__ */ new Set());
3841
+ const openedArtifacts = useRef12(/* @__PURE__ */ new Set());
3785
3842
  const artifacts = followup.final_artifacts ?? [];
3786
3843
  const visibleArtifacts = expanded ? artifacts : artifacts.slice(0, 3);
3787
- useEffect10(() => {
3844
+ useEffect11(() => {
3788
3845
  if (!reportedSuggestions.current && followup.suggestions.length > 0) {
3789
3846
  reportedSuggestions.current = true;
3790
3847
  emitInteraction(onInteraction, {
@@ -3837,7 +3894,7 @@ function PostChatFollowupBlock({
3837
3894
  "\u672C\u8F6E\u5C0F\u7ED3"
3838
3895
  ] }),
3839
3896
  followup.recaption ? /* @__PURE__ */ jsx14("p", { className: "text-[13px] leading-5", children: followup.recaption }) : null,
3840
- artifacts.length > 0 ? /* @__PURE__ */ jsxs12(Fragment2, { children: [
3897
+ artifacts.length > 0 ? /* @__PURE__ */ jsxs12(Fragment3, { children: [
3841
3898
  /* @__PURE__ */ jsx14("div", { className: "grid max-w-full grid-cols-3 gap-1.5", children: visibleArtifacts.map((artifact, artifactIndex) => /* @__PURE__ */ jsx14(
3842
3899
  ArtifactCard,
3843
3900
  {
@@ -4095,8 +4152,16 @@ function MessageList({
4095
4152
  resultFeedbackByEntry = /* @__PURE__ */ new Map(),
4096
4153
  onResultFeedbackSaved
4097
4154
  }) {
4098
- const userMessages = messages.filter((message) => isUserMessage(message));
4155
+ const visibleRootMessages = messages.filter((message) => {
4156
+ if ((message.loop_name ?? "root") !== "root") return false;
4157
+ if (isHiddenInternalMessage(message)) return false;
4158
+ if (message.kind === "context") return false;
4159
+ return message.role !== "tool" || getPlanningDividerKind(message) !== null;
4160
+ });
4161
+ const userMessages = visibleRootMessages.filter((message) => isUserMessage(message));
4099
4162
  const latestUserMessage = userMessages.at(-1);
4163
+ const latestPromptText = latestUserMessage ? (typeof latestUserMessage.content === "string" ? latestUserMessage.content : latestUserMessage.content.filter((part) => part.type === "text").map((part) => part.text).join("")).replace(/\s+/g, " ").trim() : "";
4164
+ const latestPromptPreview = latestPromptText.length > 80 ? `${latestPromptText.slice(0, 80)}\u2026` : latestPromptText;
4100
4165
  const shouldPinLatestUser = latestUserMessage != null && (latestUserMessage.entry_id == null || latestUserMessage.entry_id.startsWith("local-user-"));
4101
4166
  const renderBlocks = useMemo7(() => {
4102
4167
  const visible = messages.filter((message) => {
@@ -4181,78 +4246,90 @@ function MessageList({
4181
4246
  initial: "instant",
4182
4247
  resize: "instant",
4183
4248
  children: [
4184
- /* @__PURE__ */ jsx17(StickToBottom.Content, { className: "blade-chat-messages-scroll", children: /* @__PURE__ */ jsx17("div", { className: "blade-chat-messages-content mx-auto max-w-[748px]", children: /* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 flex-col", children: [
4185
- renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs15("div", { className: "blade-chat-empty", children: [
4186
- /* @__PURE__ */ jsx17(MessageSquare, { size: 40, strokeWidth: 1.5 }),
4187
- /* @__PURE__ */ jsx17("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
4188
- /* @__PURE__ */ jsx17("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
4189
- ] }) : renderBlocks.map((block) => {
4190
- if (block.type === "message") {
4191
- return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.message.entry_id, children: isUserMessage(block.message) ? /* @__PURE__ */ jsx17(UserMessageBubble, { message: block.message }) : isErrorMessage(block.message) ? /* @__PURE__ */ jsx17(ErrorMessageBlock, { message: block.message }) : null }, block.key);
4192
- }
4193
- if (block.type === "assistant_turn") {
4194
- const blockFeedback = block.messages.map(
4195
- (message) => message.entry_id ? resultFeedbackByEntry.get(message.entry_id) : void 0
4196
- ).find((feedback) => feedback != null);
4197
- const hasActiveFollowup = Boolean(
4198
- postChatFollowup && block.messages.some(
4199
- (message) => message.entry_id === postChatFollowup.assistant_entry_id
4200
- )
4201
- );
4202
- return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsxs15(
4203
- RenderErrorBoundary,
4204
- {
4205
- label: "\u52A9\u624B\u6D88\u606F",
4206
- details: block.key,
4207
- resetKey: getMessageResetSignature(block.messages),
4208
- children: [
4209
- /* @__PURE__ */ jsx17(
4210
- AssistantTurnBlock,
4211
- {
4212
- messages: block.messages,
4213
- isStreaming: block.isStreaming,
4214
- askAnswers,
4215
- onAnswer,
4216
- sessionStatus,
4217
- toolCallRenderer,
4218
- hidePlanUpdateTools,
4219
- sessionId
4220
- }
4221
- ),
4222
- blockFeedback && !hasActiveFollowup ? /* @__PURE__ */ jsx17(HistoricalResultFeedback, { feedback: blockFeedback }) : null,
4223
- hasActiveFollowup && postChatFollowup ? /* @__PURE__ */ jsx17(
4224
- PostChatFollowupBlock,
4225
- {
4226
- followup: postChatFollowup,
4227
- sessionId,
4228
- onSuggestion,
4229
- isViewer,
4230
- onInteraction: onFollowupInteraction,
4231
- savedFeedback: blockFeedback,
4232
- onFeedbackSaved: onResultFeedbackSaved
4233
- }
4234
- ) : null
4235
- ]
4236
- }
4237
- ) }, block.key);
4238
- }
4239
- if (block.type === "compaction") {
4240
- return /* @__PURE__ */ jsxs15(
4241
- "div",
4242
- {
4243
- className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
4244
- children: [
4245
- /* @__PURE__ */ jsx17(Layers, { size: 12 }),
4246
- /* @__PURE__ */ jsx17("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
4247
- ]
4248
- },
4249
- block.key
4250
- );
4249
+ /* @__PURE__ */ jsx17(StickToBottom.Content, { className: "blade-chat-messages-scroll", children: /* @__PURE__ */ jsxs15("div", { className: "blade-chat-messages-content mx-auto max-w-[748px]", children: [
4250
+ isStreaming && latestUserMessage && latestPromptPreview ? /* @__PURE__ */ jsx17(
4251
+ "button",
4252
+ {
4253
+ type: "button",
4254
+ className: "sticky top-0 z-20 mb-4 w-full truncate rounded-2xl border border-[hsl(var(--primary)/0.2)] bg-[hsl(var(--background)/0.92)] px-4 py-3 text-left text-sm font-medium shadow-sm backdrop-blur",
4255
+ onClick: () => Array.from(document.querySelectorAll("[data-entry-id]")).find((el) => el.dataset.entryId === latestUserMessage.entry_id)?.scrollIntoView({ behavior: "smooth", block: "start" }),
4256
+ "aria-label": "\u8DF3\u8F6C\u5230\u5F53\u524D\u63D0\u95EE",
4257
+ children: latestPromptPreview
4251
4258
  }
4252
- return /* @__PURE__ */ jsx17(PlanningDivider, { kind: block.kind }, block.key);
4253
- }),
4254
- sessionStatus === "interrupted" && !isStreaming ? /* @__PURE__ */ jsx17("div", { className: "flex", children: /* @__PURE__ */ jsx17("div", { className: "rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }) }) : null
4255
- ] }) }) }),
4259
+ ) : null,
4260
+ /* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 flex-col", children: [
4261
+ renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs15("div", { className: "blade-chat-empty", children: [
4262
+ /* @__PURE__ */ jsx17(MessageSquare, { size: 40, strokeWidth: 1.5 }),
4263
+ /* @__PURE__ */ jsx17("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
4264
+ /* @__PURE__ */ jsx17("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
4265
+ ] }) : renderBlocks.map((block) => {
4266
+ if (block.type === "message") {
4267
+ return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.message.entry_id, children: isUserMessage(block.message) ? /* @__PURE__ */ jsx17(UserMessageBubble, { message: block.message }) : isErrorMessage(block.message) ? /* @__PURE__ */ jsx17(ErrorMessageBlock, { message: block.message }) : null }, block.key);
4268
+ }
4269
+ if (block.type === "assistant_turn") {
4270
+ const blockFeedback = block.messages.map(
4271
+ (message) => message.entry_id ? resultFeedbackByEntry.get(message.entry_id) : void 0
4272
+ ).find((feedback) => feedback != null);
4273
+ const hasActiveFollowup = Boolean(
4274
+ postChatFollowup && block.messages.some(
4275
+ (message) => message.entry_id === postChatFollowup.assistant_entry_id
4276
+ )
4277
+ );
4278
+ return /* @__PURE__ */ jsx17("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsxs15(
4279
+ RenderErrorBoundary,
4280
+ {
4281
+ label: "\u52A9\u624B\u6D88\u606F",
4282
+ details: block.key,
4283
+ resetKey: getMessageResetSignature(block.messages),
4284
+ children: [
4285
+ /* @__PURE__ */ jsx17(
4286
+ AssistantTurnBlock,
4287
+ {
4288
+ messages: block.messages,
4289
+ isStreaming: block.isStreaming,
4290
+ askAnswers,
4291
+ onAnswer,
4292
+ sessionStatus,
4293
+ toolCallRenderer,
4294
+ hidePlanUpdateTools,
4295
+ sessionId
4296
+ }
4297
+ ),
4298
+ blockFeedback && !hasActiveFollowup ? /* @__PURE__ */ jsx17(HistoricalResultFeedback, { feedback: blockFeedback }) : null,
4299
+ hasActiveFollowup && postChatFollowup ? /* @__PURE__ */ jsx17(
4300
+ PostChatFollowupBlock,
4301
+ {
4302
+ followup: postChatFollowup,
4303
+ sessionId,
4304
+ onSuggestion,
4305
+ isViewer,
4306
+ onInteraction: onFollowupInteraction,
4307
+ savedFeedback: blockFeedback,
4308
+ onFeedbackSaved: onResultFeedbackSaved
4309
+ }
4310
+ ) : null
4311
+ ]
4312
+ }
4313
+ ) }, block.key);
4314
+ }
4315
+ if (block.type === "compaction") {
4316
+ return /* @__PURE__ */ jsxs15(
4317
+ "div",
4318
+ {
4319
+ className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
4320
+ children: [
4321
+ /* @__PURE__ */ jsx17(Layers, { size: 12 }),
4322
+ /* @__PURE__ */ jsx17("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
4323
+ ]
4324
+ },
4325
+ block.key
4326
+ );
4327
+ }
4328
+ return /* @__PURE__ */ jsx17(PlanningDivider, { kind: block.kind }, block.key);
4329
+ }),
4330
+ sessionStatus === "interrupted" && !isStreaming ? /* @__PURE__ */ jsx17("div", { className: "flex", children: /* @__PURE__ */ jsx17("div", { className: "rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }) }) : null
4331
+ ] })
4332
+ ] }) }),
4256
4333
  /* @__PURE__ */ jsx17(
4257
4334
  PinLatestUserMessage,
4258
4335
  {
@@ -4275,8 +4352,8 @@ function PinLatestUserMessage({
4275
4352
  targetKey
4276
4353
  }) {
4277
4354
  const { contentRef, scrollRef, scrollToBottom, stopScroll } = useStickToBottomContext();
4278
- const previousCountRef = useRef12(userMessageCount);
4279
- const spacerHeightRef = useRef12(0);
4355
+ const previousCountRef = useRef13(userMessageCount);
4356
+ const spacerHeightRef = useRef13(0);
4280
4357
  const getScrollElement = useCallback7(() => scrollRef.current, [scrollRef]);
4281
4358
  const getContentElement = useCallback7(() => contentRef.current, [contentRef]);
4282
4359
  const getTargetElement = useCallback7(() => {
@@ -4305,7 +4382,7 @@ function PinLatestUserMessage({
4305
4382
  stopAutoScroll: stopScroll,
4306
4383
  scrollToBottom
4307
4384
  });
4308
- useEffect11(() => {
4385
+ useEffect12(() => {
4309
4386
  if (userMessageCount > previousCountRef.current && !shouldPinLatestUser) {
4310
4387
  scrollToBottom("instant");
4311
4388
  }
@@ -4315,9 +4392,9 @@ function PinLatestUserMessage({
4315
4392
  }
4316
4393
  function ScrollToBottomButton() {
4317
4394
  const { isAtBottom, scrollToBottom } = useStickToBottomContext();
4318
- const [visible, setVisible] = useState13(false);
4319
- const hideTimerRef = useRef12(null);
4320
- useEffect11(() => {
4395
+ const [visible, setVisible] = useState14(false);
4396
+ const hideTimerRef = useRef13(null);
4397
+ useEffect12(() => {
4321
4398
  if (isAtBottom) {
4322
4399
  if (!hideTimerRef.current) {
4323
4400
  hideTimerRef.current = setTimeout(() => {
@@ -4394,6 +4471,7 @@ function ChatSurface({
4394
4471
  onInputChange,
4395
4472
  onSuggestion,
4396
4473
  onSend,
4474
+ onAppend,
4397
4475
  onStop,
4398
4476
  sessionStatus,
4399
4477
  askAnswers,
@@ -4462,10 +4540,12 @@ function ChatSurface({
4462
4540
  value: inputText,
4463
4541
  onValueChange: onInputChange,
4464
4542
  onSend,
4543
+ onAppend,
4465
4544
  onStop,
4466
4545
  isStreaming,
4467
4546
  isStopping,
4468
4547
  placeholder,
4548
+ queueKey: sessionId,
4469
4549
  className: classNames?.chatInput
4470
4550
  }
4471
4551
  ),
@@ -4476,13 +4556,13 @@ function ChatSurface({
4476
4556
  }
4477
4557
 
4478
4558
  // src/components/AgentChat.tsx
4479
- import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
4559
+ import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
4480
4560
  function isUnauthorizedError(error) {
4481
4561
  return error instanceof BladeApiError && error.status === 401;
4482
4562
  }
4483
4563
  function LoginCard({ client, onLoggedIn }) {
4484
- const [loggingIn, setLoggingIn] = useState14(false);
4485
- const [loginError, setLoginError] = useState14(null);
4564
+ const [loggingIn, setLoggingIn] = useState15(false);
4565
+ const [loginError, setLoginError] = useState15(null);
4486
4566
  const handleLogin = async () => {
4487
4567
  setLoggingIn(true);
4488
4568
  setLoginError(null);
@@ -4514,8 +4594,8 @@ function LoginCard({ client, onLoggedIn }) {
4514
4594
  }
4515
4595
  function AgentChat(props) {
4516
4596
  const client = useBladeClient();
4517
- const [attempt, setAttempt] = useState14(0);
4518
- const [needLogin, setNeedLogin] = useState14(() => !client.hasToken());
4597
+ const [attempt, setAttempt] = useState15(0);
4598
+ const [needLogin, setNeedLogin] = useState15(() => !client.hasToken());
4519
4599
  if (needLogin) {
4520
4600
  return /* @__PURE__ */ jsx19(
4521
4601
  "div",
@@ -4552,7 +4632,7 @@ function ChatSessionView({
4552
4632
  onUnauthorized
4553
4633
  }) {
4554
4634
  const client = useBladeClient();
4555
- const [planRevealRevisions, setPlanRevealRevisions] = useState14(
4635
+ const [planRevealRevisions, setPlanRevealRevisions] = useState15(
4556
4636
  () => /* @__PURE__ */ new Map()
4557
4637
  );
4558
4638
  const handleSessionConnected = useCallback8((connectedSession) => {
@@ -4573,12 +4653,12 @@ function ChatSessionView({
4573
4653
  onSessionConnected: handleSessionConnected
4574
4654
  });
4575
4655
  const replay = useReplay(session);
4576
- const [stopRequested, setStopRequested] = useState14(false);
4577
- const [inputText, setInputText] = useState14("");
4578
- const [resultFeedback, setResultFeedback] = useState14([]);
4656
+ const [stopRequested, setStopRequested] = useState15(false);
4657
+ const [inputText, setInputText] = useState15("");
4658
+ const [resultFeedback, setResultFeedback] = useState15([]);
4579
4659
  const resolvedSessionId = session?.sessionId;
4580
4660
  const isViewer = state?.viewerRole === "viewer";
4581
- useEffect12(() => {
4661
+ useEffect13(() => {
4582
4662
  setResultFeedback([]);
4583
4663
  if (!resolvedSessionId || isViewer) return;
4584
4664
  let cancelled = false;
@@ -4613,12 +4693,12 @@ function ChatSessionView({
4613
4693
  saved
4614
4694
  ]);
4615
4695
  }, []);
4616
- useEffect12(() => {
4696
+ useEffect13(() => {
4617
4697
  if (session) {
4618
4698
  onSessionReady?.(session);
4619
4699
  }
4620
4700
  }, [session, onSessionReady]);
4621
- useEffect12(() => {
4701
+ useEffect13(() => {
4622
4702
  if (!session) return;
4623
4703
  const offAttach = session.on("attachRequested", ({ label, content }) => {
4624
4704
  setInputText((prev) => `${prev ? `${prev}
@@ -4634,12 +4714,12 @@ ${content}`);
4634
4714
  offInsert();
4635
4715
  };
4636
4716
  }, [session]);
4637
- useEffect12(() => {
4717
+ useEffect13(() => {
4638
4718
  if (isUnauthorizedError(error)) {
4639
4719
  onUnauthorized();
4640
4720
  }
4641
4721
  }, [error, onUnauthorized]);
4642
- useEffect12(() => {
4722
+ useEffect13(() => {
4643
4723
  if (!session || !commands) return;
4644
4724
  const unsubscribes = Object.entries(commands).map(
4645
4725
  ([action, handler]) => session.onCommand(action, (payload) => handler(payload))
@@ -4671,7 +4751,7 @@ ${content}`);
4671
4751
  slots,
4672
4752
  placeholder,
4673
4753
  connection: state?.connection ?? "connecting",
4674
- banner: /* @__PURE__ */ jsxs17(Fragment3, { children: [
4754
+ banner: /* @__PURE__ */ jsxs17(Fragment4, { children: [
4675
4755
  /* @__PURE__ */ jsx19(
4676
4756
  ReplayBar,
4677
4757
  {
@@ -4697,6 +4777,9 @@ ${content}`);
4697
4777
  onInputChange: setInputText,
4698
4778
  onSuggestion: setInputText,
4699
4779
  onSend: handleSend,
4780
+ onAppend: (text) => {
4781
+ void session?.send(text, { mode: state?.mode ?? void 0 });
4782
+ },
4700
4783
  onStop: handleStop,
4701
4784
  sessionStatus: state?.status ?? void 0,
4702
4785
  askAnswers: state?.askAnswers,
@@ -4713,10 +4796,10 @@ ${content}`);
4713
4796
  }
4714
4797
 
4715
4798
  // src/components/LlmChat.tsx
4716
- import { useEffect as useEffect13, useMemo as useMemo9, useState as useState16 } from "react";
4799
+ import { useEffect as useEffect14, useMemo as useMemo9, useState as useState17 } from "react";
4717
4800
 
4718
4801
  // src/components/LlmAdvancedSettings.tsx
4719
- import { useState as useState15 } from "react";
4802
+ import { useState as useState16 } from "react";
4720
4803
  import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
4721
4804
  var FIELDS = [
4722
4805
  { id: "baseURL", label: "\u6A21\u578B\u670D\u52A1\u5730\u5740", placeholder: "http://\u5185\u7F51\u5730\u5740/v1" },
@@ -4763,8 +4846,8 @@ function writeOverride(settings, baseURL, override) {
4763
4846
  }
4764
4847
  function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
4765
4848
  const normalized = normalizeAdvanced(settings);
4766
- const [open, setOpen] = useState15(false);
4767
- const [draft, setDraft] = useState15(override);
4849
+ const [open, setOpen] = useState16(false);
4850
+ const [draft, setDraft] = useState16(override);
4768
4851
  if (!normalized) return null;
4769
4852
  const fields = FIELDS.filter((field) => normalized[field.id]);
4770
4853
  const dirty = Object.keys(override).length > 0;
@@ -4847,11 +4930,11 @@ function LlmChat({
4847
4930
  onOverrideChange,
4848
4931
  ...options
4849
4932
  }) {
4850
- const [override, setOverride] = useState16(() => readOverride(advanced, options.baseURL));
4933
+ const [override, setOverride] = useState17(() => readOverride(advanced, options.baseURL));
4851
4934
  const effective = { ...options, ...override };
4852
4935
  const { messages, isStreaming, error, send, stop, reset } = useLlmChat(effective);
4853
- const [inputText, setInputText] = useState16("");
4854
- const [stopRequested, setStopRequested] = useState16(false);
4936
+ const [inputText, setInputText] = useState17("");
4937
+ const [stopRequested, setStopRequested] = useState17(false);
4855
4938
  const handle = useMemo9(
4856
4939
  () => ({
4857
4940
  insertText: (text) => setInputText((prev) => prev ? `${prev}
@@ -4861,7 +4944,7 @@ ${text}` : text),
4861
4944
  }),
4862
4945
  [send, reset]
4863
4946
  );
4864
- useEffect13(() => {
4947
+ useEffect14(() => {
4865
4948
  onReady?.(handle);
4866
4949
  }, [handle, onReady]);
4867
4950
  return /* @__PURE__ */ jsx21(
@@ -5007,6 +5090,112 @@ function ContextGroupCard({ contexts, className }) {
5007
5090
  ] });
5008
5091
  }
5009
5092
 
5093
+ // src/components/SessionMemoryToggle.tsx
5094
+ import { useCallback as useCallback9, useEffect as useEffect15, useRef as useRef14, useState as useState18, useSyncExternalStore as useSyncExternalStore2 } from "react";
5095
+ import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
5096
+ var saveStates = /* @__PURE__ */ new WeakMap();
5097
+ function getSaveState(client, sessionId) {
5098
+ let clientStates = saveStates.get(client);
5099
+ if (!clientStates) {
5100
+ clientStates = /* @__PURE__ */ new Map();
5101
+ saveStates.set(client, clientStates);
5102
+ }
5103
+ let state = clientStates.get(sessionId);
5104
+ if (!state) {
5105
+ state = { saving: false, listeners: /* @__PURE__ */ new Set() };
5106
+ clientStates.set(sessionId, state);
5107
+ }
5108
+ return state;
5109
+ }
5110
+ function notify(state) {
5111
+ for (const listener of state.listeners) listener();
5112
+ }
5113
+ function cleanupSaveState(client, sessionId, state) {
5114
+ if (state.saving || state.listeners.size > 0) return;
5115
+ const clientStates = saveStates.get(client);
5116
+ if (clientStates?.get(sessionId) === state) clientStates.delete(sessionId);
5117
+ }
5118
+ function SessionMemoryToggle({
5119
+ sessionId,
5120
+ enabled,
5121
+ client: clientProp,
5122
+ disabled = false,
5123
+ label = "\u5F53\u524D\u4F1A\u8BDD\u4F7F\u7528\u8BB0\u5FC6",
5124
+ className,
5125
+ labelClassName,
5126
+ inputClassName,
5127
+ onSaved,
5128
+ onError
5129
+ }) {
5130
+ const contextClient = useOptionalBladeClient();
5131
+ const client = clientProp ?? contextClient;
5132
+ if (!client) {
5133
+ throw new Error("SessionMemoryToggle \u5FC5\u987B\u5728 <BladeProvider> \u5185\u4F7F\u7528\u6216\u663E\u5F0F\u4F20\u5165 client");
5134
+ }
5135
+ const saveState = getSaveState(client, sessionId);
5136
+ const subscribe = useCallback9(
5137
+ (listener) => {
5138
+ saveState.listeners.add(listener);
5139
+ return () => {
5140
+ saveState.listeners.delete(listener);
5141
+ cleanupSaveState(client, sessionId, saveState);
5142
+ };
5143
+ },
5144
+ [client, saveState, sessionId]
5145
+ );
5146
+ const getSaving = useCallback9(() => saveState.saving, [saveState]);
5147
+ const saving = useSyncExternalStore2(
5148
+ subscribe,
5149
+ getSaving,
5150
+ getSaving
5151
+ );
5152
+ const [draftEnabled, setDraftEnabled] = useState18(enabled);
5153
+ const activeSessionIdRef = useRef14(sessionId);
5154
+ activeSessionIdRef.current = sessionId;
5155
+ useEffect15(() => {
5156
+ setDraftEnabled(enabled);
5157
+ }, [enabled, sessionId]);
5158
+ const update = useCallback9(
5159
+ (nextEnabled) => {
5160
+ const currentSaveState = getSaveState(client, sessionId);
5161
+ if (currentSaveState.saving) return;
5162
+ currentSaveState.saving = true;
5163
+ notify(currentSaveState);
5164
+ setDraftEnabled(nextEnabled);
5165
+ void client.sessions.updateSessionMemory(sessionId, nextEnabled).then(
5166
+ (updated) => {
5167
+ if (activeSessionIdRef.current === sessionId) {
5168
+ setDraftEnabled(updated.memory_enabled);
5169
+ }
5170
+ onSaved?.(sessionId, updated.memory_enabled);
5171
+ },
5172
+ (error) => {
5173
+ if (activeSessionIdRef.current === sessionId) setDraftEnabled(enabled);
5174
+ onError?.(error);
5175
+ }
5176
+ ).finally(() => {
5177
+ currentSaveState.saving = false;
5178
+ notify(currentSaveState);
5179
+ cleanupSaveState(client, sessionId, currentSaveState);
5180
+ });
5181
+ },
5182
+ [client, enabled, onError, onSaved, sessionId]
5183
+ );
5184
+ return /* @__PURE__ */ jsxs20("label", { className: cn("flex items-center justify-between", className), children: [
5185
+ /* @__PURE__ */ jsx24("span", { className: labelClassName, children: label }),
5186
+ /* @__PURE__ */ jsx24(
5187
+ "input",
5188
+ {
5189
+ type: "checkbox",
5190
+ checked: draftEnabled,
5191
+ onChange: (event) => update(event.target.checked),
5192
+ disabled: disabled || saving,
5193
+ className: inputClassName
5194
+ }
5195
+ )
5196
+ ] });
5197
+ }
5198
+
5010
5199
  // src/lib/agent-computer-command.ts
5011
5200
  var COMPUTER_LAUNCH_COMMAND_PATTERN = /(?:^|[\n;&|(]\s*)computer\s+launch(?:\s|$)/;
5012
5201
  function isAgentComputerCommand(command) {
@@ -5065,6 +5254,7 @@ export {
5065
5254
  PlanUpdateBlock,
5066
5255
  ReplayBar,
5067
5256
  ReplayMismatchPrompt,
5257
+ SessionMemoryToggle,
5068
5258
  WhatIfUserBubble,
5069
5259
  classifyAgentComputerLaunchOutcome,
5070
5260
  collectMemoryRefs,