@blade-hq/agent-kit 1.0.27 → 1.0.29

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 (53) hide show
  1. package/dist/{chunk-M72OL7ZJ.js → chunk-HCQDDDOX.js} +2 -2
  2. package/dist/{chunk-X2NJKKXR.js → chunk-N2OE5MVV.js} +253 -52
  3. package/dist/chunk-N2OE5MVV.js.map +1 -0
  4. package/dist/{chunk-C2FKVGJU.js → chunk-PYYDXV2A.js} +2 -2
  5. package/dist/{chunk-JU5SL4HP.js → chunk-TVWC3PED.js} +2 -2
  6. package/dist/{chunk-SBASG3M2.js → chunk-UWNQ4PXK.js} +4 -1101
  7. package/dist/chunk-UWNQ4PXK.js.map +1 -0
  8. package/dist/{chunk-34GDDOWY.js → chunk-YJ6BX3FU.js} +1379 -31
  9. package/dist/chunk-YJ6BX3FU.js.map +1 -0
  10. package/dist/{chunk-TE6G6NVX.js → chunk-YXHPJGXR.js} +27 -13
  11. package/dist/chunk-YXHPJGXR.js.map +1 -0
  12. package/dist/client/blade-client.d.ts +2 -0
  13. package/dist/client/index.d.ts +1 -0
  14. package/dist/client/index.js +3 -1
  15. package/dist/client/resources/app-dev-templates.d.ts +93 -0
  16. package/dist/client/resources/models.d.ts +3 -0
  17. package/dist/{react → client/shared}/projection/builder.d.ts +1 -1
  18. package/dist/{react → client/shared}/projection/helpers.d.ts +1 -1
  19. package/dist/client/shared/projection/history.d.ts +12 -0
  20. package/dist/{react → client/shared}/projection/index.d.ts +1 -0
  21. package/dist/{react → client/shared}/projection/state.d.ts +1 -1
  22. package/dist/client/types/index.d.ts +2 -2
  23. package/dist/client/types/rest.d.ts +1104 -66
  24. package/dist/client/types/socket-events.d.ts +18 -132
  25. package/dist/react/api/app-dev-templates.d.ts +5 -0
  26. package/dist/react/api/model-config.d.ts +68 -0
  27. package/dist/react/api/models.d.ts +2 -0
  28. package/dist/react/api/published-apps.js +3 -3
  29. package/dist/react/api/sessions.js +2 -2
  30. package/dist/react/components/chat/AskUserQuestionBlock.d.ts +1 -1
  31. package/dist/react/components/chat/ChatInput.d.ts +10 -4
  32. package/dist/react/components/chat/ChatView.d.ts +3 -1
  33. package/dist/react/components/chat/MessageList.d.ts +3 -1
  34. package/dist/react/components/chat/index.d.ts +1 -1
  35. package/dist/react/components/chat/index.js +5 -5
  36. package/dist/react/components/chat/scroll-anchor.d.ts +8 -0
  37. package/dist/react/components/plan/index.js +4 -4
  38. package/dist/react/components/session/index.js +3 -3
  39. package/dist/react/components/workspace/index.js +3 -3
  40. package/dist/react/hooks/use-model-preferences.d.ts +1 -0
  41. package/dist/react/hooks/use-resolved-model.d.ts +1 -0
  42. package/dist/react/index.d.ts +3 -0
  43. package/dist/react/index.js +87 -7
  44. package/dist/react/index.js.map +1 -1
  45. package/dist/react/schemas/session.d.ts +1 -0
  46. package/package.json +1 -1
  47. package/dist/chunk-34GDDOWY.js.map +0 -1
  48. package/dist/chunk-SBASG3M2.js.map +0 -1
  49. package/dist/chunk-TE6G6NVX.js.map +0 -1
  50. package/dist/chunk-X2NJKKXR.js.map +0 -1
  51. /package/dist/{chunk-M72OL7ZJ.js.map → chunk-HCQDDDOX.js.map} +0 -0
  52. /package/dist/{chunk-C2FKVGJU.js.map → chunk-PYYDXV2A.js.map} +0 -0
  53. /package/dist/{chunk-JU5SL4HP.js.map → chunk-TVWC3PED.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getClient
3
- } from "./chunk-SBASG3M2.js";
3
+ } from "./chunk-UWNQ4PXK.js";
4
4
  import {
5
5
  __export
6
6
  } from "./chunk-PZ5AY32C.js";
@@ -20,4 +20,4 @@ export {
20
20
  unpublishApp,
21
21
  published_apps_exports
22
22
  };
