@blade-hq/agent-kit 0.5.6 → 0.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/{SkillStatusBar-CvNCQRtt.d.ts → SkillStatusBar-Dlf-_G5d.d.ts} +2 -2
  2. package/dist/{blade-client-B6xMtRwr.d.ts → blade-client-7VANnJfr.d.ts} +5 -1
  3. package/dist/{chunk-CBO2A567.js → chunk-ETHPRRT2.js} +9 -1
  4. package/dist/chunk-ETHPRRT2.js.map +1 -0
  5. package/dist/{chunk-VPWN6VS4.js → chunk-G2LJZTPX.js} +178 -93
  6. package/dist/chunk-G2LJZTPX.js.map +1 -0
  7. package/dist/{chunk-PX53WJ2C.js → chunk-GIE2Q2MB.js} +28 -16
  8. package/dist/chunk-GIE2Q2MB.js.map +1 -0
  9. package/dist/{chunk-T3G4VHAM.js → chunk-STCTXRMJ.js} +2 -2
  10. package/dist/{chunk-D7IT5PRL.js → chunk-UM7G65GH.js} +116 -20
  11. package/dist/chunk-UM7G65GH.js.map +1 -0
  12. package/dist/{chunk-NOUG4L43.js → chunk-X3S36RR2.js} +2 -2
  13. package/dist/client/index.d.ts +1051 -17
  14. package/dist/client/index.js +1 -1
  15. package/dist/react/api/vibe-coding.d.ts +3 -3
  16. package/dist/react/api/vibe-coding.js +2 -2
  17. package/dist/react/components/chat/index.d.ts +6 -5
  18. package/dist/react/components/chat/index.js +5 -5
  19. package/dist/react/components/plan/index.js +3 -3
  20. package/dist/react/components/session/index.d.ts +1 -1
  21. package/dist/react/components/session/index.js +3 -3
  22. package/dist/react/components/workspace/index.js +3 -3
  23. package/dist/react/index.d.ts +177 -8
  24. package/dist/react/index.js +469 -6
  25. package/dist/react/index.js.map +1 -1
  26. package/dist/{session-ADRevzHD.d.ts → session-BuaeCsMC.d.ts} +62 -2
  27. package/dist/style.css +1 -1
  28. package/package.json +1 -1
  29. package/dist/chunk-CBO2A567.js.map +0 -1
  30. package/dist/chunk-D7IT5PRL.js.map +0 -1
  31. package/dist/chunk-PX53WJ2C.js.map +0 -1
  32. package/dist/chunk-VPWN6VS4.js.map +0 -1
  33. /package/dist/{chunk-T3G4VHAM.js.map → chunk-STCTXRMJ.js.map} +0 -0
  34. /package/dist/{chunk-NOUG4L43.js.map → chunk-X3S36RR2.js.map} +0 -0
