@blade-hq/agent-kit 0.5.2 → 0.5.3

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.
Files changed (30) hide show
  1. package/dist/{SkillStatusBar-DpbkD4Jx.d.ts → SkillStatusBar-BKAGU9tr.d.ts} +5 -3
  2. package/dist/{blade-client-CFvmjs5R.d.ts → blade-client-R3cLVOYs.d.ts} +25 -3
  3. package/dist/{chunk-YW2THJUX.js → chunk-BFF6D2XV.js} +2 -2
  4. package/dist/{chunk-4ZEOWH6U.js → chunk-IRCXJHXT.js} +2 -2
  5. package/dist/{chunk-7JX26TWV.js → chunk-Q6CSM5DE.js} +60 -4
  6. package/dist/chunk-Q6CSM5DE.js.map +1 -0
  7. package/dist/{chunk-EV225FLR.js → chunk-RTBAPZIO.js} +216 -189
  8. package/dist/chunk-RTBAPZIO.js.map +1 -0
  9. package/dist/{chunk-PTMCGZFG.js → chunk-UQEXX57F.js} +15 -3
  10. package/dist/{chunk-PTMCGZFG.js.map → chunk-UQEXX57F.js.map} +1 -1
  11. package/dist/{chunk-LVLGDM76.js → chunk-ZQQNSKQS.js} +2 -2
  12. package/dist/client/index.d.ts +1 -1
  13. package/dist/client/index.js +1 -1
  14. package/dist/react/api/vibe-coding.d.ts +2 -2
  15. package/dist/react/api/vibe-coding.js +2 -2
  16. package/dist/react/components/chat/index.d.ts +3 -3
  17. package/dist/react/components/chat/index.js +5 -5
  18. package/dist/react/components/plan/index.js +3 -3
  19. package/dist/react/components/session/index.js +3 -3
  20. package/dist/react/components/workspace/index.js +32 -4
  21. package/dist/react/components/workspace/index.js.map +1 -1
  22. package/dist/react/index.d.ts +9 -7
  23. package/dist/react/index.js +6 -6
  24. package/dist/style.css +1 -1
  25. package/package.json +1 -1
  26. package/dist/chunk-7JX26TWV.js.map +0 -1
  27. package/dist/chunk-EV225FLR.js.map +0 -1
  28. /package/dist/{chunk-YW2THJUX.js.map → chunk-BFF6D2XV.js.map} +0 -0
  29. /package/dist/{chunk-4ZEOWH6U.js.map → chunk-IRCXJHXT.js.map} +0 -0
  30. /package/dist/{chunk-LVLGDM76.js.map → chunk-ZQQNSKQS.js.map} +0 -0