23
- //# sourceMappingURL=chunk-M72OL7ZJ.js.map
23
+ //# sourceMappingURL=chunk-HCQDDDOX.js.map
@@ -13,10 +13,10 @@ import {
13
13
  getCodeLanguageFromFilename,
14
14
  parseAskUserQuestion,
15
15
  useHighlightedCodeHtml
16
- } from "./chunk-TE6G6NVX.js";
16
+ } from "./chunk-YXHPJGXR.js";
17
17
  import {
18
18
  resolveSessionFilePreviewTarget
19
- } from "./chunk-C2FKVGJU.js";
19
+ } from "./chunk-PYYDXV2A.js";
20
20
  import {
21
21
  apiFetchResponse,
22
22
  buildMessageContent,
@@ -60,7 +60,7 @@ import {
60
60
  useUiBridgeStore,
61
61
  useUiStore,
62
62
  writeFile
63
- } from "./chunk-SBASG3M2.js";
63
+ } from "./chunk-UWNQ4PXK.js";
64
64
  import {
65
65
  registerBridgeIframe,
66
66
  tapBridgeEvent
@@ -69,7 +69,7 @@ import {
69
69
  ModelOption,
70
70
  ModelsConfig,
71
71
  ModelsResource
72
- } from "./chunk-34GDDOWY.js";
72
+ } from "./chunk-YJ6BX3FU.js";
73
73
  import {
74
74
  cn,
75
75
  copyToClipboard
@@ -704,6 +704,7 @@ function useModelConfig() {
704
704
  return {
705
705
  models: data?.models ?? [],
706
706
  defaultModel: data?.default ?? "",
707
+ thinkingAvailable: data?.thinkingAvailable ?? false,
707
708
  isLoading
708
709
  };
709
710
  }
@@ -1000,9 +1001,9 @@ function useInputHistory(sessionId) {
1000
1001
  // src/react/hooks/use-resolved-model.ts
1001
1002
  import { useMemo as useMemo3 } from "react";
1002
1003
  function useResolvedModel(activeSessionModel) {
1003
- const { models, defaultModel, isLoading } = useModelConfig();
1004
+ const { models, defaultModel, thinkingAvailable, isLoading } = useModelConfig();
1004
1005
  const { preferredModel } = usePreferredModel();
1005
- return useMemo3(() => {
1006
+ const resolved = useMemo3(() => {
1006
1007
  if (isLoading) {
1007
1008
  const optimistic = activeSessionModel || preferredModel || defaultModel || "";
1008
1009
  return {
@@ -1027,6 +1028,8 @@ function useResolvedModel(activeSessionModel) {
1027
1028
  }
1028
1029
  return { resolvedModel: "", hasAvailableModel: false, isLoading };
1029
1030
  }, [activeSessionModel, preferredModel, defaultModel, models, isLoading]);
1031
+ const selectedThinkingAvailable = models.find((model) => model.id === resolved.resolvedModel)?.thinkingAvailable ?? thinkingAvailable;
1032
+ return { ...resolved, thinkingAvailable: selectedThinkingAvailable };
1030
1033
  }
1031
1034
 
1032
1035
  // src/react/lib/attachment-upload.ts
@@ -2849,6 +2852,7 @@ function ChatInput({
2849
2852
  onCommand,
2850
2853
  canShareSession = false,
2851
2854
  slotAboveTextarea,
2855
+ slotBeforeSend,
2852
2856
  externalDraft,
2853
2857
  externalAttachments,
2854
2858
  maxWidthClassName = "max-w-3xl",
@@ -2935,7 +2939,12 @@ function ChatInput({
2935
2939
  const draftAppends = useUiBridgeStore(
2936
2940
  (state) => activeSessionId ? state.draftAppends[activeSessionId] : void 0
2937
2941
  );
2938
- const { resolvedModel, hasAvailableModel, isLoading: isModelLoading } = useResolvedModel(activeSessionModel);
2942
+ const {
2943
+ resolvedModel,
2944
+ hasAvailableModel,
2945
+ thinkingAvailable,
2946
+ isLoading: isModelLoading
2947
+ } = useResolvedModel(activeSessionModel);
2939
2948
  useEffect7(() => {
2940
2949
  setSelectedModel(resolvedModel);
2941
2950
  }, [resolvedModel]);
@@ -3301,6 +3310,7 @@ function ChatInput({
3301
3310
  setRewindDraft(activeSessionId, null);
3302
3311
  }, [activeSessionId, editor, rewindDraft, setRewindDraft]);
3303
3312
  const submittingRef = useRef5(false);
3313
+ const [isSubmitting, setIsSubmitting] = useState8(false);
3304
3314
  useEffect7(() => {
3305
3315
  let changed = false;
3306
3316
  const nextLocalIds = /* @__PURE__ */ new Set();
@@ -3425,6 +3435,7 @@ function ChatInput({
3425
3435
  return;
3426
3436
  }
3427
3437
  submittingRef.current = true;
3438
+ setIsSubmitting(true);
3428
3439
  try {
3429
3440
  const json = editor?.getJSON();
3430
3441
  const rawText = serializeEditorContent(json);
@@ -3437,9 +3448,15 @@ function ChatInput({
3437
3448
  let uploadTargetSessionId;
3438
3449
  if (pendingFiles.length > 0) {
3439
3450
  let uploadSessionId = activeSessionId;
3451
+ let pendingFilesHandled = false;
3440
3452
  if (!uploadSessionId && ensureSession) {
3441
3453
  try {
3442
- uploadSessionId = await ensureSession(text || "\u65B0\u4F1A\u8BDD");
3454
+ const ensured = await ensureSession(text || "\u65B0\u4F1A\u8BDD", {
3455
+ model: selectedModel || null,
3456
+ thinkingOverride: thinkingAvailable && (sessionThinking != null || thinkingPreference != null) ? thinkingEnabled : void 0
3457
+ });
3458
+ uploadSessionId = typeof ensured === "string" ? ensured : ensured.sessionId;
3459
+ pendingFilesHandled = typeof ensured === "object" && ensured.pendingFilesHandled === true;
3443
3460
  } catch {
3444
3461
  setComposerAttachments(
3445
3462
  (prev) => prev.map(
@@ -3451,7 +3468,12 @@ function ChatInput({
3451
3468
  }
3452
3469
  if (uploadSessionId) {
3453
3470
  uploadTargetSessionId = uploadSessionId;
3454
- if (pendingFiles.length > 0) {
3471
+ if (pendingFilesHandled) {
3472
+ updatedAttachments = composerAttachments.map(
3473
+ (attachment) => pendingFiles.some((pending) => pending.id === attachment.id) ? { ...attachment, status: "uploaded", uploadedPath: attachment.name } : attachment
3474
+ );
3475
+ setComposerAttachments(updatedAttachments);
3476
+ } else if (pendingFiles.length > 0) {
3455
3477
  setComposerAttachments(
3456
3478
  (prev) => prev.map(
3457
3479
  (a) => a.kind === "file" && a.status === "pending" ? { ...a, status: "uploading", uploadProgress: 0 } : a
@@ -3525,17 +3547,23 @@ function ChatInput({
3525
3547
  if (activeSessionId && activePendingContexts.length > 0) {
3526
3548
  consumePendingContexts(activeSessionId);
3527
3549
  }
3528
- onSend(nextPayload, {
3529
- targetSessionId: uploadTargetSessionId,
3530
- model: selectedModel || null,
3531
- thinkingOverride: sessionThinking != null || thinkingPreference != null ? thinkingEnabled : void 0
3532
- });
3550
+ try {
3551
+ await onSend(nextPayload, {
3552
+ targetSessionId: uploadTargetSessionId,
3553
+ model: selectedModel || null,
3554
+ thinkingOverride: thinkingAvailable && (sessionThinking != null || thinkingPreference != null) ? thinkingEnabled : void 0
3555
+ });
3556
+ } catch (error) {
3557
+ toast.error(error instanceof Error ? error.message : "\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
3558
+ return;
3559
+ }
3533
3560
  if (text.trim()) {
3534
3561
  inputHistory.addEntry(text);
3535
3562
  }
3536
3563
  resetComposer();
3537
3564
  } finally {
3538
3565
  submittingRef.current = false;
3566
+ setIsSubmitting(false);
3539
3567
  }
3540
3568
  };
3541
3569
  const removeAttachment = (id) => {
@@ -3561,7 +3589,7 @@ function ChatInput({
3561
3589
  (attachment) => attachment.status === "uploading"
3562
3590
  );
3563
3591
  const hasValidAttachments = composerAttachments.some((attachment) => attachment.status !== "failed");
3564
- const isSendDisabled = connectionStatus !== "connected" || isModelLoading || !hasAvailableModel || hasUploadingFiles || !input.trim() && !hasValidAttachments && !hasRenderedAttachments && !hasPendingContexts;
3592
+ const isSendDisabled = connectionStatus !== "connected" || isModelLoading || !hasAvailableModel || isSubmitting || hasUploadingFiles || !input.trim() && !hasValidAttachments && !hasRenderedAttachments && !hasPendingContexts;
3565
3593
  return /* @__PURE__ */ jsxs9(Fragment2, { children: [
3566
3594
  /* @__PURE__ */ jsx10("div", { className: `bg-[hsl(var(--background))] px-5 py-3 ${className ?? ""}`, children: /* @__PURE__ */ jsxs9("div", { className: `mx-auto ${maxWidthClassName} ${innerClassName ?? ""}`, children: [
3567
3595
  activeSessionId ? /* @__PURE__ */ jsx10(BackgroundTasksPill, { sessionId: activeSessionId }) : null,
@@ -3859,7 +3887,7 @@ function ChatInput({
3859
3887
  }
3860
3888
  )
3861
3889
  ] }) : null,
3862
- /* @__PURE__ */ jsx10(
3890
+ thinkingAvailable ? /* @__PURE__ */ jsx10(
3863
3891
  ThinkingToggle,
3864
3892
  {
3865
3893
  enabled: thinkingEnabled,
@@ -3868,7 +3896,7 @@ function ChatInput({
3868
3896
  setPreferredThinkingEnabled(next);
3869
3897
  }
3870
3898
  }
3871
- ),
3899
+ ) : null,
3872
3900
  /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 px-3 py-2", children: [
3873
3901
  /* @__PURE__ */ jsx10(Sparkles2, { size: 13 }),
3874
3902
  /* @__PURE__ */ jsx10("span", { className: "shrink-0", children: "\u6A21\u578B" }),
@@ -3929,6 +3957,7 @@ function ChatInput({
3929
3957
  children: !asrEnabled ? /* @__PURE__ */ jsx10(MicOff, { size: 13 }) : isRecording ? /* @__PURE__ */ jsx10(VoiceWaveform, { level: voiceLevel, size: 18 }) : /* @__PURE__ */ jsx10(Mic, { size: 13 })
3930
3958
  }
3931
3959
  ),
3960
+ slotBeforeSend,
3932
3961
  isStreaming ? /* @__PURE__ */ jsx10(
3933
3962
  "button",
3934
3963
  {
@@ -3948,7 +3977,7 @@ function ChatInput({
3948
3977
  "aria-label": "\u53D1\u9001\u6D88\u606F",
3949
3978
  title: isModelLoading ? "\u6B63\u5728\u52A0\u8F7D\u6A21\u578B\u914D\u7F6E" : !hasAvailableModel ? "\u6CA1\u6709\u53EF\u7528\u6A21\u578B\uFF0C\u8BF7\u68C0\u67E5\u6A21\u578B\u670D\u52A1\u914D\u7F6E" : "\u53D1\u9001\u6D88\u606F",
3950
3979
  className: "flex h-7 w-7 items-center justify-center rounded-lg bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-80 disabled:opacity-25",
3951
- children: /* @__PURE__ */ jsx10(Send, { size: 13 })
3980
+ children: isSubmitting ? /* @__PURE__ */ jsx10(Loader22, { size: 13, className: "animate-spin" }) : /* @__PURE__ */ jsx10(Send, { size: 13 })
3952
3981
  }
3953
3982
  )
3954
3983
  ] })
@@ -5997,7 +6026,7 @@ function ToolCallBlock({
5997
6026
  }, [shouldAutoExpandWhileStreaming]);
5998
6027
  const resolvedSessionStatus = sessionStatus ?? sessions.find((session) => session.id === resolvedSessionId)?.status;
5999
6028
  const resolvedAnswerData = answerData ?? storedAnswerData;
6000
- const resolvedAnswered = answered ?? (!!resolvedAnswerData && resolvedSessionStatus !== "waiting_for_input");
6029
+ const resolvedAnswered = toolCall.status === "awaiting_answer" ? false : answered ?? (!!resolvedAnswerData && resolvedSessionStatus !== "waiting_for_input");
6001
6030
  const resolvedOnAnswer = onAnswer ?? answerCallback;
6002
6031
  const uiMeta = useMemo13(() => {
6003
6032
  const block = turnBlocks?.find(
@@ -6007,9 +6036,8 @@ function ToolCallBlock({
6007
6036
  }, [toolCall.id, turnBlocks]);
6008
6037
  if (normalizedName === "AskUserQuestion") {
6009
6038
  const askData = parseAskUserQuestion(typeof toolCall.result === "string" ? toolCall.result : null) ?? parseAskUserQuestion(buildAskUserPayload(toolCall.arguments));
6010
- const canAnswer = toolCall.status === "awaiting_answer" && Boolean(resolvedOnAnswer) && Boolean(resolvedSessionStatus);
6011
- const shouldRenderQuestion = Boolean(askData) && (canAnswer || resolvedAnswered || toolCall.status === "done");
6012
- if (askData && shouldRenderQuestion) {
6039
+ const canAnswer = toolCall.status === "awaiting_answer" && resolvedSessionStatus === "waiting_for_input" && Boolean(resolvedOnAnswer);
6040
+ if (askData) {
6013
6041
  const questionBlock = /* @__PURE__ */ jsx22(
6014
6042
  AskUserQuestionBlock,
6015
6043
  {
@@ -6029,6 +6057,14 @@ function ToolCallBlock({
6029
6057
  /* @__PURE__ */ jsx22("div", { className: "ml-4", children: /* @__PURE__ */ jsx22(ThinkingBadge, { reasoning, variant: "block" }) })
6030
6058
  ] });
6031
6059
  }
6060
+ return /* @__PURE__ */ jsxs18("div", { className: "ml-4 max-w-lg rounded-xl border border-amber-500/35 bg-amber-500/10 p-4 text-sm text-[hsl(var(--foreground))]", children: [
6061
+ /* @__PURE__ */ jsx22("div", { className: "font-semibold", children: "\u9009\u62E9\u9898\u5185\u5BB9\u6682\u65F6\u65E0\u6CD5\u663E\u793A" }),
6062
+ /* @__PURE__ */ jsxs18("div", { className: "mt-1 text-xs leading-5 text-[hsl(var(--muted-foreground))]", children: [
6063
+ "\u6536\u5230\u7684\u4EA4\u4E92\u6570\u636E\u4E0D\u5B8C\u6574\u6216\u7248\u672C\u4E0D\u517C\u5BB9\u3002\u8BF7\u91CD\u8BD5\uFF1B\u82E5\u95EE\u9898\u6301\u7EED\uFF0C\u8BF7\u63D0\u4F9B\u4EA4\u4E92\u7F16\u53F7",
6064
+ /* @__PURE__ */ jsx22("span", { className: "ml-1 font-mono", children: toolCall.id }),
6065
+ "\u3002"
6066
+ ] })
6067
+ ] });
6032
6068
  }
6033
6069
  const filePath = extractToolFilePath(toolCall);
6034
6070
  const tone = getToolTone(toolCall.status);
@@ -8218,6 +8254,21 @@ function TurnNavRail({
8218
8254
  );
8219
8255
  }
8220
8256
 
8257
+ // src/react/components/chat/scroll-anchor.ts
8258
+ var ENTRY_SCROLL_MARGIN_PX = 24;
8259
+ function normalizeScrollEntryKey(value) {
8260
+ const text = value?.trim();
8261
+ return text ? text : null;
8262
+ }
8263
+ function calculateAnchoredScrollTop({
8264
+ scrollTop,
8265
+ containerTop,
8266
+ targetTop,
8267
+ margin = ENTRY_SCROLL_MARGIN_PX
8268
+ }) {
8269
+ return Math.max(0, scrollTop + targetTop - containerTop - margin);
8270
+ }
8271
+
8221
8272
  // src/react/components/chat/MessageList.tsx
8222
8273
  import { jsx as jsx37, jsxs as jsxs32 } from "react/jsx-runtime";
8223
8274
  function parseModeChange(message) {
@@ -8296,13 +8347,20 @@ function findScrollContainer(start) {
8296
8347
  }
8297
8348
  return null;
8298
8349
  }
8350
+ function findEntryAnchor(root, entryId) {
8351
+ return Array.from(root.querySelectorAll("[data-entry-id]")).find(
8352
+ (el) => el.dataset.entryId === entryId
8353
+ ) ?? null;
8354
+ }
8299
8355
  function MessageList({
8300
8356
  sessionId,
8301
8357
  messages: rawMessages,
8302
8358
  onAnswer,
8303
8359
  sessionStatus,
8304
8360
  onConfirmPlan,
8305
- customization
8361
+ customization,
8362
+ scrollToStartKey,
8363
+ scrollToEntryKey
8306
8364
  }) {
8307
8365
  const messages = Array.isArray(rawMessages) ? rawMessages : [];
8308
8366
  const agentLoops = useChatStore((state) => state.agentLoops[sessionId]);
@@ -8381,7 +8439,8 @@ function MessageList({
8381
8439
  blocks.push({
8382
8440
  type: "message",
8383
8441
  message,
8384
- key: message.entry_id ?? `${message.role}-${blocks.length}`
8442
+ key: message.entry_id ?? `${message.role}-${blocks.length}`,
8443
+ anchorId: message.entry_id ? `chat-entry-${message.entry_id}` : null
8385
8444
  });
8386
8445
  }
8387
8446
  flushAssistant();
@@ -8429,9 +8488,18 @@ function MessageList({
8429
8488
  ) ?? null,
8430
8489
  [lastTurnId, renderBlocks]
8431
8490
  );
8491
+ const scrollEntryId = useMemo18(() => normalizeScrollEntryKey(scrollToEntryKey), [scrollToEntryKey]);
8492
+ const shouldAnchorEntry = Boolean(scrollEntryId);
8493
+ const hasMessages = messages.length > 0;
8432
8494
  const containerRef = useRef13(null);
8433
8495
  const scrollContainerRef = useRef13(null);
8434
8496
  const frameRef = useRef13(null);
8497
+ const startScrollFrameRef = useRef13(null);
8498
+ const startScrollTimeoutRef = useRef13(null);
8499
+ const entryScrollFrameRef = useRef13(null);
8500
+ const entryScrollTimeoutRefs = useRef13([]);
8501
+ const appliedStartScrollKeyRef = useRef13(null);
8502
+ const appliedEntryScrollKeyRef = useRef13(null);
8435
8503
  const [activeTurnId, setActiveTurnId] = useState21(lastTurnId);
8436
8504
  const layoutSignature = useMemo18(
8437
8505
  () => getMessagesMeasureSignature(messages),
@@ -8492,6 +8560,123 @@ function MessageList({
8492
8560
  useEffect18(() => {
8493
8561
  scheduleMeasure();
8494
8562
  }, [lastTurnId, layoutSignature, scheduleMeasure]);
8563
+ const scrollToSessionStart = useEffectEvent4(() => {
8564
+ const container = scrollContainerRef.current ?? findScrollContainer(containerRef.current);
8565
+ if (!container) return;
8566
+ container.scrollTo({ top: 0, behavior: "auto" });
8567
+ setActiveTurnId(null);
8568
+ });
8569
+ useEffect18(() => {
8570
+ if (!scrollToStartKey || !hasMessages) return;
8571
+ const applyKey = scrollToStartKey;
8572
+ if (appliedStartScrollKeyRef.current === applyKey) return;
8573
+ appliedStartScrollKeyRef.current = applyKey;
8574
+ if (startScrollFrameRef.current != null) {
8575
+ window.cancelAnimationFrame(startScrollFrameRef.current);
8576
+ }
8577
+ if (startScrollTimeoutRef.current != null) {
8578
+ window.clearTimeout(startScrollTimeoutRef.current);
8579
+ }
8580
+ startScrollFrameRef.current = window.requestAnimationFrame(() => {
8581
+ startScrollFrameRef.current = null;
8582
+ scrollToSessionStart();
8583
+ startScrollTimeoutRef.current = window.setTimeout(() => {
8584
+ startScrollTimeoutRef.current = null;
8585
+ scrollToSessionStart();
8586
+ }, 80);
8587
+ });
8588
+ return () => {
8589
+ if (startScrollFrameRef.current != null) {
8590
+ window.cancelAnimationFrame(startScrollFrameRef.current);
8591
+ startScrollFrameRef.current = null;
8592
+ }
8593
+ if (startScrollTimeoutRef.current != null) {
8594
+ window.clearTimeout(startScrollTimeoutRef.current);
8595
+ startScrollTimeoutRef.current = null;
8596
+ }
8597
+ };
8598
+ }, [hasMessages, scrollToSessionStart, scrollToStartKey]);
8599
+ const scrollToEntry = useEffectEvent4((entryId) => {
8600
+ const root = containerRef.current;
8601
+ if (!root) return false;
8602
+ const target = findEntryAnchor(root, entryId);
8603
+ if (!target) return false;
8604
+ stickContextRef.current?.stopScroll();
8605
+ const container = scrollContainerRef.current ?? findScrollContainer(root);
8606
+ if (container) {
8607
+ const containerRect = container.getBoundingClientRect();
8608
+ const targetRect = target.getBoundingClientRect();
8609
+ container.scrollTo({
8610
+ top: calculateAnchoredScrollTop({
8611
+ scrollTop: container.scrollTop,
8612
+ containerTop: containerRect.top,
8613
+ targetTop: targetRect.top
8614
+ }),
8615
+ behavior: "auto"
8616
+ });
8617
+ } else {
8618
+ target.scrollIntoView({ behavior: "auto", block: "start" });
8619
+ }
8620
+ setActiveTurnId(target.dataset.turnId ?? null);
8621
+ return true;
8622
+ });
8623
+ const getAnchorTargetScrollTop = useCallback12(
8624
+ (defaultTargetScrollTop, { scrollElement, contentElement }) => {
8625
+ if (!scrollEntryId) return defaultTargetScrollTop;
8626
+ const target = findEntryAnchor(contentElement, scrollEntryId);
8627
+ if (!target) return defaultTargetScrollTop;
8628
+ const containerRect = scrollElement.getBoundingClientRect();
8629
+ const targetRect = target.getBoundingClientRect();
8630
+ return calculateAnchoredScrollTop({
8631
+ scrollTop: scrollElement.scrollTop,
8632
+ containerTop: containerRect.top,
8633
+ targetTop: targetRect.top
8634
+ });
8635
+ },
8636
+ [scrollEntryId]
8637
+ );
8638
+ useEffect18(() => {
8639
+ if (!scrollEntryId || !hasMessages) return;
8640
+ const entryId = scrollEntryId;
8641
+ const applyKey = scrollEntryId;
8642
+ if (appliedEntryScrollKeyRef.current === applyKey) return;
8643
+ appliedEntryScrollKeyRef.current = applyKey;
8644
+ if (entryScrollFrameRef.current != null) {
8645
+ window.cancelAnimationFrame(entryScrollFrameRef.current);
8646
+ }
8647
+ for (const timeoutId of entryScrollTimeoutRefs.current) {
8648
+ window.clearTimeout(timeoutId);
8649
+ }
8650
+ entryScrollTimeoutRefs.current = [];
8651
+ entryScrollFrameRef.current = window.requestAnimationFrame(() => {
8652
+ entryScrollFrameRef.current = null;
8653
+ const attempts = [0, 60, 160, 360, 700, 1200];
8654
+ let matched = false;
8655
+ for (const delay of attempts) {
8656
+ const timeoutId = window.setTimeout(() => {
8657
+ matched = scrollToEntry(entryId) || matched;
8658
+ }, delay);
8659
+ entryScrollTimeoutRefs.current.push(timeoutId);
8660
+ }
8661
+ const fallbackId = window.setTimeout(() => {
8662
+ entryScrollTimeoutRefs.current = [];
8663
+ if (!matched && !scrollToEntry(entryId)) {
8664
+ scrollToSessionStart();
8665
+ }
8666
+ }, 1400);
8667
+ entryScrollTimeoutRefs.current.push(fallbackId);
8668
+ });
8669
+ return () => {
8670
+ if (entryScrollFrameRef.current != null) {
8671
+ window.cancelAnimationFrame(entryScrollFrameRef.current);
8672
+ entryScrollFrameRef.current = null;
8673
+ }
8674
+ for (const timeoutId of entryScrollTimeoutRefs.current) {
8675
+ window.clearTimeout(timeoutId);
8676
+ }
8677
+ entryScrollTimeoutRefs.current = [];
8678
+ };
8679
+ }, [hasMessages, scrollEntryId, scrollToEntry, scrollToSessionStart]);
8495
8680
  const handleSelectTurn = useCallback12((turnId) => {
8496
8681
  document.getElementById(turnId)?.scrollIntoView({ behavior: "smooth", block: "start" });
8497
8682
  }, []);
@@ -8514,8 +8699,9 @@ function MessageList({
8514
8699
  {
8515
8700
  contextRef: stickContextRef,
8516
8701
  className: "h-full overflow-y-hidden",
8517
- initial: "instant",
8702
+ initial: shouldAnchorEntry ? "instant" : "instant",
8518
8703
  resize: stickyTurn ? { damping: 0.8, stiffness: 0.08, mass: 1 } : "instant",
8704
+ targetScrollTop: shouldAnchorEntry ? getAnchorTargetScrollTop : void 0,
8519
8705
  children: [
8520
8706
  /* @__PURE__ */ jsx37(StickToBottom.Content, { className: "px-5 py-6", children: /* @__PURE__ */ jsx37(
8521
8707
  MessageListContent,
@@ -8536,7 +8722,8 @@ function MessageList({
8536
8722
  ) }),
8537
8723
  /* @__PURE__ */ jsx37(ScrollToBottomButton, {})
8538
8724
  ]
8539
- }
8725
+ },
8726
+ scrollEntryId ?? "latest"
8540
8727
  )
8541
8728
  ]
8542
8729
  }
@@ -8581,31 +8768,40 @@ function MessageListContent({
8581
8768
  /* @__PURE__ */ jsx37("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
8582
8769
  ] }) : renderBlocks.map((block, blockIndex) => {
8583
8770
  if (block.type === "message") {
8584
- return /* @__PURE__ */ jsx37("div", { className: "msg-animate", children: isUserMessage(block.message) ? customization?.components?.UserMessage ? /* @__PURE__ */ jsx37(
8585
- customization.components.UserMessage,
8586
- {
8587
- message: block.message,
8588
- className: customization.classNames?.userMessage
8589
- }
8590
- ) : /* @__PURE__ */ jsx37(
8591
- UserMessageBubble,
8592
- {
8593
- message: block.message,
8594
- className: customization?.classNames?.userMessage
8595
- }
8596
- ) : isErrorMessage(block.message) ? customization?.components?.ErrorMessage ? /* @__PURE__ */ jsx37(
8597
- customization.components.ErrorMessage,
8598
- {
8599
- message: block.message,
8600
- className: customization.classNames?.errorMessage
8601
- }
8602
- ) : /* @__PURE__ */ jsx37(
8603
- ErrorMessageBlock,
8771
+ return /* @__PURE__ */ jsx37(
8772
+ "div",
8604
8773
  {
8605
- message: block.message,
8606
- className: customization?.classNames?.errorMessage
8607
- }
8608
- ) : null }, block.key);
8774
+ id: block.anchorId ?? void 0,
8775
+ "data-entry-id": block.message.entry_id,
8776
+ className: "msg-animate scroll-mt-6",
8777
+ children: isUserMessage(block.message) ? customization?.components?.UserMessage ? /* @__PURE__ */ jsx37(
8778
+ customization.components.UserMessage,
8779
+ {
8780
+ message: block.message,
8781
+ className: customization.classNames?.userMessage
8782
+ }
8783
+ ) : /* @__PURE__ */ jsx37(
8784
+ UserMessageBubble,
8785
+ {
8786
+ message: block.message,
8787
+ className: customization?.classNames?.userMessage
8788
+ }
8789
+ ) : isErrorMessage(block.message) ? customization?.components?.ErrorMessage ? /* @__PURE__ */ jsx37(
8790
+ customization.components.ErrorMessage,
8791
+ {
8792
+ message: block.message,
8793
+ className: customization.classNames?.errorMessage
8794
+ }
8795
+ ) : /* @__PURE__ */ jsx37(
8796
+ ErrorMessageBlock,
8797
+ {
8798
+ message: block.message,
8799
+ className: customization?.classNames?.errorMessage
8800
+ }
8801
+ ) : null
8802
+ },
8803
+ block.key
8804
+ );
8609
8805
  }
8610
8806
  if (block.type === "assistant_turn") {
8611
8807
  const nextBlock = renderBlocks[blockIndex + 1];
@@ -8621,6 +8817,7 @@ function MessageListContent({
8621
8817
  {
8622
8818
  id: block.anchorId,
8623
8819
  "data-turn-id": block.anchorId,
8820
+ "data-entry-id": block.messages[0]?.entry_id,
8624
8821
  className: "msg-animate flex scroll-mt-6 flex-col gap-4",
8625
8822
  children: [
8626
8823
  /* @__PURE__ */ jsx37(
@@ -8735,6 +8932,8 @@ function ChatView({
8735
8932
  inputLabel,
8736
8933
  onDoubleEscape,
8737
8934
  composerHint,
8935
+ scrollToStartKey,
8936
+ scrollToEntryKey,
8738
8937
  classNames,
8739
8938
  components,
8740
8939
  renderers
@@ -8789,7 +8988,9 @@ function ChatView({
8789
8988
  onAnswer: isViewer ? void 0 : (msg, toolCallId, answerData) => send(msg, mode, { tool_call_id: toolCallId, ...answerData }),
8790
8989
  sessionStatus,
8791
8990
  onConfirmPlan: isViewer ? void 0 : onConfirmPlan,
8792
- customization
8991
+ customization,
8992
+ scrollToStartKey,
8993
+ scrollToEntryKey
8793
8994
  },
8794
8995
  sessionId
8795
8996
  ),
@@ -8878,4 +9079,4 @@ use-stick-to-bottom/dist/StickToBottom.js:
8878
9079
  * Licensed under the MIT License. See License.txt in the project root for license information.
8879
9080
  *--------------------------------------------------------------------------------------------*)
8880
9081
  */
8881
- //# sourceMappingURL=chunk-X2NJKKXR.js.map
9082
+ //# sourceMappingURL=chunk-N2OE5MVV.js.map