@@ -8,14 +8,15 @@ import {
8
8
  getCodeLanguageFromFilename,
9
9
  parseAskUserQuestion,
10
10
  useHighlightedCodeHtml
11
- } from "./chunk-PX53WJ2C.js";
11
+ } from "./chunk-GIE2Q2MB.js";
12
12
  import {
13
13
  Collapsible,
14
14
  CollapsibleContent,
15
15
  CollapsibleTrigger,
16
16
  resolveSessionFilePreviewTarget
17
- } from "./chunk-NOUG4L43.js";
17
+ } from "./chunk-X3S36RR2.js";
18
18
  import {
19
+ apiFetchResponse,
19
20
  buildMessageContent,
20
21
  buildToolPreviewKey,
21
22
  contentPreview,
@@ -57,7 +58,7 @@ import {
57
58
  useUiBridgeStore,
58
59
  useUiStore,
59
60
  writeFile
60
- } from "./chunk-D7IT5PRL.js";
61
+ } from "./chunk-UM7G65GH.js";
61
62
  import {
62
63
  registerBridgeIframe,
63
64
  tapBridgeEvent
@@ -66,7 +67,7 @@ import {
66
67
  ModelOption,
67
68
  ModelsConfig,
68
69
  ModelsResource
69
- } from "./chunk-CBO2A567.js";
70
+ } from "./chunk-ETHPRRT2.js";
70
71
  import {
71
72
  cn,
72
73
  copyToClipboard
@@ -77,7 +78,7 @@ import {
77
78
 
78
79
  // src/react/components/chat/ChatView.tsx
79
80
  import { Eye } from "lucide-react";
80
- import { useCallback as useCallback13 } from "react";
81
+ import { useCallback as useCallback13, useMemo as useMemo18 } from "react";
81
82
 
82
83
  // src/react/hooks/use-chat.ts
83
84
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -2121,11 +2122,22 @@ function isImageAttachment(attachment) {
2121
2122
  function buildSubmitPayload(text, attachments, pendingContexts, onBeforeSend) {
2122
2123
  const validAttachments = attachments.filter((attachment) => attachment.status !== "failed");
2123
2124
  const payload = buildMessageContent(text, validAttachments);
2124
- const contextParts = pendingContexts.map((context) => ({
2125
- type: "text",
2126
- text: `[\u4E0A\u4E0B\u6587: ${context.label}]
2125
+ const contextParts = pendingContexts.flatMap((context) => {
2126
+ const parts = [
2127
+ {
2128
+ type: "text",
2129
+ text: `[\u4E0A\u4E0B\u6587: ${context.label}]
2127
2130
  ${context.content}`
2128
- }));
2131
+ }
2132
+ ];
2133
+ if (context.imageUrl) {
2134
+ parts.push({
2135
+ type: "image_url",
2136
+ image_url: { url: context.imageUrl }
2137
+ });
2138
+ }
2139
+ return parts;
2140
+ });
2129
2141
  const mergedPayload = contextParts.length === 0 ? payload : typeof payload === "string" ? payload.trim() ? [...contextParts, { type: "text", text: payload }] : contextParts : [...contextParts, ...payload];
2130
2142
  return onBeforeSend ? onBeforeSend(mergedPayload) : mergedPayload;
2131
2143
  }
@@ -2430,12 +2442,23 @@ function ComposerContextPill({
2430
2442
  id,
2431
2443
  label,
2432
2444
  content,
2445
+ imageUrl,
2433
2446
  onRemove
2434
2447
  }) {
2435
2448
  const [showDetail, setShowDetail] = useState8(false);
2436
2449
  const tokenK = formatTokenK(content);
2437
2450
  return /* @__PURE__ */ jsxs8(Fragment2, { children: [
2438
2451
  /* @__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: [
2452
+ imageUrl ? /* @__PURE__ */ jsx9(
2453
+ "button",
2454
+ {
2455
+ type: "button",
2456
+ onClick: () => setShowDetail(true),
2457
+ className: "h-6 w-6 overflow-hidden rounded-md border border-[hsl(var(--border))] bg-white",
2458
+ title: "\u67E5\u770B\u9009\u4E2D\u533A\u57DF\u622A\u56FE",
2459
+ children: /* @__PURE__ */ jsx9("img", { src: imageUrl, alt: "\u9009\u4E2D\u533A\u57DF\u622A\u56FE", className: "h-full w-full object-cover" })
2460
+ }
2461
+ ) : null,
2439
2462
  /* @__PURE__ */ jsx9(
2440
2463
  "button",
2441
2464
  {
@@ -2481,7 +2504,10 @@ function ComposerContextPill({
2481
2504
  onKeyDown: (e) => e.stopPropagation(),
2482
2505
  children: [
2483
2506
  /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between border-b border-[hsl(var(--border))] px-6 py-3", children: [
2484
- /* @__PURE__ */ jsx9("h4", { className: "text-xs font-semibold text-[hsl(var(--foreground))]", children: label }),
2507
+ /* @__PURE__ */ jsxs8("div", { className: "min-w-0", children: [
2508
+ /* @__PURE__ */ jsx9("h4", { className: "truncate text-xs font-semibold text-[hsl(var(--foreground))]", children: label }),
2509
+ imageUrl ? /* @__PURE__ */ jsx9("p", { className: "mt-0.5 text-[11px] text-[hsl(var(--muted-foreground))]", children: "\u5DF2\u9644\u52A0\u9009\u4E2D\u533A\u57DF\u622A\u56FE\uFF0C\u975E\u591A\u6A21\u6001\u6A21\u578B\u4F1A\u81EA\u52A8\u5FFD\u7565\u56FE\u7247\u3002" }) : null
2510
+ ] }),
2485
2511
  /* @__PURE__ */ jsx9(
2486
2512
  "button",
2487
2513
  {
@@ -2492,7 +2518,10 @@ function ComposerContextPill({
2492
2518
  }
2493
2519
  )
2494
2520
  ] }),
2495
- /* @__PURE__ */ jsx9("div", { className: "min-h-0 flex-1 overflow-y-auto px-6 py-4", children: /* @__PURE__ */ jsx9("pre", { className: "whitespace-pre-wrap break-words font-mono text-[11px] leading-[1.6] text-[hsl(var(--foreground)/0.85)]", children: content }) })
2521
+ /* @__PURE__ */ jsxs8("div", { className: "min-h-0 flex-1 overflow-y-auto px-6 py-4", children: [
2522
+ imageUrl ? /* @__PURE__ */ jsx9("div", { className: "mb-4 rounded-xl border border-[hsl(var(--border))] bg-white p-2", children: /* @__PURE__ */ jsx9("img", { src: imageUrl, alt: "\u9009\u4E2D\u533A\u57DF\u622A\u56FE", className: "max-h-72 max-w-full rounded-lg object-contain" }) }) : null,
2523
+ /* @__PURE__ */ jsx9("pre", { className: "whitespace-pre-wrap break-words font-mono text-[11px] leading-[1.6] text-[hsl(var(--foreground)/0.85)]", children: content })
2524
+ ] })
2496
2525
  ]
2497
2526
  }
2498
2527
  )
@@ -3387,6 +3416,7 @@ function ChatInput({
3387
3416
  id: context.id,
3388
3417
  label: context.label,
3389
3418
  content: context.content,
3419
+ imageUrl: context.imageUrl,
3390
3420
  onRemove: (contextId) => {
3391
3421
  if (!activeSessionId) {
3392
3422
  return;
@@ -3751,10 +3781,10 @@ function ConnectionBanner() {
3751
3781
  import { ChevronDown as ChevronDown3, Lightbulb as Lightbulb2, MessageSquare } from "lucide-react";
3752
3782
  import {
3753
3783
  useCallback as useCallback12,
3754
- useEffect as useEffect16,
3784
+ useEffect as useEffect17,
3755
3785
  useEffectEvent as useEffectEvent4,
3756
3786
  useMemo as useMemo17,
3757
- useRef as useRef12,
3787
+ useRef as useRef13,
3758
3788
  useState as useState21
3759
3789
  } from "react";
3760
3790
 
@@ -4184,7 +4214,7 @@ function useStickToBottomContext() {
4184
4214
 
4185
4215
  // src/react/components/chat/AssistantTurnBlock.tsx
4186
4216
  import { AlertCircle, BookOpen, Check as Check4, ChevronRight as ChevronRight5 } from "lucide-react";
4187
- import { useCallback as useCallback11, useEffect as useEffect14, useMemo as useMemo15, useRef as useRef11, useState as useState18 } from "react";
4217
+ import { memo as memo3, useCallback as useCallback11, useEffect as useEffect15, useMemo as useMemo15, useRef as useRef12, useState as useState18 } from "react";
4188
4218
 
4189
4219
  // src/react/routes.ts
4190
4220
  var MEMORIES_ROUTE = "/memories";
@@ -4466,7 +4496,7 @@ ReasoningContent.displayName = "ReasoningContent";
4466
4496
 
4467
4497
  // src/react/components/chat/AgentLoopBlock.tsx
4468
4498
  import { Bot, Check as Check3, ChevronRight as ChevronRight4, FileText as FileText6, Loader2 as Loader24, MessageSquareMore as MessageSquareMore2 } from "lucide-react";
4469
- import { useEffect as useEffect13, useMemo as useMemo14, useState as useState17 } from "react";
4499
+ import { useEffect as useEffect14, useMemo as useMemo14, useState as useState17 } from "react";
4470
4500
 
4471
4501
  // src/react/components/chat/ResourceIframe.tsx
4472
4502
  import { useEffect as useEffect10, useEffectEvent as useEffectEvent3, useRef as useRef9, useState as useState11 } from "react";
@@ -5752,7 +5782,8 @@ function buildAskUserPayload(argumentsJson) {
5752
5782
  }
5753
5783
 
5754
5784
  // src/react/components/chat/UserMessageBubble.tsx
5755
- import { useState as useState16 } from "react";
5785
+ import { useQueries } from "@tanstack/react-query";
5786
+ import { useEffect as useEffect13, useRef as useRef11, useState as useState16 } from "react";
5756
5787
 
5757
5788
  // src/react/lib/preview-dispatch.ts
5758
5789
  var IMAGE_EXTS = [
@@ -6253,29 +6284,47 @@ function UserMessageBubble({ message, className }) {
6253
6284
  )
6254
6285
  ] }) });
6255
6286
  }
6256
- const imageTextAttachments = activeSessionId ? textAttachments.flatMap((attachment) => {
6257
- if (!isImageTextAttachment(attachment.name, attachment.uploadedPath)) {
6258
- return [];
6259
- }
6260
- const pathForUrl = attachment.uploadedPath ?? attachment.name;
6261
- return [
6262
- {
6263
- ...attachment,
6264
- url: buildSessionFileUrl(activeSessionId, pathForUrl)
6265
- }
6266
- ];
6287
+ const imageTextAttachmentMeta = activeSessionId ? textAttachments.flatMap((attachment) => {
6288
+ if (!isImageTextAttachment(attachment.name, attachment.uploadedPath)) return [];
6289
+ return [{ ...attachment, filePath: attachment.uploadedPath ?? attachment.name }];
6267
6290
  }) : [];
6268
6291
  const nonImageTextAttachments = textAttachments.filter(
6269
6292
  (attachment) => !isImageTextAttachment(attachment.name, attachment.uploadedPath)
6270
6293
  );
6294
+ const imageAttachmentQueries = useQueries({
6295
+ queries: imageTextAttachmentMeta.map((att) => ({
6296
+ queryKey: ["session-image", activeSessionId, att.filePath],
6297
+ queryFn: async ({ signal }) => {
6298
+ const apiPath = `/api/sessions/${encodeURIComponent(activeSessionId)}/files/${encodeURIComponent(att.filePath)}`;
6299
+ const res = await apiFetchResponse(apiPath, { signal });
6300
+ const blob = await res.blob();
6301
+ return URL.createObjectURL(blob);
6302
+ },
6303
+ enabled: !!activeSessionId
6304
+ }))
6305
+ });
6306
+ const blobUrlMapRef = useRef11(/* @__PURE__ */ new Map());
6307
+ for (let i = 0; i < imageAttachmentQueries.length; i++) {
6308
+ const url = imageAttachmentQueries[i].data;
6309
+ const key = imageTextAttachmentMeta[i]?.filePath ?? String(i);
6310
+ const prev = blobUrlMapRef.current.get(key);
6311
+ if (prev && url && prev !== url) URL.revokeObjectURL(prev);
6312
+ if (url) blobUrlMapRef.current.set(key, url);
6313
+ }
6314
+ useEffect13(() => {
6315
+ const urls = blobUrlMapRef.current;
6316
+ return () => {
6317
+ for (const u of urls.values()) URL.revokeObjectURL(u);
6318
+ };
6319
+ }, []);
6271
6320
  const lightboxImages = [
6272
6321
  ...imageParts.map((part) => ({
6273
6322
  src: part.image_url.url,
6274
6323
  alt: "\u7528\u6237\u4E0A\u4F20\u7684\u56FE\u7247"
6275
6324
  })),
6276
- ...imageTextAttachments.map((attachment) => ({
6277
- src: attachment.url,
6278
- alt: attachment.name || "\u7528\u6237\u4E0A\u4F20\u7684\u56FE\u7247"
6325
+ ...imageAttachmentQueries.map((q, i) => ({
6326
+ src: q.data ?? "",
6327
+ alt: imageTextAttachmentMeta[i]?.name || "\u7528\u6237\u4E0A\u4F20\u7684\u56FE\u7247"
6279
6328
  }))
6280
6329
  ];
6281
6330
  const [lightboxIndex, setLightboxIndex] = useState16(null);
@@ -6299,7 +6348,7 @@ function UserMessageBubble({ message, className }) {
6299
6348
  setPreview({ filename: attachment.name, url, mode });
6300
6349
  };
6301
6350
  return /* @__PURE__ */ jsx28("div", { className: cn("flex justify-end", className), children: /* @__PURE__ */ jsxs23("div", { className: "group flex max-w-[72%] flex-col items-end gap-3", children: [
6302
- (imageParts.length > 0 || imageTextAttachments.length > 0) && /* @__PURE__ */ jsxs23("div", { className: "grid gap-2", children: [
6351
+ (imageParts.length > 0 || imageTextAttachmentMeta.length > 0) && /* @__PURE__ */ jsxs23("div", { className: "grid gap-2", children: [
6303
6352
  imageParts.map((part, idx) => /* @__PURE__ */ jsx28(
6304
6353
  "button",
6305
6354
  {
@@ -6318,24 +6367,34 @@ function UserMessageBubble({ message, className }) {
6318
6367
  },
6319
6368
  part.image_url.url
6320
6369
  )),
6321
- imageTextAttachments.map((attachment, idx) => /* @__PURE__ */ jsx28(
6322
- "button",
6323
- {
6324
- type: "button",
6325
- onClick: () => setLightboxIndex(imageParts.length + idx),
6326
- className: "cursor-zoom-in",
6327
- title: "\u67E5\u770B\u5927\u56FE",
6328
- children: /* @__PURE__ */ jsx28(
6329
- "img",
6330
- {
6331
- src: attachment.url,
6332
- alt: attachment.name || "\u7528\u6237\u4E0A\u4F20\u7684\u56FE\u7247",
6333
- className: "max-h-64 rounded-xl border border-[hsl(var(--user-msg-border))] object-cover"
6334
- }
6335
- )
6336
- },
6337
- `${attachment.uploadedPath}:${attachment.name}`
6338
- ))
6370
+ imageTextAttachmentMeta.map((att, idx) => {
6371
+ const blobUrl = imageAttachmentQueries[idx]?.data;
6372
+ return blobUrl ? /* @__PURE__ */ jsx28(
6373
+ "button",
6374
+ {
6375
+ type: "button",
6376
+ onClick: () => setLightboxIndex(imageParts.length + idx),
6377
+ className: "cursor-zoom-in",
6378
+ title: "\u67E5\u770B\u5927\u56FE",
6379
+ children: /* @__PURE__ */ jsx28(
6380
+ "img",
6381
+ {
6382
+ src: blobUrl,
6383
+ alt: att.name || "\u7528\u6237\u4E0A\u4F20\u7684\u56FE\u7247",
6384
+ className: "max-h-64 rounded-xl border border-[hsl(var(--user-msg-border))] object-cover"
6385
+ }
6386
+ )
6387
+ },
6388
+ `${att.uploadedPath}:${att.name}`
6389
+ ) : imageAttachmentQueries[idx]?.isError ? null : /* @__PURE__ */ jsx28(
6390
+ "div",
6391
+ {
6392
+ className: "flex h-20 items-center justify-center rounded-xl border border-[hsl(var(--user-msg-border))] bg-[hsl(var(--muted)/0.3)] text-xs text-[hsl(var(--muted-foreground))]",
6393
+ children: "\u52A0\u8F7D\u4E2D..."
6394
+ },
6395
+ `${att.uploadedPath}:${att.name}`
6396
+ );
6397
+ })
6339
6398
  ] }),
6340
6399
  lightboxImages.length > 0 && /* @__PURE__ */ jsx28(
6341
6400
  ImageLightbox,
@@ -6496,7 +6555,7 @@ function AgentLoopBlock({ toolCall, sessionId, reasoning }) {
6496
6555
  ),
6497
6556
  [childMessages]
6498
6557
  );
6499
- useEffect13(() => {
6558
+ useEffect14(() => {
6500
6559
  if (hasAwaitingAnswer) {
6501
6560
  setExpanded(true);
6502
6561
  }
@@ -6820,6 +6879,8 @@ function getLoopCardStyles(status) {
6820
6879
 
6821
6880
  // src/react/components/chat/AssistantTurnBlock.tsx
6822
6881
  import { jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
6882
+ var EMPTY_MESSAGES3 = [];
6883
+ var EMPTY_AGENT_LOOPS2 = {};
6823
6884
  function defaultTurnDisplayMode({
6824
6885
  forceExpanded,
6825
6886
  hasActionableToolCall
@@ -6954,7 +7015,7 @@ function collectTurnMessages({
6954
7015
  });
6955
7016
  return merged;
6956
7017
  }
6957
- function AssistantTurnBlock({
7018
+ function AssistantTurnBlockBase({
6958
7019
  sessionId,
6959
7020
  messages: rawMessages,
6960
7021
  isStreaming = false,
@@ -6967,6 +7028,10 @@ function AssistantTurnBlock({
6967
7028
  }) {
6968
7029
  const messages = Array.isArray(rawMessages) ? rawMessages : [];
6969
7030
  const allToolCalls = useMemo15(() => messages.flatMap((m) => m.tool_calls ?? []), [messages]);
7031
+ const hasAgentToolCall = useMemo15(
7032
+ () => allToolCalls.some((toolCall) => formatToolName(toolCall.name) === "Agent"),
7033
+ [allToolCalls]
7034
+ );
6970
7035
  const hasActionableToolCall = useMemo15(
6971
7036
  () => allToolCalls.some(
6972
7037
  (tc) => tc.status === "error" || tc.status === "cancelled" || formatToolName(tc.name) === "AskUserQuestion" && tc.status === "awaiting_answer"
@@ -6976,18 +7041,22 @@ function AssistantTurnBlock({
6976
7041
  const [displayMode, setDisplayMode] = useState18(
6977
7042
  defaultTurnDisplayMode({ forceExpanded, hasActionableToolCall })
6978
7043
  );
6979
- const wasStreamingRef = useRef11(isStreaming);
6980
- useEffect14(() => {
7044
+ const wasStreamingRef = useRef12(isStreaming);
7045
+ useEffect15(() => {
6981
7046
  if (wasStreamingRef.current && !isStreaming && !forceExpanded) {
6982
7047
  setDisplayMode(defaultTurnDisplayMode({ forceExpanded, hasActionableToolCall }));
6983
7048
  }
6984
7049
  wasStreamingRef.current = isStreaming;
6985
7050
  }, [forceExpanded, hasActionableToolCall, isStreaming]);
6986
- const sessionMessages = useChatStore((state) => state.messages[sessionId] ?? []);
6987
- const agentLoops = useChatStore((state) => state.agentLoops[sessionId] ?? {});
7051
+ const sessionMessages = useChatStore(
7052
+ (state) => hasAgentToolCall ? state.messages[sessionId] ?? EMPTY_MESSAGES3 : EMPTY_MESSAGES3
7053
+ );
7054
+ const agentLoops = useChatStore(
7055
+ (state) => hasAgentToolCall ? state.agentLoops[sessionId] ?? EMPTY_AGENT_LOOPS2 : EMPTY_AGENT_LOOPS2
7056
+ );
6988
7057
  const turnMessages = useMemo15(
6989
- () => collectTurnMessages({ rootMessages: messages, sessionMessages, agentLoops }),
6990
- [agentLoops, messages, sessionMessages]
7058
+ () => hasAgentToolCall ? collectTurnMessages({ rootMessages: messages, sessionMessages, agentLoops }) : messages,
7059
+ [agentLoops, hasAgentToolCall, messages, sessionMessages]
6991
7060
  );
6992
7061
  const resourceBlocks = useMemo15(() => getInlineResourceBlocks(turnMessages), [turnMessages]);
6993
7062
  const finalMessage = useMemo15(() => getLastTextMessage(turnMessages), [turnMessages]);
@@ -7061,6 +7130,10 @@ function AssistantTurnBlock({
7061
7130
  }
7062
7131
  );
7063
7132
  }
7133
+ function areAssistantTurnBlockPropsEqual(prev, next) {
7134
+ return prev.turnKey === next.turnKey && prev.sessionId === next.sessionId && prev.messages === next.messages && prev.isStreaming === next.isStreaming && prev.isLastTurn === next.isLastTurn && prev.askAnswers === next.askAnswers && prev.onAnswer === next.onAnswer && prev.sessionStatus === next.sessionStatus && prev.level === next.level && prev.forceExpanded === next.forceExpanded && prev.customization === next.customization;
7135
+ }
7136
+ var AssistantTurnBlock = memo3(AssistantTurnBlockBase, areAssistantTurnBlockPropsEqual);
7064
7137
  function AssistantMessages({
7065
7138
  messages,
7066
7139
  isStreaming,
@@ -7630,7 +7703,7 @@ import {
7630
7703
  TerminalSquare,
7631
7704
  WandSparkles
7632
7705
  } from "lucide-react";
7633
- import { useEffect as useEffect15, useMemo as useMemo16, useState as useState20 } from "react";
7706
+ import { useEffect as useEffect16, useMemo as useMemo16, useState as useState20 } from "react";
7634
7707
  import { jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
7635
7708
  var EMPTY_EVENTS = [];
7636
7709
  function formatElapsedDuration(durationMs) {
@@ -7710,7 +7783,7 @@ function getTurnStartedAt(events) {
7710
7783
  }
7711
7784
  function useElapsedDuration(startedAt, active) {
7712
7785
  const [now, setNow] = useState20(() => Date.now());
7713
- useEffect15(() => {
7786
+ useEffect16(() => {
7714
7787
  if (!active || startedAt == null) {
7715
7788
  return;
7716
7789
  }
@@ -7846,45 +7919,34 @@ function getAssistantTurnPreview(messages) {
7846
7919
  return "\u672C\u8F6E\u56DE\u590D";
7847
7920
  }
7848
7921
  function getMessageMeasureSignature(message) {
7849
- const contentSize = typeof message.content === "string" ? message.content.length : message.content.reduce((size, part) => size + JSON.stringify(part).length, 0);
7922
+ const contentSize = typeof message.content === "string" ? message.content.length : message.content.length;
7850
7923
  return [
7851
7924
  message.entry_id ?? message.timestamp ?? message.role,
7852
7925
  message.status ?? "",
7853
7926
  message.reasoning?.length ?? 0,
7854
7927
  message.tool_calls?.length ?? 0,
7928
+ message.blocks?.length ?? 0,
7855
7929
  contentSize
7856
7930
  ].join(":");
7857
7931
  }
7858
7932
  function getMessagesMeasureSignature(messages) {
7859
7933
  return messages.map((message) => getMessageMeasureSignature(message)).join("|");
7860
7934
  }
7861
- function safeJson(value) {
7862
- try {
7863
- return JSON.stringify(value);
7864
- } catch {
7865
- return String(value);
7866
- }
7867
- }
7868
- function hashString(value) {
7869
- let hash = 0;
7870
- for (let index = 0; index < value.length; index += 1) {
7871
- hash = hash * 31 + value.charCodeAt(index) | 0;
7872
- }
7873
- return hash.toString(36);
7874
- }
7875
- function getMessageRenderSignature(message) {
7935
+ function getMessageResetSignature(message) {
7936
+ const contentSize = typeof message.content === "string" ? message.content.length : message.content.length;
7937
+ const toolSignature = message.tool_calls?.map((toolCall) => `${toolCall.id}:${toolCall.status}:${toolCall.result == null ? 0 : 1}`).join(",") ?? "";
7876
7938
  return [
7877
7939
  message.entry_id ?? message.timestamp ?? message.role,
7878
7940
  message.status ?? "",
7879
- hashString(safeJson(message.content)),
7880
- hashString(message.reasoning ?? ""),
7881
- hashString(safeJson(message.tool_calls ?? [])),
7882
- hashString(safeJson(message.blocks ?? [])),
7883
- hashString(safeJson(message.memory_refs ?? []))
7941
+ contentSize,
7942
+ message.reasoning?.length ?? 0,
7943
+ message.blocks?.length ?? 0,
7944
+ message.memory_refs?.length ?? 0,
7945
+ toolSignature
7884
7946
  ].join(":");
7885
7947
  }
7886
- function getMessagesRenderSignature(messages) {
7887
- return messages.map((message) => getMessageRenderSignature(message)).join("|");
7948
+ function getMessagesResetSignature(messages) {
7949
+ return messages.map((message) => getMessageResetSignature(message)).join("|");
7888
7950
  }
7889
7951
  function findScrollContainer(start) {
7890
7952
  let current = start;
@@ -7909,7 +7971,17 @@ function MessageList({
7909
7971
  const agentLoops = useChatStore((state) => state.agentLoops[sessionId]);
7910
7972
  const askAnswers = useChatStore((state) => state.askAnswers[sessionId]);
7911
7973
  const agentLoopNameSet = useMemo17(() => new Set(Object.keys(agentLoops ?? {})), [agentLoops]);
7974
+ const assistantMessagesCacheRef = useRef13(/* @__PURE__ */ new Map());
7912
7975
  const renderBlocks = useMemo17(() => {
7976
+ const stableAssistantMessages = (key, buffer) => {
7977
+ const previous = assistantMessagesCacheRef.current.get(key);
7978
+ if (previous && previous.length === buffer.length && previous.every((message, index) => message === buffer[index])) {
7979
+ return previous;
7980
+ }
7981
+ const next = [...buffer];
7982
+ assistantMessagesCacheRef.current.set(key, next);
7983
+ return next;
7984
+ };
7913
7985
  const visible = messages.filter((message) => {
7914
7986
  const loopName = message.loop_name ?? "root";
7915
7987
  if (loopName !== "root") return false;
@@ -7927,13 +7999,15 @@ function MessageList({
7927
7999
  assistantTurnCount += 1;
7928
8000
  const last = assistantBuffer[assistantBuffer.length - 1];
7929
8001
  const isStreaming = assistantBuffer.some((message) => message.status === "streaming");
8002
+ const key = last.entry_id ?? `assistant-${blocks.length}`;
8003
+ const stableMessages = stableAssistantMessages(key, assistantBuffer);
7930
8004
  blocks.push({
7931
8005
  type: "assistant_turn",
7932
- messages: assistantBuffer,
7933
- key: last.entry_id ?? `assistant-${blocks.length}`,
8006
+ messages: stableMessages,
8007
+ key,
7934
8008
  anchorId: `chat-turn-${assistantTurnCount}`,
7935
8009
  isStreaming,
7936
- railTitle: lastUserPreview || getAssistantTurnPreview(assistantBuffer)
8010
+ railTitle: lastUserPreview || getAssistantTurnPreview(stableMessages)
7937
8011
  });
7938
8012
  assistantBuffer = [];
7939
8013
  };
@@ -7972,6 +8046,14 @@ function MessageList({
7972
8046
  });
7973
8047
  }
7974
8048
  flushAssistant();
8049
+ const activeAssistantKeys = new Set(
8050
+ blocks.filter((block) => block.type === "assistant_turn").map((block) => block.key)
8051
+ );
8052
+ for (const key of assistantMessagesCacheRef.current.keys()) {
8053
+ if (!activeAssistantKeys.has(key)) {
8054
+ assistantMessagesCacheRef.current.delete(key);
8055
+ }
8056
+ }
7975
8057
  return blocks;
7976
8058
  }, [agentLoopNameSet, messages]);
7977
8059
  const hasInterruptedTurn = useMemo17(
@@ -8001,9 +8083,9 @@ function MessageList({
8001
8083
  ) ?? null,
8002
8084
  [lastTurnId, renderBlocks]
8003
8085
  );
8004
- const containerRef = useRef12(null);
8005
- const scrollContainerRef = useRef12(null);
8006
- const frameRef = useRef12(null);
8086
+ const containerRef = useRef13(null);
8087
+ const scrollContainerRef = useRef13(null);
8088
+ const frameRef = useRef13(null);
8007
8089
  const [activeTurnId, setActiveTurnId] = useState21(lastTurnId);
8008
8090
  const layoutSignature = useMemo17(
8009
8091
  () => getMessagesMeasureSignature(messages),
@@ -8045,7 +8127,7 @@ function MessageList({
8045
8127
  measureActiveTurn();
8046
8128
  });
8047
8129
  });
8048
- useEffect16(() => {
8130
+ useEffect17(() => {
8049
8131
  scrollContainerRef.current = findScrollContainer(containerRef.current);
8050
8132
  const sc = scrollContainerRef.current;
8051
8133
  if (!sc) return;
@@ -8061,7 +8143,7 @@ function MessageList({
8061
8143
  }
8062
8144
  };
8063
8145
  }, [scheduleMeasure]);
8064
- useEffect16(() => {
8146
+ useEffect17(() => {
8065
8147
  scheduleMeasure();
8066
8148
  }, [lastTurnId, layoutSignature, scheduleMeasure]);
8067
8149
  const handleSelectTurn = useCallback12((turnId) => {
@@ -8191,7 +8273,7 @@ function MessageListContent({
8191
8273
  {
8192
8274
  label: "\u52A9\u624B\u6D88\u606F",
8193
8275
  details: block.key,
8194
- resetKey: getMessagesRenderSignature(block.messages),
8276
+ resetKey: getMessagesResetSignature(block.messages),
8195
8277
  children: customization?.components?.AssistantTurn ? /* @__PURE__ */ jsx35(
8196
8278
  customization.components.AssistantTurn,
8197
8279
  {
@@ -8324,7 +8406,10 @@ function ChatView({
8324
8406
  },
8325
8407
  [send]
8326
8408
  );
8327
- const customization = { classNames, components, renderers };
8409
+ const customization = useMemo18(
8410
+ () => ({ classNames, components, renderers }),
8411
+ [classNames, components, renderers]
8412
+ );
8328
8413
  const SkillStatus = components?.SkillStatusBar;
8329
8414
  return /* @__PURE__ */ jsxs31("div", { className: `flex min-h-0 flex-1 flex-col overflow-hidden ${classNames?.root ?? ""}`, children: [
8330
8415
  isViewer && /* @__PURE__ */ jsxs31("div", { className: `flex items-center justify-center gap-2 border-b border-[hsl(var(--border))] bg-[hsl(var(--card))] py-2 text-xs text-[hsl(var(--muted-foreground))] ${classNames?.viewerBanner ?? ""}`, children: [
@@ -8420,4 +8505,4 @@ use-stick-to-bottom/dist/StickToBottom.js:
8420
8505
  * Licensed under the MIT License. See License.txt in the project root for license information.
8421
8506
  *--------------------------------------------------------------------------------------------*)
8422
8507
  */
8423
- //# sourceMappingURL=chunk-VPWN6VS4.js.map
8508
+ //# sourceMappingURL=chunk-G2LJZTPX.js.map