@@ -8,13 +8,13 @@ import {
8
8
  getCodeLanguageFromFilename,
9
9
  parseAskUserQuestion,
10
10
  useHighlightedCodeHtml
11
- } from "./chunk-LVLGDM76.js";
11
+ } from "./chunk-ZQQNSKQS.js";
12
12
  import {
13
13
  Collapsible,
14
14
  CollapsibleContent,
15
15
  CollapsibleTrigger,
16
16
  resolveSessionFilePreviewTarget
17
- } from "./chunk-4ZEOWH6U.js";
17
+ } from "./chunk-IRCXJHXT.js";
18
18
  import {
19
19
  buildMessageContent,
20
20
  buildToolPreviewKey,
@@ -57,7 +57,7 @@ import {
57
57
  useUiBridgeStore,
58
58
  useUiStore,
59
59
  writeFile
60
- } from "./chunk-PTMCGZFG.js";
60
+ } from "./chunk-UQEXX57F.js";
61
61
  import {
62
62
  registerBridgeIframe,
63
63
  tapBridgeEvent
@@ -66,7 +66,7 @@ import {
66
66
  ModelOption,
67
67
  ModelsConfig,
68
68
  ModelsResource
69
- } from "./chunk-7JX26TWV.js";
69
+ } from "./chunk-Q6CSM5DE.js";
70
70
  import {
71
71
  cn,
72
72
  copyToClipboard
@@ -80,13 +80,15 @@ import { Eye } from "lucide-react";
80
80
  import { useCallback as useCallback13 } from "react";
81
81
 
82
82
  // src/react/hooks/use-chat.ts
83
- import { useCallback, useEffect } from "react";
83
+ import { useCallback, useEffect, useRef, useState } from "react";
84
84
  var EMPTY_MESSAGES = [];
85
85
  function useChat(sessionId) {
86
86
  const messages = useChatStore((s) => s.messages[sessionId] ?? EMPTY_MESSAGES);
87
87
  const isStreaming = useChatStore((s) => s.isStreaming[sessionId] ?? false);
88
88
  const mode = useSessionStore((s) => s.modes[sessionId] ?? "executing");
89
89
  const setAnswerCallback = useAnswerCallbackStore((s) => s.setAnswerCallback);
90
+ const [isStopping, setIsStopping] = useState(false);
91
+ const previousSessionIdRef = useRef(sessionId);
90
92
  const send = useCallback(
91
93
  (msg, mode2, askuserAnswer, options) => {
92
94
  const synthesizedAnswer = askuserAnswer ?? buildPendingAskUserAnswer(sessionId, msg);
@@ -94,9 +96,27 @@ function useChat(sessionId) {
94
96
  },
95
97
  [sessionId]
96
98
  );
97
- const stop = useCallback(() => {
98
- getSocket().stop(sessionId);
99
- }, [sessionId]);
99
+ const stop = useCallback(async () => {
100
+ if (isStopping) return;
101
+ setIsStopping(true);
102
+ try {
103
+ await getSocket().stop(sessionId);
104
+ } catch (error) {
105
+ console.error("[chat] stop request failed", error);
106
+ } finally {
107
+ setIsStopping(false);
108
+ }
109
+ }, [isStopping, sessionId]);
110
+ useEffect(() => {
111
+ if (previousSessionIdRef.current !== sessionId) {
112
+ previousSessionIdRef.current = sessionId;
113
+ setIsStopping(false);
114
+ return;
115
+ }
116
+ if (!isStreaming) {
117
+ setIsStopping(false);
118
+ }
119
+ }, [isStreaming, sessionId]);
100
120
  const answer = useCallback(
101
121
  (msg, toolCallId, answerData) => {
102
122
  send(msg, mode, { tool_call_id: toolCallId, ...answerData });
@@ -107,7 +127,7 @@ function useChat(sessionId) {
107
127
  setAnswerCallback(sessionId, answer);
108
128
  return () => setAnswerCallback(sessionId, void 0);
109
129
  }, [answer, sessionId, setAnswerCallback]);
110
- return { messages, isStreaming, send, stop };
130
+ return { messages, isStreaming, isStopping, send, stop };
111
131
  }
112
132
  function buildPendingAskUserAnswer(sessionId, message) {
113
133
  const session = useSessionStore.getState().sessions.find((item) => item.id === sessionId);
@@ -180,17 +200,17 @@ import {
180
200
  useEffect as useEffect7,
181
201
  useEffectEvent as useEffectEvent2,
182
202
  useMemo as useMemo7,
183
- useRef as useRef4,
184
- useState as useState7
203
+ useRef as useRef5,
204
+ useState as useState8
185
205
  } from "react";
186
206
  import { createRoot } from "react-dom/client";
187
207
  import { toast } from "sonner";
188
208
 
189
209
  // src/react/asr/use-tiptap-voice-input.ts
190
- import { useCallback as useCallback3, useEffect as useEffect3, useEffectEvent, useRef as useRef2 } from "react";
210
+ import { useCallback as useCallback3, useEffect as useEffect3, useEffectEvent, useRef as useRef3 } from "react";
191
211
 
192
212
  // src/react/asr/use-voice-input.ts
193
- import { useCallback as useCallback2, useEffect as useEffect2, useRef, useState } from "react";
213
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
194
214
 
195
215
  // src/react/asr/voice-input-support.ts
196
216
  function isIpHost(hostname) {
@@ -237,14 +257,14 @@ function matchesRequest(data, requestId) {
237
257
  return !rid || rid === requestId;
238
258
  }
239
259
  function useVoiceInput(options) {
240
- const [status, setStatus] = useState("idle");
241
- const [error, setError] = useState(null);
242
- const [level, setLevel] = useState(0);
243
- const sessionRef = useRef(null);
244
- const startingRef = useRef(false);
245
- const stoppingRef = useRef(false);
246
- const unmountedRef = useRef(false);
247
- const optionsRef = useRef(options);
260
+ const [status, setStatus] = useState2("idle");
261
+ const [error, setError] = useState2(null);
262
+ const [level, setLevel] = useState2(0);
263
+ const sessionRef = useRef2(null);
264
+ const startingRef = useRef2(false);
265
+ const stoppingRef = useRef2(false);
266
+ const unmountedRef = useRef2(false);
267
+ const optionsRef = useRef2(options);
248
268
  optionsRef.current = options;
249
269
  const reportError = useCallback2((err) => {
250
270
  const payload = typeof err === "string" ? { title: err } : err;
@@ -499,8 +519,8 @@ function useVoiceInput(options) {
499
519
  // src/react/asr/use-tiptap-voice-input.ts
500
520
  function useTiptapVoiceInput(options) {
501
521
  const { editorRef, workletUrl, onError } = options;
502
- const partialRangeRef = useRef2(null);
503
- const lastPartialLenRef = useRef2(0);
522
+ const partialRangeRef = useRef3(null);
523
+ const lastPartialLenRef = useRef3(0);
504
524
  const handlePartial = useEffectEvent((text) => {
505
525
  const ed = editorRef.current;
506
526
  if (!ed) return;
@@ -619,7 +639,7 @@ function VoiceWaveform({ level, color = "currentColor", size = 16 }) {
619
639
 
620
640
  // src/react/components/model/ModelSelector.tsx
621
641
  import { Loader2, Sparkles } from "lucide-react";
622
- import { useEffect as useEffect4, useMemo, useRef as useRef3, useState as useState2 } from "react";
642
+ import { useEffect as useEffect4, useMemo, useRef as useRef4, useState as useState3 } from "react";
623
643
 
624
644
  // src/react/hooks/use-model-preferences.ts
625
645
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -727,9 +747,9 @@ function ModelSelector({
727
747
  placement = "bottom"
728
748
  }) {
729
749
  const { models, defaultModel, isLoading } = useModelConfig();
730
- const [isOpen, setIsOpen] = useState2(false);
731
- const [query, setQuery] = useState2("");
732
- const rootRef = useRef3(null);
750
+ const [isOpen, setIsOpen] = useState3(false);
751
+ const [query, setQuery] = useState3("");
752
+ const rootRef = useRef4(null);
733
753
  useEffect4(() => {
734
754
  if (!isOpen) return;
735
755
  const closeOnPointerDown = (event) => {
@@ -838,7 +858,7 @@ function ModelSelector({
838
858
  }
839
859
 
840
860
  // src/react/hooks/use-input-history.ts
841
- import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo2, useState as useState3 } from "react";
861
+ import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo2, useState as useState4 } from "react";
842
862
  var MAX_HISTORY_ENTRIES = 50;
843
863
  function getStorageKey(sessionId) {
844
864
  return sessionId ? `input-history:${sessionId}` : null;
@@ -863,9 +883,9 @@ function readEntries(storageKey) {
863
883
  }
864
884
  function useInputHistory(sessionId) {
865
885
  const storageKey = useMemo2(() => getStorageKey(sessionId), [sessionId]);
866
- const [entries, setEntries] = useState3([]);
867
- const [cursor, setCursor] = useState3(null);
868
- const [draft, setDraft] = useState3("");
886
+ const [entries, setEntries] = useState4([]);
887
+ const [cursor, setCursor] = useState4(null);
888
+ const [draft, setDraft] = useState4("");
869
889
  useEffect5(() => {
870
890
  setEntries(readEntries(storageKey));
871
891
  setCursor(null);
@@ -1004,7 +1024,7 @@ function skillDisplayName(skill) {
1004
1024
 
1005
1025
  // src/react/components/chat/FileCompletionMenu.tsx
1006
1026
  import { ChevronRight, File, Folder } from "lucide-react";
1007
- import { forwardRef, useCallback as useCallback5, useImperativeHandle, useMemo as useMemo4, useState as useState4 } from "react";
1027
+ import { forwardRef, useCallback as useCallback5, useImperativeHandle, useMemo as useMemo4, useState as useState5 } from "react";
1008
1028
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1009
1029
  var ROOT_DIR = ".";
1010
1030
  function isVisibleEntry(entry) {
@@ -1108,7 +1128,7 @@ var FileCompletionMenu = forwardRef(
1108
1128
  }
1109
1129
  );
1110
1130
  var FileCompletionMenuContent = forwardRef(function FileCompletionMenuContent2({ command, editor, filterQuery, items, onExit, range, sessionId }, ref) {
1111
- const [rawSelectedIndex, setRawSelectedIndex] = useState4(0);
1131
+ const [rawSelectedIndex, setRawSelectedIndex] = useState5(0);
1112
1132
  const filteredEntries = useMemo4(() => filterEntries(items, filterQuery), [filterQuery, items]);
1113
1133
  const selectedIndex = filteredEntries.length === 0 ? 0 : Math.min(rawSelectedIndex, filteredEntries.length - 1);
1114
1134
  const handleSelect = useCallback5(
@@ -1189,11 +1209,11 @@ var FileCompletionMenuContent = forwardRef(function FileCompletionMenuContent2({
1189
1209
  });
1190
1210
 
1191
1211
  // src/react/components/chat/SkillCompletionMenu.tsx
1192
- import { forwardRef as forwardRef2, useCallback as useCallback6, useImperativeHandle as useImperativeHandle2, useState as useState5 } from "react";
1212
+ import { forwardRef as forwardRef2, useCallback as useCallback6, useImperativeHandle as useImperativeHandle2, useState as useState6 } from "react";
1193
1213
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1194
1214
  var SkillCompletionMenu = forwardRef2(
1195
1215
  function SkillCompletionMenu2({ command, items }, ref) {
1196
- const [rawSelectedIndex, setRawSelectedIndex] = useState5(0);
1216
+ const [rawSelectedIndex, setRawSelectedIndex] = useState6(0);
1197
1217
  const selectedIndex = items.length === 0 ? 0 : Math.min(rawSelectedIndex, items.length - 1);
1198
1218
  const handleSelect = useCallback6(
1199
1219
  (item) => {
@@ -1298,7 +1318,7 @@ var SkillCompletionMenu = forwardRef2(
1298
1318
 
1299
1319
  // src/react/components/chat/BackgroundTasksPill.tsx
1300
1320
  import { FileText, Square, Terminal } from "lucide-react";
1301
- import { useState as useState6 } from "react";
1321
+ import { useState as useState7 } from "react";
1302
1322
 
1303
1323
  // src/react/hooks/use-background-tasks.ts
1304
1324
  import { useQuery as useQuery2 } from "@tanstack/react-query";
@@ -1347,8 +1367,8 @@ function statusClass(status) {
1347
1367
  }
1348
1368
  function BackgroundTasksPill({ sessionId }) {
1349
1369
  const { data: tasks } = useBackgroundTasks(sessionId);
1350
- const [open, setOpen] = useState6(false);
1351
- const [logTask, setLogTask] = useState6(null);
1370
+ const [open, setOpen] = useState7(false);
1371
+ const [logTask, setLogTask] = useState7(null);
1352
1372
  if (tasks.length === 0) return null;
1353
1373
  const runningCount = tasks.filter((task) => task.status === "running").length;
1354
1374
  const openTaskLog = async (task) => {
@@ -2131,20 +2151,38 @@ function ComposerFilePill({
2131
2151
  const isFile = attachment.kind === "file";
2132
2152
  const isFailed = isFile && attachment.status === "failed";
2133
2153
  const isUploading = isFile && attachment.status === "uploading";
2154
+ const uploadPercent = isUploading && typeof attachment.uploadProgress === "number" ? Math.max(0, Math.min(100, Math.round(attachment.uploadProgress * 100))) : null;
2134
2155
  return /* @__PURE__ */ jsxs8(
2135
2156
  "div",
2136
2157
  {
2137
- className: `flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] ${isFailed ? "border-red-500/30 bg-red-500/5 text-red-400" : "border-[hsl(var(--border))] bg-[hsl(var(--card))] text-[hsl(var(--foreground))]"}`,
2158
+ className: `relative flex shrink-0 items-center gap-1.5 overflow-hidden rounded-full border px-2.5 py-1 text-[11px] ${isFailed ? "border-red-500/30 bg-red-500/5 text-red-400" : "border-[hsl(var(--border))] bg-[hsl(var(--card))] text-[hsl(var(--foreground))]"}`,
2138
2159
  children: [
2139
- isUploading ? /* @__PURE__ */ jsx9(Loader22, { size: 12, className: "shrink-0 animate-spin text-[hsl(var(--muted-foreground))]" }) : /* @__PURE__ */ jsx9(Icon, { size: 12, className: "shrink-0 text-[hsl(var(--muted-foreground))]" }),
2140
- /* @__PURE__ */ jsx9("span", { className: "max-w-32 truncate", children: attachment.name }),
2160
+ uploadPercent !== null ? /* @__PURE__ */ jsx9(
2161
+ "span",
2162
+ {
2163
+ className: "absolute inset-y-0 left-0 bg-[hsl(var(--primary))]/10 transition-[width]",
2164
+ style: { width: `${uploadPercent}%` }
2165
+ }
2166
+ ) : null,
2167
+ isUploading ? /* @__PURE__ */ jsx9(
2168
+ Loader22,
2169
+ {
2170
+ size: 12,
2171
+ className: "relative z-10 shrink-0 animate-spin text-[hsl(var(--muted-foreground))]"
2172
+ }
2173
+ ) : /* @__PURE__ */ jsx9(Icon, { size: 12, className: "relative z-10 shrink-0 text-[hsl(var(--muted-foreground))]" }),
2174
+ /* @__PURE__ */ jsx9("span", { className: "relative z-10 max-w-32 truncate", children: attachment.name }),
2175
+ uploadPercent !== null ? /* @__PURE__ */ jsxs8("span", { className: "relative z-10 font-mono text-[10px] tabular-nums text-[hsl(var(--muted-foreground))]", children: [
2176
+ uploadPercent,
2177
+ "%"
2178
+ ] }) : null,
2141
2179
  /* @__PURE__ */ jsx9(
2142
2180
  "button",
2143
2181
  {
2144
2182
  type: "button",
2145
2183
  onClick: () => onRemove(attachment.id),
2146
2184
  "aria-label": `\u79FB\u9664 ${attachment.name}`,
2147
- className: "ml-0.5 inline-flex shrink-0 items-center justify-center rounded-full text-[hsl(var(--muted-foreground))] transition hover:text-rose-400",
2185
+ className: "relative z-10 ml-0.5 inline-flex shrink-0 items-center justify-center rounded-full text-[hsl(var(--muted-foreground))] transition hover:text-rose-400",
2148
2186
  children: /* @__PURE__ */ jsx9(X2, { size: 10 })
2149
2187
  }
2150
2188
  )
@@ -2158,13 +2196,13 @@ function AddContextDialog({
2158
2196
  onAdd
2159
2197
  }) {
2160
2198
  const CONTEXT_INLINE_THRESHOLD = 200;
2161
- const [label, setLabel] = useState7("");
2162
- const [content, setContent] = useState7("");
2163
- const [showSessionPicker, setShowSessionPicker] = useState7(false);
2164
- const [sessions, setSessions] = useState7([]);
2165
- const [loadingSessions, setLoadingSessions] = useState7(false);
2166
- const [importingId, setImportingId] = useState7(null);
2167
- const [isImportProcessing, setIsImportProcessing] = useState7(false);
2199
+ const [label, setLabel] = useState8("");
2200
+ const [content, setContent] = useState8("");
2201
+ const [showSessionPicker, setShowSessionPicker] = useState8(false);
2202
+ const [sessions, setSessions] = useState8([]);
2203
+ const [loadingSessions, setLoadingSessions] = useState8(false);
2204
+ const [importingId, setImportingId] = useState8(null);
2205
+ const [isImportProcessing, setIsImportProcessing] = useState8(false);
2168
2206
  const sanitizeContextFolderName = (raw) => {
2169
2207
  const normalized = raw.trim().toLowerCase();
2170
2208
  const safe = normalized.replace(/[^\w\-.\u4e00-\u9fa5]+/g, "_").replace(/^_+|_+$/g, "");
@@ -2394,7 +2432,7 @@ function ComposerContextPill({
2394
2432
  content,
2395
2433
  onRemove
2396
2434
  }) {
2397
- const [showDetail, setShowDetail] = useState7(false);
2435
+ const [showDetail, setShowDetail] = useState8(false);
2398
2436
  const tokenK = formatTokenK(content);
2399
2437
  return /* @__PURE__ */ jsxs8(Fragment2, { children: [
2400
2438
  /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center gap-1.5 rounded-full border border-[hsl(var(--border))] bg-[hsl(var(--accent))] px-2.5 py-1 text-[11px] text-[hsl(var(--foreground))]", children: [
@@ -2589,6 +2627,7 @@ function ChatInput({
2589
2627
  onSend,
2590
2628
  onStop,
2591
2629
  isStreaming,
2630
+ isStopping = false,
2592
2631
  mode = "executing",
2593
2632
  onToggleMode,
2594
2633
  renderAttachments,
@@ -2608,13 +2647,13 @@ function ChatInput({
2608
2647
  onResyncSkills,
2609
2648
  isResyncingSkills = false
2610
2649
  }) {
2611
- const [input, setInputInternal] = useState7(externalDraft?.value ?? "");
2650
+ const [input, setInputInternal] = useState8(externalDraft?.value ?? "");
2612
2651
  const setInput = useEffectEvent2((value) => {
2613
2652
  setInputInternal(value);
2614
2653
  externalDraft?.setValue(value);
2615
2654
  });
2616
2655
  const externalDraftValue = externalDraft?.value;
2617
- const inputRef = useRef4(input);
2656
+ const inputRef = useRef5(input);
2618
2657
  inputRef.current = input;
2619
2658
  useEffect7(() => {
2620
2659
  if (externalDraftValue == null) return;
@@ -2624,7 +2663,7 @@ function ChatInput({
2624
2663
  }
2625
2664
  setInputInternal(externalDraftValue);
2626
2665
  }, [externalDraftValue]);
2627
- const [composerAttachments, setComposerAttachmentsInternal] = useState7(
2666
+ const [composerAttachments, setComposerAttachmentsInternal] = useState8(
2628
2667
  externalAttachments?.value ?? []
2629
2668
  );
2630
2669
  const setComposerAttachments = useEffectEvent2(
@@ -2637,24 +2676,24 @@ function ChatInput({
2637
2676
  }
2638
2677
  );
2639
2678
  const externalAttachmentsValue = externalAttachments?.value;
2640
- const composerAttachmentsRef = useRef4(composerAttachments);
2679
+ const composerAttachmentsRef = useRef5(composerAttachments);
2641
2680
  composerAttachmentsRef.current = composerAttachments;
2642
2681
  useEffect7(() => {
2643
2682
  if (externalAttachmentsValue == null) return;
2644
2683
  if (composerAttachmentsRef.current === externalAttachmentsValue) return;
2645
2684
  setComposerAttachmentsInternal(externalAttachmentsValue);
2646
2685
  }, [externalAttachmentsValue]);
2647
- const [dragging, setDragging] = useState7(false);
2648
- const [isEditorFocused, setIsEditorFocused] = useState7(false);
2649
- const [oversizedFiles, setOversizedFiles] = useState7([]);
2650
- const [selectedModel, setSelectedModel] = useState7("");
2686
+ const [dragging, setDragging] = useState8(false);
2687
+ const [isEditorFocused, setIsEditorFocused] = useState8(false);
2688
+ const [oversizedFiles, setOversizedFiles] = useState8([]);
2689
+ const [selectedModel, setSelectedModel] = useState8("");
2651
2690
  const { setPreferredModel } = usePreferredModel();
2652
2691
  const queryClient = useQueryClient2();
2653
- const actionMenuRef = useRef4(null);
2654
- const fileInputRef = useRef4(null);
2655
- const folderInputRef = useRef4(null);
2656
- const editorRef = useRef4(null);
2657
- const localImageUrlsRef = useRef4(/* @__PURE__ */ new Map());
2692
+ const actionMenuRef = useRef5(null);
2693
+ const fileInputRef = useRef5(null);
2694
+ const folderInputRef = useRef5(null);
2695
+ const editorRef = useRef5(null);
2696
+ const localImageUrlsRef = useRef5(/* @__PURE__ */ new Map());
2658
2697
  const activeSessionId = useSessionStore((state) => state.activeSessionId);
2659
2698
  const sessions = useSessionStore((state) => state.sessions);
2660
2699
  const activeSessionModel = useMemo7(
@@ -2687,7 +2726,7 @@ function ChatInput({
2687
2726
  const inputHistory = useInputHistory(activeSessionId);
2688
2727
  const getSessionId = useEffectEvent2(() => activeSessionId);
2689
2728
  const handleSlashCommand = useEffectEvent2((commandId) => onCommand?.(commandId));
2690
- const [localImageUrls, setLocalImageUrls] = useState7({});
2729
+ const [localImageUrls, setLocalImageUrls] = useState8({});
2691
2730
  useEffect7(() => {
2692
2731
  const closeActionMenu = (event) => {
2693
2732
  const menu = actionMenuRef.current;
@@ -3016,7 +3055,7 @@ function ChatInput({
3016
3055
  editor.commands.focus("end");
3017
3056
  setRewindDraft(activeSessionId, null);
3018
3057
  }, [activeSessionId, editor, rewindDraft, setRewindDraft]);
3019
- const submittingRef = useRef4(false);
3058
+ const submittingRef = useRef5(false);
3020
3059
  useEffect7(() => {
3021
3060
  let changed = false;
3022
3061
  const nextLocalIds = /* @__PURE__ */ new Set();
@@ -3095,14 +3134,14 @@ function ChatInput({
3095
3134
  });
3096
3135
  const { isRecording, level: voiceLevel } = voice;
3097
3136
  const handleMicDisabledClick = () => toast.info("\u8BED\u97F3\u8F93\u5165\u529F\u80FD\u672A\u5F00\u542F\uFF0C\u8BF7\u5728\u540E\u7AEF .env \u914D\u7F6E ASR_API_KEY");
3098
- const voiceStopRef = useRef4(voice.stop);
3137
+ const voiceStopRef = useRef5(voice.stop);
3099
3138
  voiceStopRef.current = voice.stop;
3100
3139
  useEffect7(() => {
3101
3140
  if (isStreaming && isRecording) {
3102
3141
  void voiceStopRef.current();
3103
3142
  }
3104
3143
  }, [isStreaming, isRecording]);
3105
- const prevSessionIdRef = useRef4(activeSessionId);
3144
+ const prevSessionIdRef = useRef5(activeSessionId);
3106
3145
  useEffect7(() => {
3107
3146
  if (prevSessionIdRef.current !== activeSessionId) {
3108
3147
  prevSessionIdRef.current = activeSessionId;
@@ -3153,14 +3192,24 @@ function ChatInput({
3153
3192
  if (pendingFiles.length > 0) {
3154
3193
  setComposerAttachments(
3155
3194
  (prev) => prev.map(
3156
- (a) => a.kind === "file" && a.status === "pending" ? { ...a, status: "uploading" } : a
3195
+ (a) => a.kind === "file" && a.status === "pending" ? { ...a, status: "uploading", uploadProgress: 0 } : a
3157
3196
  )
3158
3197
  );
3159
3198
  try {
3160
3199
  const result = await uploadFiles(
3161
3200
  uploadSessionId,
3162
3201
  ".",
3163
- pendingFiles.map((a) => ({ file: a.file, name: a.name }))
3202
+ pendingFiles.map((a) => ({ file: a.file, name: a.name })),
3203
+ {
3204
+ onProgress: (progress) => {
3205
+ if (typeof progress.percent !== "number") return;
3206
+ setComposerAttachments(
3207
+ (prev) => prev.map(
3208
+ (a) => pendingFiles.some((pending) => pending.id === a.id) && a.kind === "file" && a.status === "uploading" ? { ...a, uploadProgress: progress.percent } : a
3209
+ )
3210
+ );
3211
+ }
3212
+ }
3164
3213
  );
3165
3214
  const uploadResultById = /* @__PURE__ */ new Map();
3166
3215
  const failedSet = new Set(result.failed);
@@ -3176,9 +3225,15 @@ function ChatInput({
3176
3225
  updatedAttachments = composerAttachments.map((a) => {
3177
3226
  if (!uploadResultById.has(a.id)) return a;
3178
3227
  const uploadedPath = uploadResultById.get(a.id);
3179
- if (!uploadedPath) return { ...a, status: "failed" };
3228
+ if (!uploadedPath) return { ...a, status: "failed", uploadProgress: null };
3180
3229
  const actualName = uploadedPath.split("/").pop() || a.name;
3181
- return { ...a, name: actualName, status: "uploaded", uploadedPath };
3230
+ return {
3231
+ ...a,
3232
+ name: actualName,
3233
+ status: "uploaded",
3234
+ uploadedPath,
3235
+ uploadProgress: null
3236
+ };
3182
3237
  });
3183
3238
  setComposerAttachments(updatedAttachments);
3184
3239
  if (result.uploaded.length > 0) {
@@ -3220,7 +3275,7 @@ function ChatInput({
3220
3275
  const removeAttachment = (id) => {
3221
3276
  setComposerAttachments((prev) => prev.filter((attachment) => attachment.id !== id));
3222
3277
  };
3223
- const [showAddContext, setShowAddContext] = useState7(false);
3278
+ const [showAddContext, setShowAddContext] = useState8(false);
3224
3279
  const isPlanning = mode === "planning";
3225
3280
  const placeholder = isPlanning ? "\u89C4\u5212\u8FDB\u884C\u4E2D\u2026 \u53EF\u8F93\u5165\u8865\u5145\u9700\u6C42\u6216\u7B49\u5F85\u5B8C\u6210" : "\u8F93\u5165\u6D88\u606F\u2026";
3226
3281
  const attachments = renderAttachments?.() ?? null;
@@ -3596,10 +3651,11 @@ function ChatInput({
3596
3651
  "button",
3597
3652
  {
3598
3653
  type: "button",
3599
- onClick: onStop,
3600
- "aria-label": "\u505C\u6B62\u751F\u6210",
3601
- className: "flex h-7 w-7 items-center justify-center rounded-lg bg-[hsl(var(--destructive))] text-[hsl(var(--destructive-foreground))] transition-opacity hover:opacity-80",
3602
- children: /* @__PURE__ */ jsx9(Square2, { size: 13 })
3654
+ onClick: () => void onStop(),
3655
+ disabled: isStopping,
3656
+ "aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u751F\u6210",
3657
+ className: "flex h-7 w-7 items-center justify-center rounded-lg bg-[hsl(var(--destructive))] text-[hsl(var(--destructive-foreground))] transition-opacity hover:opacity-80 disabled:cursor-wait disabled:opacity-70",
3658
+ children: isStopping ? /* @__PURE__ */ jsx9(Loader22, { size: 13, className: "animate-spin" }) : /* @__PURE__ */ jsx9(Square2, { size: 13 })
3603
3659
  }
3604
3660
  ) : /* @__PURE__ */ jsx9(
3605
3661
  "button",
@@ -3689,12 +3745,12 @@ import {
3689
3745
  useEffect as useEffect16,
3690
3746
  useEffectEvent as useEffectEvent4,
3691
3747
  useMemo as useMemo17,
3692
- useRef as useRef11,
3693
- useState as useState20
3748
+ useRef as useRef12,
3749
+ useState as useState21
3694
3750
  } from "react";
3695
3751
 
3696
3752
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
3697
- import { useCallback as useCallback7, useMemo as useMemo8, useRef as useRef5, useState as useState8 } from "react";
3753
+ import { useCallback as useCallback7, useMemo as useMemo8, useRef as useRef6, useState as useState9 } from "react";
3698
3754
  var DEFAULT_SPRING_ANIMATION = {
3699
3755
  /**
3700
3756
  * A value from 0 to 1, on how much to damp the animation.
@@ -3731,10 +3787,10 @@ globalThis.document?.addEventListener("click", () => {
3731
3787
  mouseDown = false;
3732
3788
  });
3733
3789
  var useStickToBottom = (options = {}) => {
3734
- const [escapedFromLock, updateEscapedFromLock] = useState8(false);
3735
- const [isAtBottom, updateIsAtBottom] = useState8(options.initial !== false);
3736
- const [isNearBottom, setIsNearBottom] = useState8(false);
3737
- const optionsRef = useRef5(null);
3790
+ const [escapedFromLock, updateEscapedFromLock] = useState9(false);
3791
+ const [isAtBottom, updateIsAtBottom] = useState9(options.initial !== false);
3792
+ const [isNearBottom, setIsNearBottom] = useState9(false);
3793
+ const optionsRef = useRef6(null);
3738
3794
  optionsRef.current = options;
3739
3795
  const isSelecting = useCallback7(() => {
3740
3796
  if (!mouseDown) {
@@ -4038,11 +4094,11 @@ function mergeAnimations(...animations) {
4038
4094
 
4039
4095
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
4040
4096
  import * as React from "react";
4041
- import { createContext, useContext, useEffect as useEffect8, useImperativeHandle as useImperativeHandle3, useLayoutEffect, useMemo as useMemo9, useRef as useRef6 } from "react";
4097
+ import { createContext, useContext, useEffect as useEffect8, useImperativeHandle as useImperativeHandle3, useLayoutEffect, useMemo as useMemo9, useRef as useRef7 } from "react";
4042
4098
  var StickToBottomContext = createContext(null);
4043
4099
  var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect8;
4044
4100
  function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
4045
- const customTargetScrollTop = useRef6(null);
4101
+ const customTargetScrollTop = useRef7(null);
4046
4102
  const targetScrollTop = React.useCallback((target, elements) => {
4047
4103
  const get = context?.targetScrollTop ?? currentTargetScrollTop;
4048
4104
  return get?.(target, elements) ?? target;
@@ -4119,7 +4175,7 @@ function useStickToBottomContext() {
4119
4175
 
4120
4176
  // src/react/components/chat/AssistantTurnBlock.tsx
4121
4177
  import { AlertCircle, BookOpen, Check as Check4, ChevronRight as ChevronRight5 } from "lucide-react";
4122
- import { useCallback as useCallback11, useEffect as useEffect14, useMemo as useMemo15, useRef as useRef10, useState as useState17 } from "react";
4178
+ import { useCallback as useCallback11, useEffect as useEffect14, useMemo as useMemo15, useRef as useRef11, useState as useState18 } from "react";
4123
4179
 
4124
4180
  // src/react/routes.ts
4125
4181
  var MEMORIES_ROUTE = "/memories";
@@ -4137,8 +4193,8 @@ import {
4137
4193
  useContext as useContext2,
4138
4194
  useEffect as useEffect9,
4139
4195
  useMemo as useMemo11,
4140
- useRef as useRef7,
4141
- useState as useState9
4196
+ useRef as useRef8,
4197
+ useState as useState10
4142
4198
  } from "react";
4143
4199
  import { createPortal as createPortal2 } from "react-dom";
4144
4200
  import { Streamdown } from "streamdown";
@@ -4205,7 +4261,6 @@ var useReasoning = () => {
4205
4261
  }
4206
4262
  return context;
4207
4263
  };
4208
- var AUTO_CLOSE_DELAY = 3e3;
4209
4264
  var MS_IN_S = 1e3;
4210
4265
  var Reasoning = memo2(
4211
4266
  ({
@@ -4218,10 +4273,8 @@ var Reasoning = memo2(
4218
4273
  children,
4219
4274
  ...props
4220
4275
  }) => {
4221
- const resolvedDefaultOpen = defaultOpen ?? isStreaming;
4222
- const isExplicitlyClosed = defaultOpen === false;
4223
4276
  const [isOpen, setIsOpen] = useControllableState({
4224
- defaultProp: resolvedDefaultOpen,
4277
+ defaultProp: defaultOpen ?? false,
4225
4278
  onChange: onOpenChange,
4226
4279
  prop: open
4227
4280
  });
@@ -4229,12 +4282,9 @@ var Reasoning = memo2(
4229
4282
  defaultProp: void 0,
4230
4283
  prop: durationProp
4231
4284
  });
4232
- const hasEverStreamedRef = useRef7(isStreaming);
4233
- const [hasAutoClosed, setHasAutoClosed] = useState9(false);
4234
- const startTimeRef = useRef7(null);
4285
+ const startTimeRef = useRef8(null);
4235
4286
  useEffect9(() => {
4236
4287
  if (isStreaming) {
4237
- hasEverStreamedRef.current = true;
4238
4288
  if (startTimeRef.current === null) {
4239
4289
  startTimeRef.current = Date.now();
4240
4290
  }
@@ -4243,20 +4293,6 @@ var Reasoning = memo2(
4243
4293
  startTimeRef.current = null;
4244
4294
  }
4245
4295
  }, [isStreaming, setDuration]);
4246
- useEffect9(() => {
4247
- if (isStreaming && !isOpen && !isExplicitlyClosed) {
4248
- setIsOpen(true);
4249
- }
4250
- }, [isStreaming, isOpen, setIsOpen, isExplicitlyClosed]);
4251
- useEffect9(() => {
4252
- if (hasEverStreamedRef.current && !isStreaming && isOpen && !hasAutoClosed) {
4253
- const timer = setTimeout(() => {
4254
- setIsOpen(false);
4255
- setHasAutoClosed(true);
4256
- }, AUTO_CLOSE_DELAY);
4257
- return () => clearTimeout(timer);
4258
- }
4259
- }, [isStreaming, isOpen, setIsOpen, hasAutoClosed]);
4260
4296
  const handleOpenChange = useCallback9(
4261
4297
  (newOpen) => {
4262
4298
  setIsOpen(newOpen);
@@ -4270,7 +4306,7 @@ var Reasoning = memo2(
4270
4306
  return /* @__PURE__ */ jsx12(ReasoningContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx12(
4271
4307
  Collapsible,
4272
4308
  {
4273
- className: cn("not-prose mb-4", className),
4309
+ className: cn("not-prose", className),
4274
4310
  onOpenChange: handleOpenChange,
4275
4311
  open: isOpen,
4276
4312
  ...props,
@@ -4279,38 +4315,36 @@ var Reasoning = memo2(
4279
4315
  ) });
4280
4316
  }
4281
4317
  );
4282
- var defaultGetThinkingMessage = (isStreaming, duration) => {
4283
- if (isStreaming || duration === 0) {
4284
- return /* @__PURE__ */ jsx12(Shimmer, { duration: 1, children: "\u6DF1\u5EA6\u601D\u8003\u4E2D..." });
4285
- }
4286
- if (duration === void 0) {
4287
- return /* @__PURE__ */ jsx12("p", { children: "\u5DF2\u6DF1\u5EA6\u601D\u8003" });
4288
- }
4289
- return /* @__PURE__ */ jsxs10("p", { children: [
4290
- "\u5DF2\u6DF1\u5EA6\u601D\u8003 ",
4291
- duration,
4292
- " \u79D2"
4293
- ] });
4294
- };
4318
+ function formatWordCount(value) {
4319
+ return new Intl.NumberFormat("zh-CN").format(value);
4320
+ }
4295
4321
  var ReasoningTrigger = memo2(
4296
4322
  ({
4297
4323
  className,
4298
4324
  children,
4299
- getThinkingMessage = defaultGetThinkingMessage,
4325
+ wordCount,
4300
4326
  ...props
4301
4327
  }) => {
4302
4328
  const { isStreaming, isOpen, duration } = useReasoning();
4329
+ const status = isStreaming || duration === 0 ? "\u6B63\u5728\u601D\u8003" : "\u5DF2\u601D\u8003";
4330
+ const detail = `${formatWordCount(wordCount)} \u5B57`;
4303
4331
  return /* @__PURE__ */ jsx12(
4304
4332
  CollapsibleTrigger,
4305
4333
  {
4306
4334
  className: cn(
4307
- "flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
4335
+ "flex w-fit items-center gap-2 rounded-full px-1 py-0.5 text-muted-foreground text-sm transition-colors hover:text-foreground",
4308
4336
  className
4309
4337
  ),
4310
4338
  ...props,
4311
4339
  children: children ?? /* @__PURE__ */ jsxs10(Fragment3, { children: [
4312
4340
  /* @__PURE__ */ jsx12(BrainIcon, { className: "size-4" }),
4313
- getThinkingMessage(isStreaming, duration),
4341
+ /* @__PURE__ */ jsxs10("span", { className: "inline-flex items-center gap-1.5", children: [
4342
+ isStreaming || duration === 0 ? /* @__PURE__ */ jsx12(Shimmer, { duration: 1, children: status }) : /* @__PURE__ */ jsx12("span", { children: status }),
4343
+ /* @__PURE__ */ jsxs10("span", { className: "text-[hsl(var(--muted-foreground))]/70", children: [
4344
+ "\xB7 ",
4345
+ detail
4346
+ ] })
4347
+ ] }),
4314
4348
  /* @__PURE__ */ jsx12(
4315
4349
  ChevronDownIcon,
4316
4350
  {
@@ -4332,7 +4366,7 @@ var ReasoningContent = memo2(
4332
4366
  CollapsibleContent,
4333
4367
  {
4334
4368
  className: cn(
4335
- "mt-4 text-sm",
4369
+ "mt-2 max-h-60 overflow-auto rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--muted))]/20 px-3 py-2 text-sm",
4336
4370
  "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
4337
4371
  className
4338
4372
  ),
@@ -4346,7 +4380,7 @@ function compactReasoningText(text) {
4346
4380
  }
4347
4381
  function ThinkingBadge({ reasoning, variant = "inline", onClick }) {
4348
4382
  const compactReasoning = compactReasoningText(reasoning);
4349
- const [open, setOpen] = useState9(false);
4383
+ const [open, setOpen] = useState10(false);
4350
4384
  return /* @__PURE__ */ jsxs10(
4351
4385
  "span",
4352
4386
  {
@@ -4423,10 +4457,10 @@ ReasoningContent.displayName = "ReasoningContent";
4423
4457
 
4424
4458
  // src/react/components/chat/AgentLoopBlock.tsx
4425
4459
  import { Bot, Check as Check3, ChevronRight as ChevronRight4, FileText as FileText6, Loader2 as Loader24, MessageSquareMore as MessageSquareMore2 } from "lucide-react";
4426
- import { useEffect as useEffect13, useMemo as useMemo14, useState as useState16 } from "react";
4460
+ import { useEffect as useEffect13, useMemo as useMemo14, useState as useState17 } from "react";
4427
4461
 
4428
4462
  // src/react/components/chat/ResourceIframe.tsx
4429
- import { useEffect as useEffect10, useEffectEvent as useEffectEvent3, useRef as useRef8, useState as useState10 } from "react";
4463
+ import { useEffect as useEffect10, useEffectEvent as useEffectEvent3, useRef as useRef9, useState as useState11 } from "react";
4430
4464
  import { jsx as jsx13 } from "react/jsx-runtime";
4431
4465
  function isResourceBridgeMessage(value) {
4432
4466
  return typeof value === "object" && value !== null && value.__resourceBridge === true && typeof value.action === "string";
@@ -4435,8 +4469,8 @@ var INLINE_HEIGHT_MIN = 80;
4435
4469
  var INLINE_HEIGHT_MAX = 6e3;
4436
4470
  var INLINE_HEIGHT_PADDING = 8;
4437
4471
  function ResourceIframe({ ui, sessionId }) {
4438
- const iframeRef = useRef8(null);
4439
- const iframeKeyRef = useRef8(
4472
+ const iframeRef = useRef9(null);
4473
+ const iframeKeyRef = useRef9(
4440
4474
  `iframe-${Math.random().toString(36).slice(2, 10)}`
4441
4475
  );
4442
4476
  const activeSessionId = useSessionStore((state) => state.activeSessionId);
@@ -4444,7 +4478,7 @@ function ResourceIframe({ ui, sessionId }) {
4444
4478
  const theme = useUiStore((state) => state.theme);
4445
4479
  const resourceUri = ui.resourceUri ?? ui.resourceURI;
4446
4480
  const iframeLabel = ui.title ?? resourceUri ?? "\u5DE5\u5177\u754C\u9762";
4447
- const [autoHeight, setAutoHeight] = useState10(null);
4481
+ const [autoHeight, setAutoHeight] = useState11(null);
4448
4482
  useEffect10(() => {
4449
4483
  setAutoHeight(null);
4450
4484
  }, [ui.resourceHTML, resourceUri]);
@@ -4555,7 +4589,7 @@ function ResourceIframe({ ui, sessionId }) {
4555
4589
 
4556
4590
  // src/react/components/chat/ToolCallBlock.tsx
4557
4591
  import { Check, ChevronRight as ChevronRight3, Loader2 as Loader23, MessageSquareMore, PanelRightOpen, X as X4 } from "lucide-react";
4558
- import { useMemo as useMemo12, useState as useState13 } from "react";
4592
+ import { useMemo as useMemo12, useState as useState14 } from "react";
4559
4593
 
4560
4594
  // src/react/components/chat/tool-renderers/shared.tsx
4561
4595
  import { jsx as jsx14 } from "react/jsx-runtime";
@@ -4882,21 +4916,21 @@ function FileEditRenderer({ toolCall }) {
4882
4916
  }
4883
4917
 
4884
4918
  // src/react/components/chat/tool-renderers/FileReadRenderer.tsx
4885
- import { useState as useState12 } from "react";
4919
+ import { useState as useState13 } from "react";
4886
4920
 
4887
4921
  // src/react/components/chat/ImageLightbox.tsx
4888
4922
  import { ChevronLeft, ChevronRight as ChevronRight2, Download as Download2, ExternalLink, Minus, Plus as Plus2, RotateCcw, X as X3 } from "lucide-react";
4889
- import { useCallback as useCallback10, useEffect as useEffect11, useRef as useRef9, useState as useState11 } from "react";
4923
+ import { useCallback as useCallback10, useEffect as useEffect11, useRef as useRef10, useState as useState12 } from "react";
4890
4924
  import { createPortal as createPortal3 } from "react-dom";
4891
4925
  import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
4892
4926
  function ImageLightbox({ open, onOpenChange, images, initialIndex = 0 }) {
4893
- const [currentIndex, setCurrentIndex] = useState11(initialIndex);
4894
- const [scale, setScale] = useState11(1);
4895
- const [translate, setTranslate] = useState11({ x: 0, y: 0 });
4896
- const dragging = useRef9(false);
4897
- const dragStart = useRef9({ x: 0, y: 0 });
4898
- const translateStart = useRef9({ x: 0, y: 0 });
4899
- const imgRef = useRef9(null);
4927
+ const [currentIndex, setCurrentIndex] = useState12(initialIndex);
4928
+ const [scale, setScale] = useState12(1);
4929
+ const [translate, setTranslate] = useState12({ x: 0, y: 0 });
4930
+ const dragging = useRef10(false);
4931
+ const dragStart = useRef10({ x: 0, y: 0 });
4932
+ const translateStart = useRef10({ x: 0, y: 0 });
4933
+ const imgRef = useRef10(null);
4900
4934
  const reset = useCallback10(() => {
4901
4935
  setScale(1);
4902
4936
  setTranslate({ x: 0, y: 0 });
@@ -4915,7 +4949,7 @@ function ImageLightbox({ open, onOpenChange, images, initialIndex = 0 }) {
4915
4949
  setCurrentIndex((i) => Math.min(images.length - 1, i + 1));
4916
4950
  reset();
4917
4951
  }, [images.length, reset]);
4918
- const prevOpenRef = useRef9(false);
4952
+ const prevOpenRef = useRef10(false);
4919
4953
  useEffect11(() => {
4920
4954
  if (open && !prevOpenRef.current) {
4921
4955
  setCurrentIndex(initialIndex);
@@ -5205,7 +5239,7 @@ function formatLineRange(startLine, endLine) {
5205
5239
  return startLine === endLine ? `\u7B2C ${startLine} \u884C` : `\u7B2C ${startLine}-${endLine} \u884C`;
5206
5240
  }
5207
5241
  function FileReadRenderer({ toolCall }) {
5208
- const [lightboxIndex, setLightboxIndex] = useState12(null);
5242
+ const [lightboxIndex, setLightboxIndex] = useState13(null);
5209
5243
  const argsValue = parseJsonValue(toolCall.arguments);
5210
5244
  const args = isPlainObject(argsValue) ? argsValue : null;
5211
5245
  const filePath = extractToolFilePath(toolCall);
@@ -5503,7 +5537,7 @@ function ToolCallBlock({
5503
5537
  reasoning,
5504
5538
  customization
5505
5539
  }) {
5506
- const [expanded, setExpanded] = useState13(false);
5540
+ const [expanded, setExpanded] = useState14(false);
5507
5541
  const activeSessionId = useSessionStore((s) => s.activeSessionId);
5508
5542
  const sessions = useSessionStore((s) => s.sessions);
5509
5543
  const pushArtifact = useUiStore((s) => s.pushArtifact);
@@ -5707,7 +5741,7 @@ function buildAskUserPayload(argumentsJson) {
5707
5741
  }
5708
5742
 
5709
5743
  // src/react/components/chat/UserMessageBubble.tsx
5710
- import { useState as useState15 } from "react";
5744
+ import { useState as useState16 } from "react";
5711
5745
 
5712
5746
  // src/react/lib/preview-dispatch.ts
5713
5747
  var IMAGE_EXTS = [
@@ -6040,10 +6074,10 @@ function MessageFileAttachmentList({
6040
6074
 
6041
6075
  // src/react/components/chat/MessageActions.tsx
6042
6076
  import { Check as Check2, Copy } from "lucide-react";
6043
- import { useState as useState14 } from "react";
6077
+ import { useState as useState15 } from "react";
6044
6078
  import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
6045
6079
  function MessageActions({ content, className }) {
6046
- const [copied, setCopied] = useState14(false);
6080
+ const [copied, setCopied] = useState15(false);
6047
6081
  const handleCopy = async () => {
6048
6082
  const ok = await copyToClipboard(content);
6049
6083
  if (ok) {
@@ -6233,8 +6267,8 @@ function UserMessageBubble({ message, className }) {
6233
6267
  alt: attachment.name || "\u7528\u6237\u4E0A\u4F20\u7684\u56FE\u7247"
6234
6268
  }))
6235
6269
  ];
6236
- const [lightboxIndex, setLightboxIndex] = useState15(null);
6237
- const [preview, setPreview] = useState15(null);
6270
+ const [lightboxIndex, setLightboxIndex] = useState16(null);
6271
+ const [preview, setPreview] = useState16(null);
6238
6272
  const handleTextAttachmentPreview = (attachment) => {
6239
6273
  if (!activeSessionId) return;
6240
6274
  const pathForUrl = attachment.uploadedPath ?? attachment.name;
@@ -6401,7 +6435,7 @@ function AgentLoopBlock({ toolCall, sessionId, reasoning }) {
6401
6435
  () => visibleLoopToolCalls.some((childToolCall) => childToolCall.status === "awaiting_answer"),
6402
6436
  [visibleLoopToolCalls]
6403
6437
  );
6404
- const [expanded, setExpanded] = useState16(hasAwaitingAnswer);
6438
+ const [expanded, setExpanded] = useState17(hasAwaitingAnswer);
6405
6439
  const completedToolLabels = useMemo14(
6406
6440
  () => visibleLoopToolCalls.flatMap((childToolCall) => {
6407
6441
  const isCompleted = childToolCall.status === "done" || childToolCall.status !== "pending" && childToolCall.status !== "awaiting_answer" && childToolCall.status !== "error" && childToolCall.status !== "cancelled";
@@ -6653,12 +6687,9 @@ function ExpandedChildAssistantMessage({
6653
6687
  const text = typeof message.content === "string" ? message.content.trim() : message.content.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
6654
6688
  const toolCalls = message.tool_calls ?? [];
6655
6689
  const hasToolCalls = toolCalls.length > 0;
6656
- const firstToolCallId = toolCalls[0]?.id;
6657
- const toolReasoning = !isStreaming && !text && hasToolCalls ? message.reasoning : void 0;
6658
- const standaloneReasoning = !isStreaming && !text && !hasToolCalls ? message.reasoning : void 0;
6659
6690
  return /* @__PURE__ */ jsxs24("div", { className: "flex flex-col gap-2", children: [
6660
- message.reasoning && isStreaming && /* @__PURE__ */ jsxs24(Reasoning, { isStreaming, children: [
6661
- /* @__PURE__ */ jsx29(ReasoningTrigger, {}),
6691
+ message.reasoning && /* @__PURE__ */ jsxs24(Reasoning, { isStreaming, children: [
6692
+ /* @__PURE__ */ jsx29(ReasoningTrigger, { wordCount: message.reasoning.length }),
6662
6693
  /* @__PURE__ */ jsx29(ReasoningContent, { children: message.reasoning })
6663
6694
  ] }),
6664
6695
  text ? /* @__PURE__ */ jsx29(
@@ -6667,18 +6698,17 @@ function ExpandedChildAssistantMessage({
6667
6698
  text,
6668
6699
  isStreaming,
6669
6700
  sessionId,
6670
- reasoning: !isStreaming ? message.reasoning : void 0
6701
+ reasoning: void 0
6671
6702
  }
6672
6703
  ) : null,
6673
6704
  !text && isStreaming && /* @__PURE__ */ jsx29("span", { className: "pl-8 text-[12px] text-[hsl(var(--muted-foreground))]", children: "\u6B63\u5728\u751F\u6210..." }),
6674
- standaloneReasoning ? /* @__PURE__ */ jsx29("div", { className: "pl-8", children: /* @__PURE__ */ jsx29(ThinkingBadge, { reasoning: standaloneReasoning, variant: "block" }) }) : null,
6675
6705
  hasToolCalls && /* @__PURE__ */ jsx29("div", { className: "flex flex-col gap-2", children: toolCalls.map(
6676
- (toolCall, index) => formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx29(
6706
+ (toolCall) => formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx29(
6677
6707
  AgentLoopBlock,
6678
6708
  {
6679
6709
  toolCall,
6680
6710
  sessionId,
6681
- reasoning: toolCall.id === firstToolCallId ? toolReasoning : void 0
6711
+ reasoning: void 0
6682
6712
  },
6683
6713
  toolCall.id
6684
6714
  ) : /* @__PURE__ */ jsx29(
@@ -6688,7 +6718,7 @@ function ExpandedChildAssistantMessage({
6688
6718
  sessionId,
6689
6719
  level: 2,
6690
6720
  turnBlocks: message.blocks,
6691
- reasoning: index === 0 ? toolReasoning : void 0
6721
+ reasoning: void 0
6692
6722
  },
6693
6723
  toolCall.id
6694
6724
  )
@@ -6910,10 +6940,10 @@ function AssistantTurnBlock({
6910
6940
  ),
6911
6941
  [allToolCalls]
6912
6942
  );
6913
- const [displayMode, setDisplayMode] = useState17(
6943
+ const [displayMode, setDisplayMode] = useState18(
6914
6944
  defaultTurnDisplayMode({ forceExpanded, hasActionableToolCall })
6915
6945
  );
6916
- const wasStreamingRef = useRef10(isStreaming);
6946
+ const wasStreamingRef = useRef11(isStreaming);
6917
6947
  useEffect14(() => {
6918
6948
  if (wasStreamingRef.current && !isStreaming && !forceExpanded) {
6919
6949
  setDisplayMode(defaultTurnDisplayMode({ forceExpanded, hasActionableToolCall }));
@@ -7012,23 +7042,18 @@ function AssistantMessages({
7012
7042
  return messages.map(
7013
7043
  (message, index) => isRenderableAssistantMessage(message, isStreaming && index === messages.length - 1) ? (() => {
7014
7044
  const isStreamingLastMessage = isStreaming && index === messages.length - 1;
7015
- const hasText = !!getMessageText(message);
7016
7045
  const reasoning = message.reasoning;
7017
7046
  const hasReasoning = !!reasoning;
7018
- const hasAttachments = getImageParts(message.content).length > 0 || getFileParts(message.content).length > 0;
7019
7047
  const toolCalls = message.tool_calls ?? [];
7020
7048
  const hasToolCalls = toolCalls.length > 0;
7021
- const firstToolCallId = toolCalls[0]?.id;
7022
7049
  const toolRenderItems = groupDetailedToolCalls(toolCalls);
7023
- const toolReasoning = hasReasoning && !hasText && !hasAttachments && hasToolCalls && !isStreamingLastMessage ? reasoning : void 0;
7024
- const contentReasoning = hasReasoning && !isStreamingLastMessage && (hasText || hasAttachments || !hasToolCalls) ? reasoning : void 0;
7025
7050
  return /* @__PURE__ */ jsxs25(
7026
7051
  "div",
7027
7052
  {
7028
7053
  className: "flex flex-col gap-3",
7029
7054
  children: [
7030
- hasReasoning && isStreamingLastMessage && /* @__PURE__ */ jsxs25(Reasoning, { isStreaming: isStreamingLastMessage, children: [
7031
- /* @__PURE__ */ jsx30(ReasoningTrigger, {}),
7055
+ hasReasoning && /* @__PURE__ */ jsxs25(Reasoning, { isStreaming: isStreamingLastMessage, children: [
7056
+ /* @__PURE__ */ jsx30(ReasoningTrigger, { wordCount: reasoning.length }),
7032
7057
  /* @__PURE__ */ jsx30(ReasoningContent, { children: reasoning })
7033
7058
  ] }),
7034
7059
  /* @__PURE__ */ jsx30(
@@ -7037,7 +7062,7 @@ function AssistantMessages({
7037
7062
  message,
7038
7063
  isStreaming: isStreamingLastMessage,
7039
7064
  sessionId,
7040
- reasoning: contentReasoning,
7065
+ reasoning: void 0,
7041
7066
  className: customization?.classNames?.assistantText
7042
7067
  }
7043
7068
  ),
@@ -7048,7 +7073,7 @@ function AssistantMessages({
7048
7073
  {
7049
7074
  toolCalls: item.toolCalls,
7050
7075
  kind: item.kind,
7051
- reasoning: item.toolCalls[0]?.id === firstToolCallId ? toolReasoning : void 0,
7076
+ reasoning: void 0,
7052
7077
  sessionId,
7053
7078
  sessionStatus,
7054
7079
  askAnswers,
@@ -7066,7 +7091,7 @@ function AssistantMessages({
7066
7091
  {
7067
7092
  toolCall,
7068
7093
  sessionId,
7069
- reasoning: toolCall.id === firstToolCallId ? toolReasoning : void 0
7094
+ reasoning: void 0
7070
7095
  },
7071
7096
  toolCall.id
7072
7097
  ) : customization?.components?.ToolCall ? /* @__PURE__ */ jsx30(
@@ -7080,7 +7105,7 @@ function AssistantMessages({
7080
7105
  sessionStatus,
7081
7106
  level,
7082
7107
  turnBlocks: message.blocks,
7083
- reasoning: toolCall.id === firstToolCallId ? toolReasoning : void 0,
7108
+ reasoning: void 0,
7084
7109
  customization
7085
7110
  },
7086
7111
  toolCall.id
@@ -7095,7 +7120,7 @@ function AssistantMessages({
7095
7120
  sessionStatus,
7096
7121
  level,
7097
7122
  turnBlocks: message.blocks,
7098
- reasoning: toolCall.id === firstToolCallId ? toolReasoning : void 0,
7123
+ reasoning: void 0,
7099
7124
  customization
7100
7125
  },
7101
7126
  toolCall.id
@@ -7137,7 +7162,7 @@ function CompactToolGroupBlock({
7137
7162
  level,
7138
7163
  customization
7139
7164
  }) {
7140
- const [expanded, setExpanded] = useState17(false);
7165
+ const [expanded, setExpanded] = useState18(false);
7141
7166
  const indentClass = level === 2 ? "ml-3" : "ml-4";
7142
7167
  const hasError = toolCalls.some(
7143
7168
  (tc) => tc.status === "error" || tc.status === "cancelled"
@@ -7303,7 +7328,7 @@ function AssistantText({
7303
7328
  }
7304
7329
  function MemoryRefsHint({ refs: rawRefs }) {
7305
7330
  const refs = Array.isArray(rawRefs) ? rawRefs : [];
7306
- const [expanded, setExpanded] = useState17(false);
7331
+ const [expanded, setExpanded] = useState18(false);
7307
7332
  const hasSkill = refs.some((r3) => r3.skill_name);
7308
7333
  const label = hasSkill ? "\u53C2\u8003\u4E86\u8BE5\u6280\u80FD\u7684\u5386\u53F2\u7ECF\u9A8C" : "\u53C2\u8003\u4E86\u5386\u53F2\u7ECF\u9A8C";
7309
7334
  return /* @__PURE__ */ jsxs25("div", { className: "ml-4", children: [
@@ -7351,7 +7376,7 @@ function MemoryRefsHint({ refs: rawRefs }) {
7351
7376
 
7352
7377
  // src/react/components/chat/CompactionCard.tsx
7353
7378
  import { ChevronDown as ChevronDown2, ChevronRight as ChevronRight6, Loader2 as Loader25, Square as Square3, XCircle } from "lucide-react";
7354
- import { useState as useState18 } from "react";
7379
+ import { useState as useState19 } from "react";
7355
7380
  import { jsx as jsx31, jsxs as jsxs26 } from "react/jsx-runtime";
7356
7381
  var PERCENT_FORMATTER = new Intl.NumberFormat("zh-CN", {
7357
7382
  style: "percent",
@@ -7411,7 +7436,7 @@ function CompactionCard({
7411
7436
  ...compaction,
7412
7437
  status: status ?? "completed"
7413
7438
  } : activeCompaction;
7414
- const [expanded, setExpanded] = useState18(false);
7439
+ const [expanded, setExpanded] = useState19(false);
7415
7440
  if (!source || !source.compaction_id) {
7416
7441
  return null;
7417
7442
  }
@@ -7453,7 +7478,7 @@ function CompactionCard({
7453
7478
  "button",
7454
7479
  {
7455
7480
  type: "button",
7456
- onClick: () => getSocket().stop(sessionId),
7481
+ onClick: () => void getSocket().stop(sessionId),
7457
7482
  className: "shrink-0 rounded-md border border-current/15 px-2 py-0.5 text-[11px] transition-opacity hover:opacity-80",
7458
7483
  children: "\u53D6\u6D88\u6574\u7406"
7459
7484
  }
@@ -7572,7 +7597,7 @@ import {
7572
7597
  TerminalSquare,
7573
7598
  WandSparkles
7574
7599
  } from "lucide-react";
7575
- import { useEffect as useEffect15, useMemo as useMemo16, useState as useState19 } from "react";
7600
+ import { useEffect as useEffect15, useMemo as useMemo16, useState as useState20 } from "react";
7576
7601
  import { jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
7577
7602
  var EMPTY_EVENTS = [];
7578
7603
  function formatElapsedDuration(durationMs) {
@@ -7651,7 +7676,7 @@ function getTurnStartedAt(events) {
7651
7676
  return null;
7652
7677
  }
7653
7678
  function useElapsedDuration(startedAt, active) {
7654
- const [now, setNow] = useState19(() => Date.now());
7679
+ const [now, setNow] = useState20(() => Date.now());
7655
7680
  useEffect15(() => {
7656
7681
  if (!active || startedAt == null) {
7657
7682
  return;
@@ -7943,10 +7968,10 @@ function MessageList({
7943
7968
  ) ?? null,
7944
7969
  [lastTurnId, renderBlocks]
7945
7970
  );
7946
- const containerRef = useRef11(null);
7947
- const scrollContainerRef = useRef11(null);
7948
- const frameRef = useRef11(null);
7949
- const [activeTurnId, setActiveTurnId] = useState20(lastTurnId);
7971
+ const containerRef = useRef12(null);
7972
+ const scrollContainerRef = useRef12(null);
7973
+ const frameRef = useRef12(null);
7974
+ const [activeTurnId, setActiveTurnId] = useState21(lastTurnId);
7950
7975
  const layoutSignature = useMemo17(
7951
7976
  () => getMessagesMeasureSignature(messages),
7952
7977
  [messages]
@@ -8244,6 +8269,7 @@ function ChatView({
8244
8269
  const {
8245
8270
  messages,
8246
8271
  isStreaming,
8272
+ isStopping,
8247
8273
  send,
8248
8274
  stop
8249
8275
  } = useChat(sessionId);
@@ -8291,6 +8317,7 @@ function ChatView({
8291
8317
  onSend: (msg, _targetSessionId, model) => send(msg, mode, void 0, { model: model || void 0 }),
8292
8318
  onStop: stop,
8293
8319
  isStreaming,
8320
+ isStopping,
8294
8321
  mode,
8295
8322
  onToggleMode: toggleMode,
8296
8323
  renderAttachments,
@@ -8360,4 +8387,4 @@ use-stick-to-bottom/dist/StickToBottom.js:
8360
8387
  * Licensed under the MIT License. See License.txt in the project root for license information.
8361
8388
  *--------------------------------------------------------------------------------------------*)
8362
8389
  */
8363
- //# sourceMappingURL=chunk-EV225FLR.js.map
8390
+ //# sourceMappingURL=chunk-RTBAPZIO.js.map