@agents24/chat-react 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +92 -18
  2. package/compatibility.json +10 -10
  3. package/dist/chunk-SSUO4EPD.js +189 -0
  4. package/dist/chunk-SSUO4EPD.js.map +1 -0
  5. package/dist/chunk-ZCNHLFRB.js +108 -0
  6. package/dist/chunk-ZCNHLFRB.js.map +1 -0
  7. package/dist/index.cjs +112 -95
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +0 -1
  10. package/dist/index.js +362 -68
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp-oauth.d.ts +13 -0
  13. package/dist/runtime/index.cjs +594 -0
  14. package/dist/runtime/index.cjs.map +1 -0
  15. package/dist/runtime/index.d.ts +2 -0
  16. package/dist/runtime/index.js +458 -0
  17. package/dist/runtime/index.js.map +1 -0
  18. package/dist/runtime/input-contract.d.ts +20 -0
  19. package/dist/runtime/use-agent-chat-runtime.d.ts +83 -0
  20. package/dist/scaffold/app.d.ts +30 -0
  21. package/dist/scaffold/components/chat/chat-composer.d.ts +21 -0
  22. package/dist/scaffold/components/chat/chat-message.d.ts +21 -0
  23. package/dist/scaffold/components/chat/chat-shell.d.ts +8 -0
  24. package/dist/scaffold/components/chat/collapsible-user-message.d.ts +3 -0
  25. package/dist/scaffold/components/chat/external-link-dialog.d.ts +2 -0
  26. package/dist/scaffold/components/chat/identity-control.d.ts +4 -0
  27. package/dist/scaffold/components/chat/message-parts.d.ts +12 -0
  28. package/dist/scaffold/components/chat/theme-menu.d.ts +1 -0
  29. package/dist/scaffold/components/chat/thread-sidebar.d.ts +16 -0
  30. package/dist/scaffold/components/chat/timeline-loading.d.ts +7 -0
  31. package/dist/scaffold/components/chat/turn-outline.d.ts +4 -0
  32. package/dist/scaffold/components/theme-provider.d.ts +20 -0
  33. package/dist/scaffold/components/ui/attachment.d.ts +24 -0
  34. package/dist/scaffold/components/ui/bubble.d.ts +17 -0
  35. package/dist/scaffold/components/ui/button-group.d.ts +11 -0
  36. package/dist/scaffold/components/ui/button.d.ts +10 -0
  37. package/dist/scaffold/components/ui/dialog.d.ts +17 -0
  38. package/dist/scaffold/components/ui/dropdown-menu.d.ts +29 -0
  39. package/dist/scaffold/components/ui/input.d.ts +3 -0
  40. package/dist/scaffold/components/ui/marker.d.ts +11 -0
  41. package/dist/scaffold/components/ui/message-scroller.d.ts +10 -0
  42. package/dist/scaffold/components/ui/message.d.ts +10 -0
  43. package/dist/scaffold/components/ui/popover.d.ts +6 -0
  44. package/dist/scaffold/components/ui/select.d.ts +8 -0
  45. package/dist/scaffold/components/ui/separator.d.ts +4 -0
  46. package/dist/scaffold/components/ui/sheet.d.ts +14 -0
  47. package/dist/scaffold/components/ui/sidebar.d.ts +69 -0
  48. package/dist/scaffold/components/ui/skeleton.d.ts +2 -0
  49. package/dist/scaffold/components/ui/textarea.d.ts +3 -0
  50. package/dist/scaffold/components/ui/tooltip.d.ts +15 -0
  51. package/dist/scaffold/hooks/use-mobile.d.ts +1 -0
  52. package/dist/scaffold/identity.d.ts +10 -0
  53. package/dist/scaffold/index.cjs +2270 -0
  54. package/dist/scaffold/index.cjs.map +1 -0
  55. package/dist/scaffold/index.d.ts +14 -0
  56. package/dist/scaffold/index.js +2244 -0
  57. package/dist/scaffold/index.js.map +1 -0
  58. package/dist/scaffold/lib/utils.d.ts +2 -0
  59. package/dist/styles.css +143 -298
  60. package/dist/types.d.ts +3 -13
  61. package/dist/ui/adapters.d.ts +1 -1
  62. package/dist/ui/agent-chat-actions.d.ts +12 -5
  63. package/dist/ui/agent-chat-composer.d.ts +2 -1
  64. package/dist/ui/agent-chat-context-compression-row.d.ts +5 -0
  65. package/dist/ui/agent-chat-context-status.d.ts +13 -0
  66. package/dist/ui/agent-chat-message.d.ts +7 -5
  67. package/dist/ui/agent-response-timeline.d.ts +29 -7
  68. package/dist/ui/ask-user-interaction-state.d.ts +20 -0
  69. package/dist/ui/ask-user-interaction.d.ts +8 -0
  70. package/dist/ui/index.cjs +1392 -272
  71. package/dist/ui/index.cjs.map +1 -1
  72. package/dist/ui/index.d.ts +5 -0
  73. package/dist/ui/index.js +2380 -65
  74. package/dist/ui/index.js.map +1 -1
  75. package/dist/ui/tool-sessions.d.ts +70 -0
  76. package/dist/ui/use-audio-recorder.d.ts +8 -0
  77. package/package.json +27 -12
  78. package/scaffold/components.json +25 -0
  79. package/scaffold/manifest.json +28 -0
  80. package/scaffold/src/app.tsx +71 -0
  81. package/scaffold/src/chat.css +54 -0
  82. package/scaffold/src/components/chat/chat-composer.tsx +237 -0
  83. package/scaffold/src/components/chat/chat-message.tsx +76 -0
  84. package/scaffold/src/components/chat/chat-shell.tsx +257 -0
  85. package/scaffold/src/components/chat/collapsible-user-message.tsx +37 -0
  86. package/scaffold/src/components/chat/external-link-dialog.tsx +48 -0
  87. package/scaffold/src/components/chat/identity-control.tsx +69 -0
  88. package/scaffold/src/components/chat/message-parts.tsx +83 -0
  89. package/scaffold/src/components/chat/theme-menu.tsx +79 -0
  90. package/scaffold/src/components/chat/thread-sidebar.tsx +114 -0
  91. package/scaffold/src/components/chat/timeline-loading.ts +17 -0
  92. package/scaffold/src/components/chat/turn-outline.tsx +73 -0
  93. package/scaffold/src/components/theme-provider.tsx +91 -0
  94. package/scaffold/src/components/ui/attachment.tsx +204 -0
  95. package/scaffold/src/components/ui/bubble.tsx +125 -0
  96. package/scaffold/src/components/ui/button-group.tsx +83 -0
  97. package/scaffold/src/components/ui/button.tsx +65 -0
  98. package/scaffold/src/components/ui/dialog.tsx +171 -0
  99. package/scaffold/src/components/ui/dropdown-menu.tsx +271 -0
  100. package/scaffold/src/components/ui/input.tsx +19 -0
  101. package/scaffold/src/components/ui/marker.tsx +69 -0
  102. package/scaffold/src/components/ui/message-scroller.tsx +129 -0
  103. package/scaffold/src/components/ui/message.tsx +92 -0
  104. package/scaffold/src/components/ui/popover.tsx +40 -0
  105. package/scaffold/src/components/ui/select.tsx +92 -0
  106. package/scaffold/src/components/ui/separator.tsx +28 -0
  107. package/scaffold/src/components/ui/sheet.tsx +150 -0
  108. package/scaffold/src/components/ui/sidebar.tsx +677 -0
  109. package/scaffold/src/components/ui/skeleton.tsx +13 -0
  110. package/scaffold/src/components/ui/textarea.tsx +18 -0
  111. package/scaffold/src/components/ui/tooltip.tsx +56 -0
  112. package/scaffold/src/hooks/use-mobile.ts +24 -0
  113. package/scaffold/src/identity.ts +17 -0
  114. package/scaffold/src/index.css +222 -0
  115. package/scaffold/src/index.tsx +39 -0
  116. package/scaffold/src/lib/utils.ts +6 -0
  117. package/scaffold/styles.css +3 -0
  118. package/dist/chunk-CHAU7FNW.js +0 -140
  119. package/dist/chunk-CHAU7FNW.js.map +0 -1
  120. package/dist/chunk-LI67M74F.js +0 -445
  121. package/dist/chunk-LI67M74F.js.map +0 -1
  122. package/dist/chunk-NLEOWTZG.js +0 -1327
  123. package/dist/chunk-NLEOWTZG.js.map +0 -1
  124. package/dist/renderers.d.ts +0 -10
  125. package/dist/templates/agent-chat-app.d.ts +0 -18
  126. package/dist/templates/agent-chat-layout.d.ts +0 -69
  127. package/dist/templates/agent-chat-shell.d.ts +0 -37
  128. package/dist/templates/index.cjs +0 -2476
  129. package/dist/templates/index.cjs.map +0 -1
  130. package/dist/templates/index.d.ts +0 -3
  131. package/dist/templates/index.js +0 -723
  132. package/dist/templates/index.js.map +0 -1
package/dist/ui/index.cjs CHANGED
@@ -30,12 +30,23 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/ui/index.ts
31
31
  var ui_exports = {};
32
32
  __export(ui_exports, {
33
+ AgentChatActivityRow: () => AgentChatActivityRow,
33
34
  AgentChatComposer: () => AgentChatComposer,
35
+ AgentChatContextCompressionRow: () => AgentChatContextCompressionRow,
36
+ AgentChatContextStatus: () => AgentChatContextStatus,
34
37
  AgentChatDefaultActions: () => AgentChatDefaultActions,
35
38
  AgentChatMessage: () => AgentChatMessage,
36
- AgentChatPartRow: () => DefaultPartRow,
39
+ AgentChatModelTurnRow: () => AgentChatModelTurnRow,
40
+ AgentChatNodeActivityRow: () => AgentChatNodeActivityRow,
41
+ AgentChatPart: () => AgentChatPart,
42
+ AgentChatSubagentGroup: () => AgentChatSubagentGroup,
43
+ AgentChatToolGroup: () => AgentChatToolGroup,
44
+ AgentChatToolPart: () => AgentChatToolPart,
45
+ AgentChatToolRow: () => AgentChatToolRow,
46
+ AgentChatToolSession: () => AgentChatToolSession,
37
47
  AgentChatUserMessageContent: () => AgentChatUserMessageContent,
38
48
  AgentResponseTimeline: () => AgentResponseTimeline,
49
+ AskUserInteraction: () => AskUserInteraction,
39
50
  Attachment: () => Attachment,
40
51
  AttachmentAction: () => AttachmentAction,
41
52
  AttachmentActions: () => AttachmentActions,
@@ -72,15 +83,22 @@ __export(ui_exports, {
72
83
  MessageResponse: () => MessageResponse,
73
84
  ShadcnMessage: () => Message,
74
85
  ShadcnMessageContent: () => MessageContent,
86
+ agentChatToolLabel: () => agentChatToolLabel,
75
87
  attachmentMediaVariants: () => attachmentMediaVariants,
76
88
  attachmentVariants: () => attachmentVariants,
77
89
  bubbleVariants: () => bubbleVariants,
78
90
  cn: () => cn,
91
+ contextStatusMetrics: () => contextStatusMetrics,
79
92
  hitlActionLabel: () => hitlActionLabel,
80
93
  hitlTitle: () => hitlTitle,
94
+ isAgentChatSubagent: () => isAgentChatSubagent,
95
+ isAgentChatToolRunning: () => isAgentChatToolRunning,
96
+ latestRunningAgentChatToolId: () => latestRunningAgentChatToolId,
81
97
  markerVariants: () => markerVariants,
98
+ partitionAgentChatToolTimeline: () => partitionAgentChatToolTimeline,
82
99
  resolvedHitlLabel: () => resolvedHitlLabel,
83
- shouldCollapseUserMessage: () => shouldCollapseUserMessage
100
+ shouldCollapseUserMessage: () => shouldCollapseUserMessage,
101
+ useAudioRecorder: () => useAudioRecorder
84
102
  });
85
103
  module.exports = __toCommonJS(ui_exports);
86
104
 
@@ -120,20 +138,21 @@ function formatFileCount(files) {
120
138
  if (files.length === 1) return files[0].filename;
121
139
  return `${files.length} files`;
122
140
  }
123
- var pillButtonClass = "inline-flex size-8 shrink-0 items-center justify-center gap-2 rounded-full border-0 bg-neutral-100 text-sm text-neutral-600 shadow-none transition-colors hover:bg-neutral-200 hover:text-neutral-800 focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700";
124
- var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-full border-0 bg-neutral-900 text-sm text-white shadow-none transition-[background-color,color,opacity,transform] duration-200 hover:bg-neutral-800 hover:text-white focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-white";
141
+ var pillButtonClass = "inline-flex size-8 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-muted text-sm text-muted-foreground shadow-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
142
+ var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-primary text-sm text-primary-foreground shadow-none transition-[background-color,color,opacity,transform] duration-200 hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
125
143
  function AgentChatComposer({
126
144
  accept,
127
145
  allowAttachments = true,
128
146
  attachmentLayout = "flow",
129
147
  className,
130
148
  disabled = false,
149
+ forceExpanded = false,
131
150
  inputToolbarContent,
132
151
  isRunning = false,
133
152
  onAttachmentCountChange,
134
153
  onStop,
135
154
  onSubmit,
136
- placeholder = "Send follow-up\u2026",
155
+ placeholder = "Message the agent",
137
156
  textareaRef
138
157
  }) {
139
158
  const fileInputRef = React.useRef(null);
@@ -142,7 +161,7 @@ function AgentChatComposer({
142
161
  const [text, setText] = React.useState("");
143
162
  const [files, setFiles] = React.useState([]);
144
163
  const [isSubmitting, setIsSubmitting] = React.useState(false);
145
- const isExpanded = text.includes("\n") || text.length > 62;
164
+ const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
146
165
  const setTextareaRef = React.useCallback(
147
166
  (node) => {
148
167
  localTextareaRef.current = node;
@@ -197,6 +216,7 @@ function AgentChatComposer({
197
216
  setText("");
198
217
  setFiles([]);
199
218
  revokeComposerFiles(submittedFiles);
219
+ } catch {
200
220
  } finally {
201
221
  setIsSubmitting(false);
202
222
  }
@@ -207,7 +227,7 @@ function AgentChatComposer({
207
227
  void submit();
208
228
  }
209
229
  };
210
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), children: [
230
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), "data-agents24-chat-composer": "", children: [
211
231
  files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
212
232
  "div",
213
233
  {
@@ -220,7 +240,7 @@ function AgentChatComposer({
220
240
  children: files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
221
241
  "div",
222
242
  {
223
- className: "a24-composer__attachment group relative flex h-8 max-w-[min(20rem,calc(100vw-2rem))] cursor-default select-none items-center gap-1.5 rounded-md border border-neutral-200 bg-white px-1.5 text-sm font-medium text-foreground transition-colors hover:bg-neutral-50",
243
+ className: "a24-composer__attachment group relative flex h-8 max-w-[min(20rem,calc(100vw-2rem))] cursor-default select-none items-center gap-1.5 rounded-md border border-border bg-card px-1.5 text-sm font-medium text-card-foreground transition-colors hover:bg-accent",
224
244
  children: [
225
245
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex size-5 shrink-0 items-center justify-center rounded bg-background text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Paperclip, { className: "size-3" }) }),
226
246
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "min-w-0 flex-1 truncate", children: file.filename }),
@@ -245,8 +265,8 @@ function AgentChatComposer({
245
265
  {
246
266
  "data-expanded": isExpanded ? "" : void 0,
247
267
  className: cn(
248
- "a24-composer__surface relative w-full border border-neutral-200 bg-white shadow-sm transition-colors duration-200 focus-within:border-neutral-300 dark:border-neutral-700 dark:bg-card dark:focus-within:border-neutral-600",
249
- isExpanded ? "rounded-[18px] p-2 pb-1.5" : "rounded-full px-2 py-[3px]"
268
+ "a24-composer__surface relative w-full border border-input bg-background shadow-sm",
269
+ isExpanded ? "rounded-xl p-2 pb-1.5" : "rounded-xl px-2 py-[3px]"
250
270
  ),
251
271
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
252
272
  "div",
@@ -294,7 +314,7 @@ function AgentChatComposer({
294
314
  "aria-label": "Message",
295
315
  autoComplete: "off",
296
316
  className: cn(
297
- "a24-composer__textarea w-full resize-none border-0 bg-transparent px-1 text-[15px] leading-relaxed text-foreground shadow-none outline-none placeholder:text-neutral-400 focus-visible:ring-0 scrollbar-thin",
317
+ "a24-composer__textarea w-full resize-none border-0 bg-transparent px-1 text-[15px] leading-relaxed text-foreground shadow-none outline-none placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0",
298
318
  isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
299
319
  ),
300
320
  disabled: disabled || isSubmitting || isRunning,
@@ -342,7 +362,9 @@ function AgentChatComposer({
342
362
  }
343
363
 
344
364
  // src/ui/agent-chat-actions.tsx
365
+ var React3 = __toESM(require("react"), 1);
345
366
  var import_lucide_react2 = require("lucide-react");
367
+ var import_radix_ui = require("radix-ui");
346
368
 
347
369
  // src/ui/adapters.ts
348
370
  var React2 = __toESM(require("react"), 1);
@@ -613,6 +635,7 @@ function MessageFooter({ className, ...props }) {
613
635
  }
614
636
 
615
637
  // src/ui/adapters.ts
638
+ var ChatMessageRoleContext = React2.createContext("assistant");
616
639
  function ChatMessage(input) {
617
640
  const { className, from, align, ...props } = input;
618
641
  const isUser = from === "user";
@@ -621,22 +644,27 @@ function ChatMessage(input) {
621
644
  const messageProps = Object.assign({}, props);
622
645
  messageProps.align = resolvedAlign;
623
646
  messageProps.className = cn(
624
- "group flex w-full flex-col gap-2",
625
- isUser ? cn("is-user max-w-[80%] justify-end", isRtl ? "mr-auto" : "ml-auto") : "is-assistant max-w-full",
647
+ "a24-chat-message group flex w-full flex-col gap-2",
648
+ isUser ? cn("a24-chat-message--user is-user max-w-[80%] justify-end", isRtl ? "mr-auto" : "ml-auto") : "a24-chat-message--assistant is-assistant max-w-full",
626
649
  className
627
650
  );
628
- return React2.createElement(Message, messageProps);
651
+ return React2.createElement(
652
+ ChatMessageRoleContext.Provider,
653
+ { value: from },
654
+ React2.createElement(Message, messageProps)
655
+ );
629
656
  }
630
657
  function ChatMessageContent({
631
658
  className,
659
+ from,
632
660
  ...props
633
661
  }) {
662
+ const inheritedRole = React2.useContext(ChatMessageRoleContext);
663
+ const isUser = (from ?? inheritedRole) === "user";
634
664
  return React2.createElement("div", {
635
665
  className: cn(
636
- "is-user:dark flex w-full max-w-full min-w-0 flex-col gap-2 overflow-hidden text-sm group-[.is-user]:w-fit group-[.is-user]:overflow-visible",
637
- "group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
638
- "group-[.is-assistant]:w-full group-[.is-assistant]:max-w-full group-[.is-assistant]:text-foreground",
639
- props.dir === "rtl" ? "group-[.is-user]:mr-auto" : "group-[.is-user]:ml-auto",
666
+ "a24-chat-message-content flex max-w-full min-w-0 flex-col gap-2 text-sm",
667
+ isUser ? "a24-chat-message-content--user is-user:dark w-fit overflow-visible rounded-lg bg-secondary px-4 py-3 text-foreground" : "a24-chat-message-content--assistant w-full overflow-visible text-foreground",
640
668
  className
641
669
  ),
642
670
  "data-slot": "bubble-content",
@@ -753,11 +781,30 @@ function ChatAttachments({
753
781
 
754
782
  // src/ui/agent-chat-actions.tsx
755
783
  var import_jsx_runtime4 = require("react/jsx-runtime");
784
+ var FEEDBACK_REASONS = [
785
+ ["incorrect", "Incorrect"],
786
+ ["irrelevant", "Irrelevant"],
787
+ ["incomplete", "Incomplete"],
788
+ ["unsafe", "Unsafe"],
789
+ ["citation_issue", "Citation issue"],
790
+ ["other", "Other"]
791
+ ];
756
792
  function AgentChatDefaultActions({
793
+ additionalActions,
794
+ dir,
757
795
  handlers,
758
796
  message
759
797
  }) {
798
+ const [feedbackOpen, setFeedbackOpen] = React3.useState(false);
799
+ const [feedbackTrigger, setFeedbackTrigger] = React3.useState(null);
800
+ const [reason, setReason] = React3.useState(handlers?.feedback?.reason ?? null);
801
+ const [comment, setComment] = React3.useState(handlers?.feedback?.comment ?? "");
802
+ React3.useEffect(() => {
803
+ setReason(handlers?.feedback?.reason ?? null);
804
+ setComment(handlers?.feedback?.comment ?? "");
805
+ }, [handlers?.feedback?.comment, handlers?.feedback?.reason]);
760
806
  if (!handlers || message.role !== "assistant" || message.isFinal === false) return null;
807
+ const canWriteFeedback = Boolean(message.runId && handlers.onSetFeedback);
761
808
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(ChatMessageActions, { children: [
762
809
  handlers.onRetry ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
763
810
  ChatMessageAction,
@@ -768,24 +815,101 @@ function AgentChatDefaultActions({
768
815
  children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.RefreshCcw, { className: "size-4" })
769
816
  }
770
817
  ) : null,
771
- handlers.onLike ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
818
+ canWriteFeedback ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
772
819
  ChatMessageAction,
773
820
  {
774
821
  label: "Like",
775
- onClick: () => void handlers.onLike?.(message),
822
+ onClick: () => void handlers.onSetFeedback?.(message, {
823
+ rating: handlers.feedback?.rating === "like" ? null : "like"
824
+ }),
776
825
  tooltip: "Like this response",
777
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.ThumbsUp, { className: "size-4", fill: handlers.liked ? "currentColor" : "none" })
778
- }
779
- ) : null,
780
- handlers.onDislike ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
781
- ChatMessageAction,
782
- {
783
- label: "Dislike",
784
- onClick: () => void handlers.onDislike?.(message),
785
- tooltip: "Dislike this response",
786
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.ThumbsDown, { className: "size-4", fill: handlers.disliked ? "currentColor" : "none" })
826
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.ThumbsUp, { className: "size-4", fill: handlers.feedback?.rating === "like" ? "currentColor" : "none" })
787
827
  }
788
828
  ) : null,
829
+ canWriteFeedback ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_radix_ui.Popover.Root, { onOpenChange: setFeedbackOpen, open: feedbackOpen, children: [
830
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui.Popover.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
831
+ ChatMessageAction,
832
+ {
833
+ label: "Dislike",
834
+ onClick: () => {
835
+ if (handlers.feedback?.rating !== "dislike") {
836
+ void handlers.onSetFeedback?.(message, { rating: "dislike" });
837
+ }
838
+ setFeedbackOpen(true);
839
+ },
840
+ ref: setFeedbackTrigger,
841
+ tooltip: "Dislike this response",
842
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.ThumbsDown, { className: "size-4", fill: handlers.feedback?.rating === "dislike" ? "currentColor" : "none" })
843
+ }
844
+ ) }),
845
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
846
+ import_radix_ui.Popover.Portal,
847
+ {
848
+ container: feedbackTrigger?.closest("[data-agents24-theme-root]") ?? void 0,
849
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
850
+ import_radix_ui.Popover.Content,
851
+ {
852
+ align: "end",
853
+ "aria-label": "Dislike details",
854
+ className: "z-50 w-80 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none",
855
+ dir,
856
+ side: "top",
857
+ sideOffset: 8,
858
+ children: [
859
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm font-medium", children: "What could be better?" }),
860
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-3 flex flex-wrap gap-1.5", children: FEEDBACK_REASONS.map(([value, label]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
861
+ "button",
862
+ {
863
+ "aria-pressed": reason === value,
864
+ className: "rounded-md border border-border px-2.5 py-1.5 text-xs font-medium transition-colors hover:bg-muted aria-pressed:border-transparent aria-pressed:bg-secondary",
865
+ onClick: () => setReason(reason === value ? null : value),
866
+ type: "button",
867
+ children: label
868
+ },
869
+ value
870
+ )) }),
871
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
872
+ "textarea",
873
+ {
874
+ "aria-label": "Optional feedback comment",
875
+ className: "mt-3 min-h-20 w-full resize-none rounded-md border border-input bg-background px-3 py-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring",
876
+ maxLength: 1e3,
877
+ onChange: (event) => setComment(event.target.value),
878
+ placeholder: "Add a comment (optional)",
879
+ value: comment
880
+ }
881
+ ),
882
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mt-3 flex items-center justify-between gap-2", children: [
883
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
884
+ "button",
885
+ {
886
+ className: "rounded-md px-3 py-2 text-sm font-medium hover:bg-muted",
887
+ onClick: () => {
888
+ void Promise.resolve(handlers.onSetFeedback?.(message, { rating: null })).then(() => setFeedbackOpen(false)).catch(() => void 0);
889
+ },
890
+ type: "button",
891
+ children: "Remove feedback"
892
+ }
893
+ ),
894
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
895
+ "button",
896
+ {
897
+ className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:opacity-90",
898
+ onClick: () => {
899
+ void Promise.resolve(handlers.onSetFeedback?.(message, { rating: "dislike", reason, comment })).then(() => setFeedbackOpen(false)).catch(() => void 0);
900
+ },
901
+ type: "button",
902
+ children: "Save"
903
+ }
904
+ )
905
+ ] })
906
+ ]
907
+ }
908
+ )
909
+ }
910
+ )
911
+ ] }) : null,
912
+ additionalActions,
789
913
  handlers.onCopy ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
790
914
  ChatMessageAction,
791
915
  {
@@ -799,41 +923,17 @@ function AgentChatDefaultActions({
799
923
  }
800
924
 
801
925
  // src/ui/agent-chat-message.tsx
802
- var React5 = __toESM(require("react"), 1);
803
- var import_lucide_react4 = require("lucide-react");
926
+ var React8 = __toESM(require("react"), 1);
927
+ var import_lucide_react5 = require("lucide-react");
804
928
 
805
929
  // src/ui/agent-response-timeline.tsx
806
- var React4 = __toESM(require("react"), 1);
807
- var import_lucide_react3 = require("lucide-react");
808
-
809
- // src/ui/message-response.tsx
810
- var React3 = __toESM(require("react"), 1);
811
- var import_streamdown = require("streamdown");
812
-
813
- // src/active-content.ts
814
- var CHAT_MARKDOWN_HARDEN_OPTIONS = Object.freeze({
815
- allowDataImages: false,
816
- allowedImagePrefixes: [],
817
- allowedLinkPrefixes: ["*"],
818
- allowedProtocols: [],
819
- defaultOrigin: void 0,
820
- imageBlockPolicy: "text-only",
821
- linkBlockPolicy: "text-only"
822
- });
823
- function safeChatMarkdownHref(value) {
824
- if (!value) return void 0;
825
- if (value.startsWith("#")) return value;
826
- try {
827
- const url = new URL(value);
828
- return url.protocol === "https:" || url.protocol === "http:" ? url.href : void 0;
829
- } catch {
830
- return void 0;
831
- }
832
- }
930
+ var React7 = __toESM(require("react"), 1);
833
931
 
834
932
  // src/streaming-text.ts
835
933
  var import_react = require("react");
836
934
  var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
935
+ var STREAM_RUNWAY_MS = 80;
936
+ var STREAM_IDLE_RELEASE_MS = 120;
837
937
  var defaultCompletedTextCache = /* @__PURE__ */ new Map();
838
938
  var defaultStreamingTextCache = {
839
939
  get: (id) => defaultCompletedTextCache.get(id),
@@ -845,6 +945,36 @@ var defaultStreamingTextCache = {
845
945
  }
846
946
  }
847
947
  };
948
+ var getActiveStreamingTextPartId = (message, streamingMessageId) => {
949
+ if (message.role !== "assistant" || streamingMessageId !== message.id) return null;
950
+ const trailingPart = message.parts[message.parts.length - 1];
951
+ return trailingPart?.kind === "text" ? trailingPart.id : null;
952
+ };
953
+ var graphemeEndOffsets = (value) => {
954
+ if (!value) return [];
955
+ if (typeof Intl !== "undefined" && "Segmenter" in Intl) {
956
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
957
+ return Array.from(segmenter.segment(value), ({ index, segment }) => index + segment.length);
958
+ }
959
+ const characters = Array.from(value);
960
+ const offsets = [];
961
+ let offset = 0;
962
+ let regionalIndicators = 0;
963
+ characters.forEach((character, index) => {
964
+ const previous = characters[index - 1];
965
+ const codePoint = character.codePointAt(0) ?? 0;
966
+ const isRegionalIndicator = codePoint >= 127462 && codePoint <= 127487;
967
+ const continuesCluster = index > 0 && (/^\p{Mark}$/u.test(character) || codePoint >= 65024 && codePoint <= 65039 || codePoint >= 127995 && codePoint <= 127999 || character === "\u200D" || previous === "\u200D" || isRegionalIndicator && regionalIndicators % 2 === 1);
968
+ if (!continuesCluster) {
969
+ if (offset > 0) offsets.push(offset);
970
+ regionalIndicators = 0;
971
+ }
972
+ offset += character.length;
973
+ regionalIndicators = isRegionalIndicator ? regionalIndicators + 1 : 0;
974
+ });
975
+ offsets.push(offset);
976
+ return offsets;
977
+ };
848
978
  function useStreamingText({
849
979
  id,
850
980
  isStreaming,
@@ -863,85 +993,104 @@ function useStreamingText({
863
993
  const displayedRef = (0, import_react.useRef)(displayedText);
864
994
  const rafRef = (0, import_react.useRef)(null);
865
995
  const lastFrameAtRef = (0, import_react.useRef)(null);
996
+ const targetUpdatedAtRef = (0, import_react.useRef)(null);
997
+ const progressRemainderRef = (0, import_react.useRef)(0);
866
998
  const idRef = (0, import_react.useRef)(id);
867
- const shouldAnimateRef = (0, import_react.useRef)(isStreaming);
999
+ const streamingRef = (0, import_react.useRef)(isStreaming);
1000
+ const reduceMotionRef = (0, import_react.useRef)(false);
868
1001
  (0, import_react.useEffect)(() => {
869
1002
  if (isStreaming || !text) return;
870
1003
  cacheAdapter?.set(id, text);
871
1004
  }, [cacheAdapter, id, isStreaming, text]);
872
1005
  (0, import_react.useEffect)(() => {
873
1006
  targetRef.current = text;
874
- }, [text]);
1007
+ targetUpdatedAtRef.current = typeof performance === "undefined" ? null : performance.now();
1008
+ if (!isStreaming || reduceMotionRef.current || !text.startsWith(displayedRef.current)) {
1009
+ displayedRef.current = text;
1010
+ setDisplayedText(text);
1011
+ progressRemainderRef.current = 0;
1012
+ }
1013
+ }, [isStreaming, text]);
875
1014
  (0, import_react.useEffect)(() => {
876
1015
  if (idRef.current === id) return;
877
1016
  idRef.current = id;
878
1017
  const cachedText = cacheAdapter?.get(id);
879
1018
  const initial = cachedText && text.startsWith(cachedText) ? cachedText : text;
880
- shouldAnimateRef.current = isStreaming && initial !== text;
881
1019
  displayedRef.current = initial;
882
1020
  setDisplayedText(initial);
883
- if (rafRef.current !== null) {
884
- cancelAnimationFrame(rafRef.current);
885
- rafRef.current = null;
886
- }
887
1021
  lastFrameAtRef.current = null;
1022
+ targetUpdatedAtRef.current = typeof performance === "undefined" ? null : performance.now();
1023
+ progressRemainderRef.current = 0;
888
1024
  }, [cacheAdapter, id, isStreaming, text]);
889
1025
  (0, import_react.useEffect)(() => {
1026
+ streamingRef.current = isStreaming;
890
1027
  if (typeof window === "undefined") {
891
1028
  displayedRef.current = text;
892
1029
  setDisplayedText(text);
893
1030
  return;
894
1031
  }
895
- const reduceMotion = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
896
- if (reduceMotion) {
1032
+ const reduceMotion2 = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1033
+ reduceMotionRef.current = reduceMotion2;
1034
+ if (reduceMotion2 || !isStreaming) {
897
1035
  displayedRef.current = text;
898
1036
  setDisplayedText(text);
899
- shouldAnimateRef.current = false;
900
1037
  return;
901
1038
  }
902
1039
  const cachedText = cacheAdapter?.get(id);
903
1040
  if (cachedText === text) {
904
1041
  displayedRef.current = text;
905
1042
  setDisplayedText(text);
906
- shouldAnimateRef.current = false;
907
1043
  return;
908
1044
  }
909
- if (isStreaming) {
910
- shouldAnimateRef.current = true;
911
- }
912
- if (!shouldAnimateRef.current) {
1045
+ if (!text.startsWith(displayedRef.current)) {
913
1046
  displayedRef.current = text;
914
1047
  setDisplayedText(text);
915
- return;
916
- }
917
- if (!text.startsWith(displayedRef.current)) {
918
- displayedRef.current = "";
919
- setDisplayedText("");
920
1048
  }
921
1049
  const tick = (timestamp) => {
922
1050
  const previousTimestamp = lastFrameAtRef.current ?? timestamp;
923
1051
  lastFrameAtRef.current = timestamp;
924
1052
  const target = targetRef.current;
925
1053
  const current = displayedRef.current;
926
- if (current.length >= target.length) {
927
- shouldAnimateRef.current = false;
1054
+ if (!streamingRef.current || reduceMotionRef.current) {
1055
+ displayedRef.current = target;
1056
+ setDisplayedText(target);
928
1057
  rafRef.current = null;
929
1058
  return;
930
1059
  }
931
- const elapsedMs = Math.max(8, timestamp - previousTimestamp);
932
- const charsFromTime = Math.max(1, Math.floor(elapsedMs / 1e3 * charsPerSecond));
933
- const gap = target.length - current.length;
934
- const catchupStep = gap > catchupThreshold ? Math.min(maxCatchupChars, Math.ceil(gap / 10)) : charsFromTime;
935
- const nextLength = Math.min(
936
- target.length,
937
- current.length + Math.max(charsFromTime, catchupStep)
1060
+ if (!target.startsWith(current)) {
1061
+ displayedRef.current = target;
1062
+ setDisplayedText(target);
1063
+ progressRemainderRef.current = 0;
1064
+ rafRef.current = window.requestAnimationFrame(tick);
1065
+ return;
1066
+ }
1067
+ const pending = target.slice(current.length);
1068
+ const pendingOffsets = graphemeEndOffsets(pending);
1069
+ if (pendingOffsets.length === 0) {
1070
+ rafRef.current = window.requestAnimationFrame(tick);
1071
+ return;
1072
+ }
1073
+ const elapsedMs = Math.max(0, timestamp - previousTimestamp);
1074
+ const targetIdleMs = targetUpdatedAtRef.current === null ? STREAM_IDLE_RELEASE_MS : Math.max(0, timestamp - targetUpdatedAtRef.current);
1075
+ const runway = targetIdleMs >= STREAM_IDLE_RELEASE_MS ? 0 : Math.min(
1076
+ pendingOffsets.length - 1,
1077
+ Math.max(1, Math.ceil(charsPerSecond * STREAM_RUNWAY_MS / 1e3))
938
1078
  );
939
- const next = target.slice(0, nextLength);
940
- displayedRef.current = next;
941
- setDisplayedText(next);
1079
+ const eligibleGraphemes = pendingOffsets.length - runway;
1080
+ progressRemainderRef.current += elapsedMs / 1e3 * charsPerSecond;
1081
+ let step = Math.floor(progressRemainderRef.current);
1082
+ if (current.length === 0 && eligibleGraphemes > 0) step = Math.max(1, step);
1083
+ const catchupStep = pendingOffsets.length > catchupThreshold ? Math.min(maxCatchupChars, Math.ceil(pendingOffsets.length / 10)) : 0;
1084
+ step = Math.min(eligibleGraphemes, Math.max(step, catchupStep));
1085
+ if (step > 0) {
1086
+ progressRemainderRef.current = Math.max(0, progressRemainderRef.current - step);
1087
+ const next = current + pending.slice(0, pendingOffsets[step - 1]);
1088
+ displayedRef.current = next;
1089
+ setDisplayedText(next);
1090
+ }
942
1091
  rafRef.current = window.requestAnimationFrame(tick);
943
1092
  };
944
- if (rafRef.current === null && displayedRef.current.length < text.length) {
1093
+ if (rafRef.current === null) {
945
1094
  rafRef.current = window.requestAnimationFrame(tick);
946
1095
  }
947
1096
  return () => {
@@ -950,28 +1099,417 @@ function useStreamingText({
950
1099
  rafRef.current = null;
951
1100
  }
952
1101
  lastFrameAtRef.current = null;
1102
+ progressRemainderRef.current = 0;
953
1103
  };
954
- }, [cacheAdapter, catchupThreshold, charsPerSecond, id, isStreaming, maxCatchupChars, text]);
1104
+ }, [cacheAdapter, catchupThreshold, charsPerSecond, id, isStreaming, maxCatchupChars]);
1105
+ const reduceMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
955
1106
  return {
956
- displayedText,
1107
+ displayedText: !isStreaming || reduceMotion ? text : displayedText,
957
1108
  isAnimating: isStreaming,
958
1109
  mode: isStreaming ? "streaming" : "static",
959
1110
  parseIncompleteMarkdown: isStreaming
960
1111
  };
961
1112
  }
962
1113
 
963
- // src/ui/message-response.tsx
1114
+ // src/ui/ask-user-interaction.tsx
1115
+ var React4 = __toESM(require("react"), 1);
1116
+ var import_lucide_react3 = require("lucide-react");
1117
+
1118
+ // src/ui/ask-user-interaction-state.ts
1119
+ function emptyAskUserDrafts(questions) {
1120
+ return Object.fromEntries(
1121
+ questions.map((question) => [
1122
+ question.id,
1123
+ { customText: "", selectedOptionIds: [] }
1124
+ ])
1125
+ );
1126
+ }
1127
+ function askUserDraftHasAnswer(draft) {
1128
+ return Boolean(
1129
+ draft && (draft.selectedOptionIds.length > 0 || draft.customText.trim().length > 0)
1130
+ );
1131
+ }
1132
+ function selectAskUserOption({
1133
+ draft,
1134
+ drafts,
1135
+ optionId,
1136
+ question
1137
+ }) {
1138
+ const selectedOptionIds = question.multiSelect ? draft.selectedOptionIds.includes(optionId) ? draft.selectedOptionIds.filter((id) => id !== optionId) : [...draft.selectedOptionIds, optionId] : [optionId];
1139
+ return {
1140
+ ...drafts,
1141
+ [question.id]: {
1142
+ customText: "",
1143
+ selectedOptionIds
1144
+ }
1145
+ };
1146
+ }
1147
+ function updateAskUserCustomText({
1148
+ customText,
1149
+ drafts,
1150
+ question
1151
+ }) {
1152
+ return {
1153
+ ...drafts,
1154
+ [question.id]: {
1155
+ customText,
1156
+ selectedOptionIds: question.multiSelect ? drafts[question.id]?.selectedOptionIds || [] : []
1157
+ }
1158
+ };
1159
+ }
1160
+ function askUserAnswersFromDrafts(questions, drafts) {
1161
+ return questions.map((question) => {
1162
+ const answer = drafts[question.id];
1163
+ return {
1164
+ questionId: question.id,
1165
+ selectedOptionIds: answer?.selectedOptionIds || [],
1166
+ ...answer?.customText.trim() ? { customText: answer.customText.trim() } : {}
1167
+ };
1168
+ });
1169
+ }
1170
+
1171
+ // src/ui/ask-user-interaction.tsx
964
1172
  var import_jsx_runtime5 = require("react/jsx-runtime");
1173
+ function resolvedAnswerText(question, answer) {
1174
+ if (!answer) return ["No response recorded"];
1175
+ const labels = answer.selectedOptionIds.flatMap((optionId) => {
1176
+ const option = question.options.find((item) => item.id === optionId);
1177
+ return option ? [option.label] : [];
1178
+ });
1179
+ if (answer.customText) labels.push(answer.customText);
1180
+ return labels.length ? labels : ["No response recorded"];
1181
+ }
1182
+ function ResolvedQuestions({
1183
+ buttonRef,
1184
+ part
1185
+ }) {
1186
+ const [open, setOpen] = React4.useState(false);
1187
+ const count = part.questions.length;
1188
+ const answers = new Map(
1189
+ (part.resolution?.answers || []).map((answer) => [
1190
+ answer.questionId,
1191
+ answer
1192
+ ])
1193
+ );
1194
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "a24-ask-user a24-ask-user--resolved w-full", children: [
1195
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1196
+ "button",
1197
+ {
1198
+ "aria-expanded": open,
1199
+ className: "group flex min-w-0 items-center gap-1.5 rounded-md px-0 py-0.5 text-start text-sm text-muted-foreground transition-colors hover:text-foreground motion-reduce:transition-none",
1200
+ onClick: () => setOpen((value) => !value),
1201
+ ref: buttonRef,
1202
+ type: "button",
1203
+ children: [
1204
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
1205
+ "Asked ",
1206
+ count,
1207
+ " question",
1208
+ count === 1 ? "" : "s"
1209
+ ] }),
1210
+ open ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.ChevronDown, { "aria-hidden": "true", className: "size-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1211
+ import_lucide_react3.ChevronRight,
1212
+ {
1213
+ "aria-hidden": "true",
1214
+ className: "size-3.5 opacity-70 rtl:rotate-180"
1215
+ }
1216
+ )
1217
+ ]
1218
+ }
1219
+ ),
1220
+ open ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mt-2 space-y-3 ps-4", children: part.questions.map((question) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "min-w-0", children: [
1221
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-sm font-medium text-foreground", children: question.question }),
1222
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mt-1 space-y-1 text-sm text-muted-foreground", children: resolvedAnswerText(
1223
+ question,
1224
+ answers.get(question.id)
1225
+ ).map((value, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1226
+ "div",
1227
+ {
1228
+ className: "whitespace-pre-wrap break-words",
1229
+ children: value
1230
+ },
1231
+ `${question.id}:${index}`
1232
+ )) })
1233
+ ] }, question.id)) }) : null
1234
+ ] });
1235
+ }
1236
+ function AskUserInteraction({
1237
+ error,
1238
+ isBusy = false,
1239
+ onSubmit,
1240
+ part
1241
+ }) {
1242
+ const questions = part.questions;
1243
+ const [step, setStep] = React4.useState(0);
1244
+ const [drafts, setDrafts] = React4.useState(
1245
+ () => emptyAskUserDrafts(questions)
1246
+ );
1247
+ const headingRef = React4.useRef(null);
1248
+ const questionsRef = React4.useRef(questions);
1249
+ const resolvedButtonRef = React4.useRef(null);
1250
+ const wasResolvedRef = React4.useRef(part.status !== "pending");
1251
+ questionsRef.current = questions;
1252
+ React4.useEffect(() => {
1253
+ setStep(0);
1254
+ setDrafts(emptyAskUserDrafts(questionsRef.current));
1255
+ }, [part.interruptId]);
1256
+ React4.useEffect(() => {
1257
+ headingRef.current?.focus();
1258
+ }, [step]);
1259
+ React4.useEffect(() => {
1260
+ const isResolved = part.status !== "pending";
1261
+ if (isResolved && !wasResolvedRef.current) {
1262
+ resolvedButtonRef.current?.focus();
1263
+ }
1264
+ wasResolvedRef.current = isResolved;
1265
+ }, [part.status]);
1266
+ if (part.status !== "pending") {
1267
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ResolvedQuestions, { buttonRef: resolvedButtonRef, part });
1268
+ }
1269
+ const question = questions[step];
1270
+ if (!question) {
1271
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-sm text-destructive", role: "alert", children: "This question request is unavailable." });
1272
+ }
1273
+ const draft = drafts[question.id] || {
1274
+ customText: "",
1275
+ selectedOptionIds: []
1276
+ };
1277
+ const isLastQuestion = step === questions.length - 1;
1278
+ const canMoveForward = askUserDraftHasAnswer(draft);
1279
+ const completeStep = (nextDrafts) => {
1280
+ if (isLastQuestion) {
1281
+ onSubmit?.(askUserAnswersFromDrafts(questions, nextDrafts));
1282
+ return;
1283
+ }
1284
+ setStep((current) => Math.min(current + 1, questions.length - 1));
1285
+ };
1286
+ const selectOption = (optionId) => {
1287
+ const nextDrafts = selectAskUserOption({
1288
+ draft,
1289
+ drafts,
1290
+ optionId,
1291
+ question
1292
+ });
1293
+ setDrafts(nextDrafts);
1294
+ if (!question.multiSelect) completeStep(nextDrafts);
1295
+ };
1296
+ const updateCustomText = (customText) => {
1297
+ setDrafts(
1298
+ (current) => updateAskUserCustomText({
1299
+ customText,
1300
+ drafts: current,
1301
+ question
1302
+ })
1303
+ );
1304
+ };
1305
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "a24-ask-user space-y-3 rounded-xl border border-border/50 bg-background p-4 shadow-sm", children: [
1306
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-w-0 items-start justify-between gap-4", children: [
1307
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1308
+ "h3",
1309
+ {
1310
+ className: "min-w-0 flex-1 text-base font-medium leading-6 text-foreground outline-none",
1311
+ ref: headingRef,
1312
+ tabIndex: -1,
1313
+ children: question.question
1314
+ }
1315
+ ),
1316
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex shrink-0 items-center gap-1 text-muted-foreground", children: [
1317
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1318
+ "button",
1319
+ {
1320
+ "aria-label": "Previous question",
1321
+ className: "flex size-8 items-center justify-center rounded-full transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-30",
1322
+ disabled: isBusy || step === 0,
1323
+ onClick: () => setStep((current) => Math.max(0, current - 1)),
1324
+ type: "button",
1325
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1326
+ import_lucide_react3.ChevronLeft,
1327
+ {
1328
+ "aria-hidden": "true",
1329
+ className: "size-4 rtl:rotate-180"
1330
+ }
1331
+ )
1332
+ }
1333
+ ),
1334
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1335
+ "span",
1336
+ {
1337
+ "aria-label": `Question ${step + 1} of ${questions.length}`,
1338
+ className: "min-w-12 text-center text-sm tabular-nums",
1339
+ children: [
1340
+ step + 1,
1341
+ " / ",
1342
+ questions.length
1343
+ ]
1344
+ }
1345
+ ),
1346
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1347
+ "button",
1348
+ {
1349
+ "aria-label": isLastQuestion ? "Submit answers" : "Next question",
1350
+ className: "flex size-8 items-center justify-center rounded-full transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-30",
1351
+ disabled: isBusy || !onSubmit || !canMoveForward,
1352
+ onClick: () => completeStep(drafts),
1353
+ type: "button",
1354
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1355
+ import_lucide_react3.ChevronRight,
1356
+ {
1357
+ "aria-hidden": "true",
1358
+ className: "size-4 rtl:rotate-180"
1359
+ }
1360
+ )
1361
+ }
1362
+ )
1363
+ ] })
1364
+ ] }),
1365
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1", children: [
1366
+ question.options.map((option, index) => {
1367
+ const selected = draft.selectedOptionIds.includes(option.id);
1368
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1369
+ "button",
1370
+ {
1371
+ "aria-pressed": selected,
1372
+ className: cn(
1373
+ "group flex min-h-11 w-full items-center gap-3 rounded-xl px-2.5 py-2 text-start text-sm transition-colors motion-reduce:transition-none",
1374
+ selected ? "bg-muted" : "hover:bg-muted/70"
1375
+ ),
1376
+ disabled: isBusy || !onSubmit,
1377
+ onClick: () => selectOption(option.id),
1378
+ type: "button",
1379
+ children: [
1380
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1381
+ "span",
1382
+ {
1383
+ className: cn(
1384
+ "flex size-8 shrink-0 items-center justify-center rounded-full border text-sm tabular-nums transition-colors",
1385
+ selected ? "border-border bg-background text-foreground" : "border-border/70 bg-muted/60 text-muted-foreground"
1386
+ ),
1387
+ children: index + 1
1388
+ }
1389
+ ),
1390
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "min-w-0 flex-1 font-medium text-foreground", children: option.label }),
1391
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1392
+ import_lucide_react3.ChevronRight,
1393
+ {
1394
+ "aria-hidden": "true",
1395
+ className: "size-4 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100 rtl:rotate-180"
1396
+ }
1397
+ )
1398
+ ]
1399
+ },
1400
+ option.id
1401
+ );
1402
+ }),
1403
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-h-11 items-center gap-2 rounded-xl bg-muted px-2.5 py-1.5 focus-within:ring-1 focus-within:ring-ring", children: [
1404
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-full border border-border/70 bg-background/70 text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.Pencil, { "aria-hidden": "true", className: "size-4" }) }),
1405
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1406
+ "input",
1407
+ {
1408
+ "aria-label": "Other response",
1409
+ autoComplete: "off",
1410
+ className: "h-8 min-w-0 flex-1 bg-transparent px-1 text-sm text-foreground outline-none placeholder:text-muted-foreground",
1411
+ disabled: isBusy || !onSubmit,
1412
+ maxLength: 2e3,
1413
+ onChange: (event) => updateCustomText(event.target.value),
1414
+ onKeyDown: (event) => {
1415
+ if (event.key === "Enter" && !event.nativeEvent.isComposing && draft.customText.trim()) {
1416
+ event.preventDefault();
1417
+ completeStep(drafts);
1418
+ }
1419
+ },
1420
+ placeholder: "Other\u2026",
1421
+ value: draft.customText
1422
+ }
1423
+ ),
1424
+ draft.customText.trim() ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1425
+ "button",
1426
+ {
1427
+ className: "h-8 shrink-0 rounded-full bg-foreground px-4 text-sm font-medium text-background transition-colors hover:opacity-85 disabled:opacity-50",
1428
+ disabled: isBusy || !onSubmit,
1429
+ onClick: () => completeStep(drafts),
1430
+ type: "button",
1431
+ children: isLastQuestion ? "Submit" : "Next"
1432
+ }
1433
+ ) : null
1434
+ ] })
1435
+ ] }),
1436
+ error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-sm text-destructive", role: "alert", children: error }) : null
1437
+ ] });
1438
+ }
1439
+
1440
+ // src/ui/agent-chat-context-compression-row.tsx
1441
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1442
+ function compressionPresentation(part) {
1443
+ if (part.status === "failed") {
1444
+ return { label: "Could not compact message history", status: "failed" };
1445
+ }
1446
+ if (part.status === "completed") {
1447
+ return { label: "Compacted message history", status: "completed" };
1448
+ }
1449
+ return { label: "Compacting message history", status: "running" };
1450
+ }
1451
+ function AgentChatContextCompressionRow({
1452
+ className,
1453
+ part
1454
+ }) {
1455
+ const presentation = compressionPresentation(part);
1456
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1457
+ "div",
1458
+ {
1459
+ "aria-live": "polite",
1460
+ className: cn("a24-context-compression-row", className),
1461
+ "data-agents24-context-compression": "",
1462
+ "data-status": part.status,
1463
+ role: "status",
1464
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-tool-trigger-row", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cn(
1465
+ "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--without-orb",
1466
+ presentation.status === "failed" && "a24-tool-trigger--error",
1467
+ presentation.status === "running" && "a24-tool-trigger--active"
1468
+ ), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: cn(
1469
+ "a24-tool-trigger__label",
1470
+ presentation.status === "running" && "a24-tool-shimmer"
1471
+ ), children: presentation.label }) }) })
1472
+ }
1473
+ );
1474
+ }
1475
+
1476
+ // src/ui/message-response.tsx
1477
+ var React5 = __toESM(require("react"), 1);
1478
+ var import_streamdown = require("streamdown");
1479
+
1480
+ // src/active-content.ts
1481
+ var CHAT_MARKDOWN_HARDEN_OPTIONS = Object.freeze({
1482
+ allowDataImages: false,
1483
+ allowedImagePrefixes: [],
1484
+ allowedLinkPrefixes: ["*"],
1485
+ allowedProtocols: [],
1486
+ defaultOrigin: void 0,
1487
+ imageBlockPolicy: "text-only",
1488
+ linkBlockPolicy: "text-only"
1489
+ });
1490
+ function safeChatMarkdownHref(value) {
1491
+ if (!value) return void 0;
1492
+ if (value.startsWith("#")) return value;
1493
+ try {
1494
+ const url = new URL(value);
1495
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : void 0;
1496
+ } catch {
1497
+ return void 0;
1498
+ }
1499
+ }
1500
+
1501
+ // src/ui/message-response.tsx
1502
+ var import_jsx_runtime7 = require("react/jsx-runtime");
965
1503
  function SafeMarkdownLink({ children, href, ...props }) {
966
1504
  const domProps = { ...props };
967
1505
  delete domProps.node;
968
1506
  const safeHref = safeChatMarkdownHref(href);
969
- if (!safeHref) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children });
970
- if (safeHref.startsWith("#")) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { ...domProps, href: safeHref, children });
971
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { ...domProps, href: safeHref, rel: "noopener noreferrer", target: "_blank", children });
1507
+ if (!safeHref) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children });
1508
+ if (safeHref.startsWith("#")) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { ...domProps, href: safeHref, children });
1509
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { ...domProps, href: safeHref, rel: "noopener noreferrer", target: "_blank", children });
972
1510
  }
973
1511
  function BlockedMarkdownImage({ alt }) {
974
- return alt ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-label": `Image blocked: ${alt}`, role: "img", children: alt }) : null;
1512
+ return alt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-label": `Image blocked: ${alt}`, role: "img", children: alt }) : null;
975
1513
  }
976
1514
  var BLOCK_MARKDOWN_TAGS = /* @__PURE__ */ new Set([
977
1515
  "blockquote",
@@ -992,7 +1530,7 @@ function isMultilineCodeNode(node) {
992
1530
  return node?.tagName === "code" && node.position?.start?.line !== void 0 && node.position?.end?.line !== void 0 && node.position.start.line !== node.position.end.line;
993
1531
  }
994
1532
  function hasBlockMarkdownChild(child) {
995
- if (!React3.isValidElement(child)) {
1533
+ if (!React5.isValidElement(child)) {
996
1534
  return false;
997
1535
  }
998
1536
  if (typeof child.type === "string" && BLOCK_MARKDOWN_TAGS.has(child.type)) {
@@ -1002,7 +1540,7 @@ function hasBlockMarkdownChild(child) {
1002
1540
  if (BLOCK_MARKDOWN_TAGS.has(props.node?.tagName || "") || isMultilineCodeNode(props.node)) {
1003
1541
  return true;
1004
1542
  }
1005
- return React3.Children.toArray(props.children).some(hasBlockMarkdownChild);
1543
+ return React5.Children.toArray(props.children).some(hasBlockMarkdownChild);
1006
1544
  }
1007
1545
  function MessageMarkdownParagraph({
1008
1546
  children,
@@ -1010,17 +1548,17 @@ function MessageMarkdownParagraph({
1010
1548
  }) {
1011
1549
  const domProps = { ...props };
1012
1550
  delete domProps.node;
1013
- const childArray = React3.Children.toArray(children).filter(
1551
+ const childArray = React5.Children.toArray(children).filter(
1014
1552
  (child) => child !== null && child !== ""
1015
1553
  );
1016
- const isImageOnly = childArray.length === 1 && React3.isValidElement(childArray[0]) && childArray[0].props.node?.tagName === "img";
1554
+ const isImageOnly = childArray.length === 1 && React5.isValidElement(childArray[0]) && childArray[0].props.node?.tagName === "img";
1017
1555
  if (isImageOnly) {
1018
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children });
1556
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children });
1019
1557
  }
1020
1558
  if (childArray.some(hasBlockMarkdownChild)) {
1021
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ...domProps, children });
1559
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ...domProps, children });
1022
1560
  }
1023
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { ...domProps, children });
1561
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { ...domProps, children });
1024
1562
  }
1025
1563
  var MESSAGE_RESPONSE_COMPONENTS = {
1026
1564
  a: SafeMarkdownLink,
@@ -1040,7 +1578,7 @@ var MESSAGE_RESPONSE_REHYPE_PLUGINS = [
1040
1578
  ]
1041
1579
  ] : []
1042
1580
  ];
1043
- var MessageResponse = React3.memo(
1581
+ var MessageResponse = React5.memo(
1044
1582
  ({
1045
1583
  children,
1046
1584
  className,
@@ -1058,14 +1596,14 @@ var MessageResponse = React3.memo(
1058
1596
  maxCatchupChars: 32,
1059
1597
  text
1060
1598
  });
1061
- const streamdownComponents = React3.useMemo(
1599
+ const streamdownComponents = React5.useMemo(
1062
1600
  () => ({ ...MESSAGE_RESPONSE_COMPONENTS, ...components }),
1063
1601
  [components]
1064
1602
  );
1065
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1603
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1066
1604
  import_streamdown.Streamdown,
1067
1605
  {
1068
- className: cn("size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0", className),
1606
+ className: cn("a24-message-response", className),
1069
1607
  components: streamdownComponents,
1070
1608
  rehypePlugins: rehypePlugins || MESSAGE_RESPONSE_REHYPE_PLUGINS,
1071
1609
  ...props,
@@ -1085,6 +1623,7 @@ function hitlActionLabel(part, action) {
1085
1623
  if (action === "approve") return ["tool_review", "app_data_permission"].includes(part.hitlKind) ? "Allow once" : "Approve";
1086
1624
  if (action === "reject") return ["tool_review", "app_data_permission"].includes(part.hitlKind) ? "Don\u2019t allow" : "Reject";
1087
1625
  if (action === "connect") return "Connect";
1626
+ if (action === "respond") return "Submit";
1088
1627
  return "Skip";
1089
1628
  }
1090
1629
  function hitlTitle(part) {
@@ -1112,102 +1651,424 @@ function resolvedHitlLabel(part) {
1112
1651
  }[outcome] || outcome.replace(/_/g, " ");
1113
1652
  }
1114
1653
 
1115
- // src/ui/agent-response-timeline.tsx
1116
- var import_jsx_runtime6 = require("react/jsx-runtime");
1117
- function optionalString2(value) {
1118
- return typeof value === "string" && value.trim() ? value.trim() : null;
1119
- }
1120
- function presentationValue(part, key) {
1654
+ // src/ui/tool-sessions.tsx
1655
+ var React6 = __toESM(require("react"), 1);
1656
+ var import_lucide_react4 = require("lucide-react");
1657
+ var import_thinking_orbs = require("thinking-orbs");
1658
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1659
+ function agentChatToolLabel(part) {
1121
1660
  const presentation = part.presentation || {};
1122
- const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
1123
- return optionalString2(presentation[key]) || optionalString2(presentation[snakeKey]);
1661
+ return optionalText(presentation.activity) || optionalText(presentation.title) || optionalText(part.displayName) || optionalText(part.summary) || optionalText(part.toolName) || "Tool activity";
1124
1662
  }
1125
- function groupKey(part) {
1126
- return presentationValue(part, "groupKey");
1663
+ function isAgentChatSubagent(part) {
1664
+ const sourceKind = String(part.sourceKind || "").trim().toLowerCase();
1665
+ return sourceKind === "agent" || sourceKind === "agent_call" || Boolean(part.presentation.childRun);
1127
1666
  }
1128
- function groupLabel(part) {
1129
- return presentationValue(part, "groupLabel") || toolLabel(part);
1667
+ function isAgentChatToolRunning(part) {
1668
+ return part.state === "input-available" || part.state === "input-streaming";
1130
1669
  }
1131
- function toolLabel(part) {
1132
- return presentationValue(part, "activity") || presentationValue(part, "title") || part.toolName || "Tool activity";
1670
+ function latestRunningAgentChatToolId(parts) {
1671
+ const active = [...parts].reverse().find(isAgentChatToolRunning);
1672
+ return active?.toolCallId || active?.id || null;
1133
1673
  }
1134
- function toolDetail(part) {
1135
- return presentationValue(part, "detail") || part.errorText || null;
1674
+ function partitionAgentChatToolTimeline(parts, toolViewMode = "closed") {
1675
+ const segments = [];
1676
+ let index = 0;
1677
+ while (index < parts.length) {
1678
+ const part = parts[index];
1679
+ const leadingModelTurn = part.kind === "model-turn" && parts[index + 1]?.kind === "tool" && !isAgentChatSubagent(parts[index + 1]) ? part : void 0;
1680
+ if (leadingModelTurn) index += 1;
1681
+ const candidate = parts[index];
1682
+ if (candidate.kind !== "tool") {
1683
+ segments.push({ kind: "part", part: candidate });
1684
+ index += 1;
1685
+ continue;
1686
+ }
1687
+ if (isAgentChatSubagent(candidate)) {
1688
+ segments.push({ kind: "subagent", part: candidate });
1689
+ index += 1;
1690
+ continue;
1691
+ }
1692
+ const streak = [];
1693
+ while (index < parts.length) {
1694
+ const candidate2 = parts[index];
1695
+ if (candidate2.kind !== "tool" || isAgentChatSubagent(candidate2)) break;
1696
+ streak.push(candidate2);
1697
+ index += 1;
1698
+ }
1699
+ const modelTurn = parts[index]?.kind === "model-turn" ? parts[index] : void 0;
1700
+ if (modelTurn) index += 1;
1701
+ if (toolViewMode === "expanded") {
1702
+ if (leadingModelTurn) segments.push({ kind: "part", part: leadingModelTurn });
1703
+ segments.push(...streak.map((toolPart) => ({ kind: "tool", part: toolPart })));
1704
+ if (modelTurn) segments.push({ kind: "part", part: modelTurn });
1705
+ } else if (streak.length === 1 && !modelTurn && !leadingModelTurn) {
1706
+ segments.push({ kind: "tool", part: streak[0] });
1707
+ } else {
1708
+ segments.push({
1709
+ kind: "tool-session",
1710
+ parts: streak,
1711
+ ...leadingModelTurn ? { leadingModelTurn } : {},
1712
+ ...modelTurn ? { modelTurn } : {}
1713
+ });
1714
+ }
1715
+ }
1716
+ return segments;
1136
1717
  }
1137
- function isActiveTool(part, activeToolId) {
1138
- return part.id === activeToolId || part.toolCallId === activeToolId || part.state === "input-available" || part.state === "input-streaming";
1718
+ function optionalText(value) {
1719
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1139
1720
  }
1140
- function Shimmer({ children, className }) {
1141
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: cn("inline-block animate-pulse", className), children });
1721
+ function isActive(part, activeToolId) {
1722
+ return Boolean(activeToolId && (part.id === activeToolId || part.toolCallId === activeToolId));
1142
1723
  }
1143
- function CompactTask({
1144
- children,
1145
- defaultOpen = false,
1146
- detail,
1147
- error,
1724
+ function ToolActivityReel({
1725
+ announce = true,
1726
+ initialLabel,
1727
+ initialShownAt,
1728
+ label
1729
+ }) {
1730
+ const initial = initialLabel && initialLabel !== label ? initialLabel : label;
1731
+ const parsedInitialShownAt = initialShownAt ? Date.parse(initialShownAt) : Number.NaN;
1732
+ const [displayed, setDisplayed] = React6.useState(initial);
1733
+ const [previous, setPrevious] = React6.useState(null);
1734
+ const displayedRef = React6.useRef(initial);
1735
+ const latestRef = React6.useRef(label);
1736
+ const shownAtRef = React6.useRef(Number.isFinite(parsedInitialShownAt) ? parsedInitialShownAt : Date.now());
1737
+ React6.useEffect(() => {
1738
+ latestRef.current = label;
1739
+ if (displayedRef.current === label) return;
1740
+ const showLatest = () => {
1741
+ const next = latestRef.current;
1742
+ if (displayedRef.current === next) return;
1743
+ const old = displayedRef.current;
1744
+ displayedRef.current = next;
1745
+ shownAtRef.current = Date.now();
1746
+ setPrevious(old);
1747
+ setDisplayed(next);
1748
+ };
1749
+ const remaining = Math.max(0, 500 - (Date.now() - shownAtRef.current));
1750
+ if (remaining === 0) {
1751
+ showLatest();
1752
+ return;
1753
+ }
1754
+ const timer = window.setTimeout(showLatest, remaining);
1755
+ return () => window.clearTimeout(timer);
1756
+ }, [label]);
1757
+ React6.useEffect(() => {
1758
+ if (!previous) return;
1759
+ const timer = window.setTimeout(() => setPrevious(null), 200);
1760
+ return () => window.clearTimeout(timer);
1761
+ }, [previous]);
1762
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "a24-tool-reel", "aria-live": announce ? "polite" : "off", children: [
1763
+ previous ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "aria-hidden": "true", className: "a24-tool-reel__previous", children: previous }) : null,
1764
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: previous ? "a24-tool-reel__current" : void 0, children: displayed })
1765
+ ] });
1766
+ }
1767
+ function ToolActivityOrb({ label }) {
1768
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-orb", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_thinking_orbs.ThinkingOrb, { "aria-label": label || "Working", size: 20, speed: 0.8, state: "searching" }) });
1769
+ }
1770
+ function AgentChatActivityRow({
1148
1771
  label,
1149
- loading
1772
+ modelTurnId,
1773
+ onSelect
1150
1774
  }) {
1151
- const [open, setOpen] = React4.useState(defaultOpen);
1152
- const expandable = Boolean(children || detail);
1153
- const content = loading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Shimmer, { children: label }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: label });
1154
- if (!expandable) {
1155
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cn("flex min-w-0 items-center gap-1.5 text-sm", error ? "text-destructive" : "text-muted-foreground"), children: content });
1775
+ const content = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1776
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityOrb, { label }),
1777
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label a24-tool-shimmer", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityReel, { label }) })
1778
+ ] });
1779
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-tool-trigger-row", "data-agents24-model-turn": modelTurnId, role: "status", children: onSelect ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { className: "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--active", onClick: onSelect, type: "button", children: content }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--active", children: content }) });
1780
+ }
1781
+ function AgentChatModelTurnRow({ part }) {
1782
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AgentChatActivityRow, { label: "Planning next moves", modelTurnId: part.modelTurnId });
1783
+ }
1784
+ function AgentChatNodeActivityRow({
1785
+ onSelect,
1786
+ part
1787
+ }) {
1788
+ const [open, setOpen] = React6.useState(false);
1789
+ const contentId = React6.useId();
1790
+ const running = part.status === "running";
1791
+ const failed = part.status === "failed";
1792
+ const failureMessage = part.failure?.message || "The node could not complete the request.";
1793
+ const triggerClassName = cn(
1794
+ "a24-tool-trigger",
1795
+ "a24-tool-trigger--root",
1796
+ !running && "a24-tool-trigger--without-orb",
1797
+ running && "a24-tool-trigger--active",
1798
+ failed && "a24-tool-trigger--error"
1799
+ );
1800
+ const label = running ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-shimmer", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityReel, { label: part.label }) }) : part.label;
1801
+ if (!failed) {
1802
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-tool-trigger-row", role: "status", children: onSelect ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
1803
+ running ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityOrb, { label: part.label }) : null,
1804
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label })
1805
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: triggerClassName, children: [
1806
+ running ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityOrb, { label: part.label }) : null,
1807
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label })
1808
+ ] }) });
1156
1809
  }
1157
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "w-full", children: [
1158
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1810
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-tool-disclosure", children: [
1811
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-tool-trigger-row", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1159
1812
  "button",
1160
1813
  {
1814
+ "aria-controls": contentId,
1161
1815
  "aria-expanded": open,
1162
- className: cn(
1163
- "group flex min-w-0 items-center gap-1.5 rounded-md px-0 py-0.5 text-left text-sm transition-colors hover:text-foreground",
1164
- error ? "text-destructive" : "text-muted-foreground"
1165
- ),
1166
- onClick: () => setOpen((value) => !value),
1816
+ className: triggerClassName,
1817
+ onClick: () => {
1818
+ onSelect?.(part);
1819
+ setOpen((value) => !value);
1820
+ },
1167
1821
  type: "button",
1168
1822
  children: [
1169
- content,
1170
- open ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.ChevronDown, { className: "size-3" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.ChevronRight, { className: "size-3 opacity-70" })
1823
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label }),
1824
+ open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.ChevronDown, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.ChevronRight, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
1171
1825
  ]
1172
1826
  }
1173
- ),
1174
- open ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mt-1 pl-4 text-sm text-muted-foreground", children: [
1175
- detail ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: detail }) : null,
1176
- children
1177
- ] }) : null
1827
+ ) }),
1828
+ open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-tool-detail a24-tool-detail--error", id: contentId, children: failureMessage }) : null
1178
1829
  ] });
1179
1830
  }
1180
- function ToolPartRow({
1181
- activeToolId,
1182
- part
1831
+ function toolRowLabel(part) {
1832
+ const label = agentChatToolLabel(part);
1833
+ return isAgentChatToolRunning(part) ? label : label.replace(/^(?:running|ran)\s+/i, "");
1834
+ }
1835
+ function ToolDisclosure({
1836
+ children,
1837
+ error,
1838
+ label,
1839
+ loading,
1840
+ onExpandedChange,
1841
+ onSelect,
1842
+ part,
1843
+ root,
1844
+ showOrb,
1845
+ statusLabel,
1846
+ accessory
1183
1847
  }) {
1184
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1185
- CompactTask,
1848
+ const [open, setOpen] = React6.useState(false);
1849
+ const contentId = React6.useId();
1850
+ const expandable = React6.Children.toArray(children).length > 0;
1851
+ if (!expandable) {
1852
+ const triggerClassName = cn("a24-tool-trigger", root && "a24-tool-trigger--root", root && !showOrb && "a24-tool-trigger--without-orb", error && "a24-tool-trigger--error", loading && "a24-tool-trigger--active");
1853
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-tool-trigger-row", children: [
1854
+ onSelect && part ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
1855
+ showOrb ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityOrb, { label: statusLabel }) : null,
1856
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label })
1857
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: triggerClassName, children: [
1858
+ showOrb ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityOrb, { label: statusLabel }) : null,
1859
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label })
1860
+ ] }),
1861
+ accessory
1862
+ ] });
1863
+ }
1864
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-tool-disclosure", children: [
1865
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-tool-trigger-row", children: [
1866
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1867
+ "button",
1868
+ {
1869
+ "aria-controls": contentId,
1870
+ "aria-expanded": open,
1871
+ className: cn("a24-tool-trigger", root && "a24-tool-trigger--root", root && !showOrb && "a24-tool-trigger--without-orb", error && "a24-tool-trigger--error", loading && "a24-tool-trigger--active"),
1872
+ onClick: () => {
1873
+ const nextOpen = !open;
1874
+ setOpen(nextOpen);
1875
+ if (part) onSelect?.(part);
1876
+ onExpandedChange?.(nextOpen, part);
1877
+ },
1878
+ type: "button",
1879
+ children: [
1880
+ showOrb ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityOrb, { label: statusLabel }) : null,
1881
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label }),
1882
+ open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.ChevronDown, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.ChevronRight, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
1883
+ ]
1884
+ }
1885
+ ),
1886
+ accessory
1887
+ ] }),
1888
+ open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-tool-disclosure__content", id: contentId, children }) : null
1889
+ ] });
1890
+ }
1891
+ function ToolLabel({ active, label, part, slots }) {
1892
+ const rendered = slots?.renderToolLabel?.({ isActive: active, label, part }) ?? label;
1893
+ return active ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-shimmer", children: rendered }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: rendered });
1894
+ }
1895
+ function ToolList({ children }) {
1896
+ const listRef = React6.useRef(null);
1897
+ const [fades, setFades] = React6.useState({ bottom: false, top: false });
1898
+ const updateFades = React6.useCallback(() => {
1899
+ const list = listRef.current;
1900
+ if (!list) return;
1901
+ const next = {
1902
+ bottom: list.scrollTop + list.clientHeight < list.scrollHeight - 1,
1903
+ top: list.scrollTop > 1
1904
+ };
1905
+ setFades((current) => current.bottom === next.bottom && current.top === next.top ? current : next);
1906
+ }, []);
1907
+ React6.useEffect(() => {
1908
+ updateFades();
1909
+ const list = listRef.current;
1910
+ if (!list || typeof ResizeObserver === "undefined") return;
1911
+ const observer = new ResizeObserver(updateFades);
1912
+ observer.observe(list);
1913
+ return () => observer.disconnect();
1914
+ }, [children, updateFades]);
1915
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1916
+ "div",
1186
1917
  {
1187
- defaultOpen: part.state === "output-error",
1188
- detail: toolDetail(part),
1189
- error: part.state === "output-error",
1190
- label: toolLabel(part),
1191
- loading: isActiveTool(part, activeToolId)
1918
+ className: "a24-tool-list-frame",
1919
+ "data-scroll-fade-bottom": fades.bottom ? "" : void 0,
1920
+ "data-scroll-fade-top": fades.top ? "" : void 0,
1921
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-tool-list", onScroll: updateFades, ref: listRef, role: "list", children })
1192
1922
  }
1193
- ) });
1923
+ );
1194
1924
  }
1195
- function ToolGroup({
1925
+ function AgentChatToolRow({
1196
1926
  activeToolId,
1197
- parts
1927
+ part,
1928
+ root = true,
1929
+ showOrb,
1930
+ slots
1198
1931
  }) {
1199
- const loading = parts.some((part) => isActiveTool(part, activeToolId));
1200
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CompactTask, { label: groupLabel(parts[0]), loading, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "space-y-1", children: parts.map((part) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1201
- "div",
1932
+ const active = isActive(part, activeToolId);
1933
+ const failed = part.state === "output-error";
1934
+ const failureMessage = part.failure?.message || null;
1935
+ const detail = failed ? failureMessage : optionalText(part.presentation.detail);
1936
+ const hostDetails = failed ? null : slots?.renderToolDetails?.(part);
1937
+ const context = { isActive: active, label: toolRowLabel(part), part };
1938
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1939
+ ToolDisclosure,
1202
1940
  {
1203
- className: cn("text-sm", part.state === "output-error" ? "text-destructive" : "text-muted-foreground"),
1941
+ error: failed,
1942
+ accessory: slots?.renderToolAccessory?.(context),
1943
+ label: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolLabel, { active, label: context.label, part, slots }),
1944
+ loading: active,
1945
+ onExpandedChange: slots?.onExpandedChange,
1946
+ onSelect: slots?.onToolSelect,
1947
+ part,
1948
+ root,
1949
+ showOrb: showOrb ?? active,
1950
+ statusLabel: active ? context.label : void 0,
1204
1951
  children: [
1205
- isActiveTool(part, activeToolId) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Shimmer, { children: toolLabel(part) }) : toolLabel(part),
1206
- toolDetail(part) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ml-1 text-xs text-muted-foreground/75", children: toolDetail(part) }) : null
1952
+ detail ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cn("a24-tool-detail", failed && "a24-tool-detail--error"), children: detail }) : null,
1953
+ hostDetails
1207
1954
  ]
1208
- },
1209
- part.id
1210
- )) }) });
1955
+ }
1956
+ ) });
1957
+ }
1958
+ function sessionLabel(parts, visiblePart, running) {
1959
+ const label = agentChatToolLabel(visiblePart);
1960
+ const terminalLabel = label.replace(/^(?:running|ran)\s+/i, "");
1961
+ return running ? label : `Ran ${terminalLabel} + ${parts.length - 1} more`;
1962
+ }
1963
+ function AgentChatToolSession({
1964
+ activeToolId,
1965
+ isLatestActivity = true,
1966
+ isLive = false,
1967
+ leadingModelTurn,
1968
+ modelTurn,
1969
+ parts,
1970
+ slots
1971
+ }) {
1972
+ const presentedLiveRef = React6.useRef(false);
1973
+ if (isLive && isLatestActivity) presentedLiveRef.current = true;
1974
+ const activeIndex = parts.findLastIndex((part) => isActive(part, activeToolId));
1975
+ const running = activeIndex >= 0;
1976
+ if (parts.length === 1 && !modelTurn && !leadingModelTurn) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AgentChatToolRow, { activeToolId, part: parts[0], slots });
1977
+ const visibleIndex = running ? activeIndex : parts.length - 1;
1978
+ const visiblePart = parts[visibleIndex];
1979
+ const planning = Boolean(modelTurn);
1980
+ const presentingActivity = isLatestActivity && (planning || running || isLive);
1981
+ const failed = isLatestActivity && (visiblePart.state === "output-error" || visiblePart.state === "cancelled");
1982
+ const label = planning ? "Planning next moves" : isLive && isLatestActivity ? agentChatToolLabel(visiblePart) : sessionLabel(parts, visiblePart, running);
1983
+ const labelContent = presentedLiveRef.current ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: presentingActivity ? "a24-tool-shimmer" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityReel, { initialLabel: leadingModelTurn ? "Planning next moves" : void 0, initialShownAt: leadingModelTurn?.timestamp, label }) }) : label;
1984
+ const context = { isActive: presentingActivity, label, part: visiblePart };
1985
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1986
+ ToolDisclosure,
1987
+ {
1988
+ accessory: slots?.renderToolAccessory?.(context),
1989
+ error: failed,
1990
+ label: labelContent,
1991
+ loading: presentingActivity,
1992
+ onExpandedChange: slots?.onExpandedChange,
1993
+ part: visiblePart,
1994
+ root: true,
1995
+ showOrb: isLive && isLatestActivity,
1996
+ statusLabel: planning ? "Planning next moves" : label,
1997
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolList, { children: parts.map((part) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { role: "listitem", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AgentChatToolRow, { activeToolId, part, root: false, showOrb: false, slots }) }, part.id)) })
1998
+ }
1999
+ );
2000
+ }
2001
+ function childLabel(child) {
2002
+ return optionalText(child.title) || optionalText(child.toolName) || "Tool";
2003
+ }
2004
+ function subagentLabel(part) {
2005
+ const childRun = part.presentation.childRun;
2006
+ const children = part.presentation.childTools || [];
2007
+ const name = optionalText(childRun?.agentName) || optionalText(part.displayName) || "agent";
2008
+ const status = String(childRun?.status || (isAgentChatToolRunning(part) ? "running" : "completed")).toLowerCase();
2009
+ const activeChild = children.find((child) => child.id === part.presentation.activeChildToolId) || [...children].reverse().find((child) => child.status === "running") || children[children.length - 1];
2010
+ if (status === "running" && activeChild) return { label: `Agent ${name}, running ${childLabel(activeChild)}`, running: true };
2011
+ if (status === "running") return { label: `Agent ${name}, starting`, running: true };
2012
+ if (status === "failed" || part.state === "output-error") return { label: `Agent ${name}, failed`, running: false };
2013
+ if (status === "cancelled" || status === "canceled" || part.state === "cancelled") return { label: `Agent ${name}, cancelled`, running: false };
2014
+ if (status === "paused") return { label: `Agent ${name}, paused`, running: false };
2015
+ return { label: `Agent ${name}, ran ${children.length} ${children.length === 1 ? "tool" : "tools"}`, running: false };
2016
+ }
2017
+ function AgentChatSubagentGroup({ isLatestActivity = true, isLive = false, part, slots }) {
2018
+ const presentedLiveRef = React6.useRef(false);
2019
+ if (isLive && isLatestActivity) presentedLiveRef.current = true;
2020
+ const children = part.presentation.childTools || [];
2021
+ const state = subagentLabel(part);
2022
+ const childRunStatus = String(part.presentation.childRun?.status || "").toLowerCase();
2023
+ const failed = part.state === "output-error" || part.state === "cancelled" || childRunStatus === "failed" || childRunStatus === "cancelled" || childRunStatus === "canceled";
2024
+ const hostDetails = failed ? null : slots?.renderToolDetails?.(part);
2025
+ const failureMessage = part.failure?.message || null;
2026
+ const presentingActivity = isLatestActivity && state.running;
2027
+ const labelContent = presentedLiveRef.current ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: presentingActivity ? "a24-tool-shimmer" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityReel, { label: state.label }) }) : state.label;
2028
+ const context = { isActive: presentingActivity, label: state.label, part };
2029
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2030
+ ToolDisclosure,
2031
+ {
2032
+ accessory: slots?.renderToolAccessory?.(context),
2033
+ error: failed,
2034
+ label: labelContent,
2035
+ loading: presentingActivity,
2036
+ onExpandedChange: slots?.onExpandedChange,
2037
+ part,
2038
+ root: true,
2039
+ showOrb: isLive && isLatestActivity,
2040
+ statusLabel: state.label,
2041
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(ToolList, { children: [
2042
+ children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cn("a24-child-tool", child.status === "failed" && "a24-child-tool--error"), role: "listitem", children: slots?.renderChildTool?.(child, part) ?? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2043
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: cn(isLive && isLatestActivity && child.status === "running" && "a24-tool-shimmer"), children: childLabel(child) }),
2044
+ child.detail ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-child-tool__detail", children: child.detail }) : null
2045
+ ] }) }, child.id)),
2046
+ failureMessage ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-tool-detail a24-tool-detail--error", children: failureMessage }) : null,
2047
+ hostDetails
2048
+ ] })
2049
+ }
2050
+ );
2051
+ }
2052
+
2053
+ // src/ui/agent-response-timeline.tsx
2054
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2055
+ function isInternalAskUserTool(part) {
2056
+ return [part.toolName, part.toolKey, part.action].some((value) => value?.trim().toLowerCase() === "runtime__ask_user");
2057
+ }
2058
+ function Shimmer({ children, className }) {
2059
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: cn("inline-block animate-pulse", className), children });
2060
+ }
2061
+ function AgentChatToolPart({
2062
+ activeToolId,
2063
+ part
2064
+ }) {
2065
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatToolRow, { activeToolId, part });
2066
+ }
2067
+ function AgentChatToolGroup({
2068
+ activeToolId,
2069
+ parts
2070
+ }) {
2071
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatToolSession, { activeToolId, parts });
1211
2072
  }
1212
2073
  function HitlPartRow({
1213
2074
  getHitlActionState,
@@ -1216,22 +2077,40 @@ function HitlPartRow({
1216
2077
  part
1217
2078
  }) {
1218
2079
  const state = getHitlActionState?.(part);
1219
- const [comment, setComment] = React4.useState("");
2080
+ const [comment, setComment] = React7.useState("");
1220
2081
  const presentation = part.presentation || {};
1221
2082
  const requireComment = presentation.require_comment === true;
1222
2083
  const isTerminal = part.status !== "pending";
1223
- const resolvedRef = React4.useRef(null);
1224
- const wasTerminal = React4.useRef(isTerminal);
1225
- React4.useEffect(() => {
2084
+ const resolvedRef = React7.useRef(null);
2085
+ const wasTerminal = React7.useRef(isTerminal);
2086
+ React7.useEffect(() => {
1226
2087
  if (isTerminal && !wasTerminal.current) resolvedRef.current?.focus();
1227
2088
  wasTerminal.current = isTerminal;
1228
2089
  }, [isTerminal]);
2090
+ if (part.hitlKind === "user_input") {
2091
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2092
+ AskUserInteraction,
2093
+ {
2094
+ error: state?.error,
2095
+ isBusy: isLoading || state?.status === "resuming",
2096
+ onSubmit: onHitlAction ? (answers) => onHitlAction(part, {
2097
+ action: "respond",
2098
+ answers: answers.map((answer) => ({
2099
+ question_id: answer.questionId,
2100
+ selected_option_ids: answer.selectedOptionIds,
2101
+ ...answer.customText ? { custom_text: answer.customText } : {}
2102
+ }))
2103
+ }) : void 0,
2104
+ part
2105
+ }
2106
+ );
2107
+ }
1229
2108
  if (isTerminal) {
1230
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-hitl a24-hitl--resolved px-1 py-1 text-sm text-muted-foreground", ref: resolvedRef, role: "status", tabIndex: -1, children: resolvedHitlLabel(part) });
2109
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "a24-hitl a24-hitl--resolved px-1 py-1 text-sm text-muted-foreground", ref: resolvedRef, role: "status", tabIndex: -1, children: resolvedHitlLabel(part) });
1231
2110
  }
1232
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "a24-hitl space-y-2.5 px-1 py-2", children: [
1233
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-hitl__title text-sm font-medium text-foreground text-pretty", children: hitlTitle(part) }),
1234
- requireComment ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2111
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "a24-hitl space-y-2.5 px-1 py-2", children: [
2112
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "a24-hitl__title text-sm font-medium text-foreground text-pretty", children: hitlTitle(part) }),
2113
+ requireComment ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1235
2114
  "textarea",
1236
2115
  {
1237
2116
  "aria-label": "Approval comment",
@@ -1243,8 +2122,8 @@ function HitlPartRow({
1243
2122
  value: comment
1244
2123
  }
1245
2124
  ) : null,
1246
- state?.error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-hitl__error text-sm text-destructive", children: state.error }) : null,
1247
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-hitl__actions flex flex-wrap items-center gap-1.5", children: part.allowedActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2125
+ state?.error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "a24-hitl__error text-sm text-destructive", children: state.error }) : null,
2126
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "a24-hitl__actions flex flex-wrap items-center gap-1.5", children: part.allowedActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1248
2127
  "button",
1249
2128
  {
1250
2129
  "aria-label": hitlActionLabel(part, action),
@@ -1254,7 +2133,10 @@ function HitlPartRow({
1254
2133
  ),
1255
2134
  "data-primary": action === "approve" || action === "connect" ? "" : void 0,
1256
2135
  disabled: !onHitlAction || isLoading || state?.status === "connecting" || state?.status === "resuming" || requireComment && !comment.trim(),
1257
- onClick: () => onHitlAction?.(part, action, comment.trim() || void 0),
2136
+ onClick: () => onHitlAction?.(part, {
2137
+ action,
2138
+ ...comment.trim() ? { comment: comment.trim() } : {}
2139
+ }),
1258
2140
  type: "button",
1259
2141
  children: hitlActionLabel(part, action)
1260
2142
  },
@@ -1262,28 +2144,45 @@ function HitlPartRow({
1262
2144
  )) })
1263
2145
  ] });
1264
2146
  }
1265
- function DefaultPartRow({
2147
+ function AgentChatPart({
1266
2148
  activeToolId,
1267
2149
  getHitlActionState,
1268
2150
  isLoading,
2151
+ isTextStreaming,
1269
2152
  message,
1270
2153
  onHitlAction,
1271
- part
2154
+ part,
2155
+ responseProps,
2156
+ toolSlots
1272
2157
  }) {
1273
- if (part.kind === "text") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageResponse, { children: part.text });
1274
- if (part.kind === "tool") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToolPartRow, { activeToolId, part });
2158
+ if (part.kind === "text") {
2159
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2160
+ MessageResponse,
2161
+ {
2162
+ ...responseProps,
2163
+ streaming: isTextStreaming ?? Boolean(isLoading && part.raw?.status === "streaming"),
2164
+ streamingId: part.id,
2165
+ children: part.text
2166
+ }
2167
+ );
2168
+ }
2169
+ if (part.kind === "tool") return isAgentChatSubagent(part) ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatSubagentGroup, { part, slots: toolSlots }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatToolRow, { activeToolId, part, slots: toolSlots });
2170
+ if (part.kind === "model-turn") return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatModelTurnRow, { part });
1275
2171
  if (part.kind === "reasoning") {
1276
2172
  const label = part.text || part.label || "Thinking";
1277
- return part.status === "running" || part.status === "streaming" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Shimmer, { className: "text-sm text-muted-foreground", children: label }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-muted-foreground", children: label });
2173
+ return part.status === "running" || part.status === "streaming" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Shimmer, { className: "text-sm text-muted-foreground", children: label }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "text-sm text-muted-foreground", children: label });
2174
+ }
2175
+ if (part.kind === "context-compression") {
2176
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatContextCompressionRow, { part });
1278
2177
  }
1279
2178
  if (part.kind === "ui-blocks") {
1280
2179
  if (part.state === "output-error") {
1281
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-destructive", children: part.errorText || "Failed to render UI content." });
2180
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "a24-timeline-error text-sm text-destructive", children: part.errorText || "Failed to render UI content." });
1282
2181
  }
1283
- return part.bundle ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SafeUiBlocks, { bundle: part.bundle }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CompactTask, { label: part.state === "input-streaming" ? "Rendering UI block" : "Rendered UI block", loading: part.state === "input-streaming" });
2182
+ return part.bundle ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SafeUiBlocks, { bundle: part.bundle }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "text-sm text-muted-foreground", children: part.state === "input-streaming" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Shimmer, { children: "Rendering UI block" }) : "Rendered UI block" });
1284
2183
  }
1285
2184
  if (part.kind === "hitl") {
1286
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2185
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1287
2186
  HitlPartRow,
1288
2187
  {
1289
2188
  getHitlActionState,
@@ -1293,18 +2192,18 @@ function DefaultPartRow({
1293
2192
  }
1294
2193
  );
1295
2194
  }
1296
- if (part.kind === "error") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-destructive", role: "alert", children: part.errorText });
2195
+ if (part.kind === "error") return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "a24-timeline-error text-sm text-destructive", role: "alert", children: part.errorText });
1297
2196
  if (part.kind === "source") {
1298
2197
  const href = safeExternalUrl(part.url);
1299
- return href ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { className: "a24-source-link", href, rel: "noopener noreferrer", target: "_blank", children: part.title }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "a24-source-link", children: part.title });
2198
+ return href ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("a", { className: "a24-source-link", href, rel: "noopener noreferrer", target: "_blank", children: part.title }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "a24-source-link", children: part.title });
1300
2199
  }
1301
2200
  if (part.kind === "citation") {
1302
2201
  const href = safeExternalUrl(part.url);
1303
2202
  const label = part.label || "Citation";
1304
- return href ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { className: "a24-citation", href, rel: "noopener noreferrer", target: "_blank", children: label }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "a24-citation", children: label });
2203
+ return href ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("a", { className: "a24-citation", href, rel: "noopener noreferrer", target: "_blank", children: label }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "a24-citation", children: label });
1305
2204
  }
1306
- if (part.kind === "attachment") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-output-attachment", children: part.attachment.filename || "Attachment" });
1307
- if (part.kind === "data") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { "data-agents24-data-part": part.name, className: "hidden", children: message.id });
2205
+ if (part.kind === "attachment") return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "a24-output-attachment", children: part.attachment.filename || "Attachment" });
2206
+ if (part.kind === "data") return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { "data-agents24-data-part": part.name, className: "hidden", children: message.id });
1308
2207
  return null;
1309
2208
  }
1310
2209
  function safeExternalUrl(value) {
@@ -1318,28 +2217,28 @@ function safeExternalUrl(value) {
1318
2217
  }
1319
2218
  function SafeUiBlocks({ bundle }) {
1320
2219
  const rows = Array.isArray(bundle.rows) ? bundle.rows : [];
1321
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { "aria-label": typeof bundle.title === "string" ? bundle.title : "Agent result", className: "a24-ui-blocks", children: [
1322
- typeof bundle.title === "string" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: bundle.title }) : null,
2220
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { "aria-label": typeof bundle.title === "string" ? bundle.title : "Agent result", className: "a24-ui-blocks", children: [
2221
+ typeof bundle.title === "string" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { children: bundle.title }) : null,
1323
2222
  rows.map((rawRow, rowIndex) => {
1324
2223
  const row = rawRow && typeof rawRow === "object" && !Array.isArray(rawRow) ? rawRow : {};
1325
2224
  const blocks = Array.isArray(row.blocks) ? row.blocks : [];
1326
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-ui-blocks__row", children: blocks.map((rawBlock, blockIndex) => {
2225
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "a24-ui-blocks__row", children: blocks.map((rawBlock, blockIndex) => {
1327
2226
  const block = rawBlock && typeof rawBlock === "object" && !Array.isArray(rawBlock) ? rawBlock : {};
1328
2227
  const title = typeof block.title === "string" ? block.title : "Result";
1329
2228
  const kind = String(block.kind || "note");
1330
2229
  if (kind === "table" && Array.isArray(block.columns) && Array.isArray(block.rows)) {
1331
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "a24-ui-block", children: [
1332
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h4", { children: title }),
1333
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-ui-table-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("table", { children: [
1334
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { children: block.columns.map((column, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { children: String(column) }, index)) }) }),
1335
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tbody", { children: block.rows.map((rawCells, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { children: (Array.isArray(rawCells) ? rawCells : []).map((cell, cellIndex) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { children: String(cell) }, cellIndex)) }, index)) })
2230
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "a24-ui-block", children: [
2231
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h4", { children: title }),
2232
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "a24-ui-table-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { children: [
2233
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tr", { children: block.columns.map((column, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { children: String(column) }, index)) }) }),
2234
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tbody", { children: block.rows.map((rawCells, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tr", { children: (Array.isArray(rawCells) ? rawCells : []).map((cell, cellIndex) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { children: String(cell) }, cellIndex)) }, index)) })
1336
2235
  ] }) })
1337
2236
  ] }, String(block.id || blockIndex));
1338
2237
  }
1339
2238
  const value = block.value ?? block.text ?? "";
1340
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "a24-ui-block", children: [
1341
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h4", { children: title }),
1342
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: String(value) })
2239
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "a24-ui-block", children: [
2240
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h4", { children: title }),
2241
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { children: String(value) })
1343
2242
  ] }, String(block.id || blockIndex));
1344
2243
  }) }, `row-${rowIndex}`);
1345
2244
  })
@@ -1349,53 +2248,81 @@ function AgentResponseTimeline({
1349
2248
  getHitlActionState,
1350
2249
  isLoading = false,
1351
2250
  message,
2251
+ nodeActivityMode = "default",
1352
2252
  onHitlAction,
1353
- renderPart
2253
+ parts: displayParts,
2254
+ responseProps,
2255
+ toolSlots,
2256
+ toolViewMode = "closed"
1354
2257
  }) {
1355
- const parts = message.parts || [];
1356
- const activeToolId = [...parts].reverse().find((part) => part.kind === "tool" && (part.state === "input-available" || part.state === "input-streaming"))?.id;
2258
+ const parts = (displayParts || message.parts || []).filter((part) => (part.kind !== "tool" || !isInternalAskUserTool(part)) && (part.kind !== "model-turn" || part.turnKind === "agent") && (part.kind !== "node-activity" || nodeActivityMode === "all" || nodeActivityMode === "default" && part.defaultChatVisible));
2259
+ const toolParts = parts.filter((part) => part.kind === "tool");
2260
+ const activeToolId = latestRunningAgentChatToolId(toolParts);
2261
+ const activeTextPartId = getActiveStreamingTextPartId(
2262
+ { ...message, parts: [...parts] },
2263
+ isLoading ? message.id : null
2264
+ );
1357
2265
  const rendered = [];
1358
- let index = 0;
1359
- while (index < parts.length) {
1360
- const part = parts[index];
1361
- if (part.kind === "tool" && groupKey(part)) {
1362
- const key = groupKey(part);
1363
- const group = [];
1364
- while (index < parts.length) {
1365
- const candidate = parts[index];
1366
- if (candidate.kind !== "tool" || groupKey(candidate) !== key) break;
1367
- group.push(candidate);
1368
- index += 1;
1369
- }
1370
- rendered.push(group.length === 1 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToolPartRow, { activeToolId, part: group[0] }, group[0].id) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToolGroup, { activeToolId, parts: group }, `tool-group-${key}-${group[0].id}`));
2266
+ let activeActivityClaimed = false;
2267
+ const segments = partitionAgentChatToolTimeline(parts, toolViewMode);
2268
+ for (const [segmentIndex, segment] of segments.entries()) {
2269
+ if (segment.kind === "tool") {
2270
+ const isLatestActivity = segmentIndex === segments.length - 1;
2271
+ const showOrb = toolViewMode === "closed" ? isLoading && isLatestActivity : Boolean(activeToolId && (segment.part.id === activeToolId || segment.part.toolCallId === activeToolId));
2272
+ rendered.push(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatToolRow, { activeToolId, part: segment.part, showOrb, slots: toolSlots }, segment.part.id));
2273
+ continue;
2274
+ }
2275
+ if (segment.kind === "tool-session") {
2276
+ rendered.push(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatToolSession, { activeToolId, isLatestActivity: segmentIndex === segments.length - 1, isLive: isLoading, leadingModelTurn: segment.leadingModelTurn, modelTurn: segment.modelTurn, parts: segment.parts, slots: toolSlots }, `tool-session-${segment.parts[0].id}`));
2277
+ continue;
2278
+ }
2279
+ if (segment.kind === "subagent") {
2280
+ rendered.push(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatSubagentGroup, { isLatestActivity: segmentIndex === segments.length - 1, isLive: isLoading, part: segment.part, slots: toolSlots }, segment.part.id));
2281
+ continue;
2282
+ }
2283
+ const part = segment.part;
2284
+ if (part.kind === "model-turn") {
2285
+ const key = activeActivityClaimed ? part.id : `${message.id}-active-activity`;
2286
+ activeActivityClaimed = true;
2287
+ rendered.push(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatActivityRow, { label: "Planning next moves", modelTurnId: part.modelTurnId }, key));
2288
+ continue;
2289
+ }
2290
+ if (part.kind === "node-activity") {
2291
+ const key = activeActivityClaimed ? part.id : `${message.id}-active-activity`;
2292
+ if (part.status === "running") activeActivityClaimed = true;
2293
+ rendered.push(
2294
+ part.status === "running" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatActivityRow, { label: part.label }, key) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatNodeActivityRow, { part }, part.id)
2295
+ );
1371
2296
  continue;
1372
2297
  }
1373
2298
  rendered.push(
1374
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(React4.Fragment, { children: renderPart ? renderPart(part, message) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1375
- DefaultPartRow,
2299
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(React7.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2300
+ AgentChatPart,
1376
2301
  {
1377
2302
  activeToolId,
1378
2303
  getHitlActionState,
1379
2304
  isLoading,
2305
+ isTextStreaming: part.kind === "text" && part.id === activeTextPartId,
1380
2306
  message,
1381
2307
  onHitlAction,
1382
- part
2308
+ part,
2309
+ responseProps,
2310
+ toolSlots
1383
2311
  }
1384
2312
  ) }, part.id)
1385
2313
  );
1386
- index += 1;
1387
2314
  }
1388
2315
  if (rendered.length === 0 && message.content) {
1389
- rendered.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageResponse, { children: message.content }, `${message.id}-fallback`));
2316
+ rendered.push(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MessageResponse, { children: message.content }, `${message.id}-fallback`));
1390
2317
  }
1391
2318
  if (rendered.length === 0 && isLoading) {
1392
- rendered.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-muted-foreground", children: "Thinking" }, `${message.id}-thinking`));
2319
+ rendered.push(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatActivityRow, { label: "Thinking" }, `${message.id}-active-activity`));
1393
2320
  }
1394
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "space-y-3", children: rendered });
2321
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "space-y-3", children: rendered });
1395
2322
  }
1396
2323
 
1397
2324
  // src/ui/agent-chat-message.tsx
1398
- var import_jsx_runtime7 = require("react/jsx-runtime");
2325
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1399
2326
  var DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD = 360;
1400
2327
  var DEFAULT_USER_MESSAGE_COLLAPSED_LINES = 4;
1401
2328
  function shouldCollapseUserMessage(content, threshold = DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD) {
@@ -1408,19 +2335,19 @@ function AgentChatUserMessageContent({
1408
2335
  showLessLabel = "Show less",
1409
2336
  showMoreLabel = "Show more"
1410
2337
  }) {
1411
- const [isExpanded, setIsExpanded] = React5.useState(false);
1412
- const contentId = React5.useId();
2338
+ const [isExpanded, setIsExpanded] = React8.useState(false);
2339
+ const contentId = React8.useId();
1413
2340
  const shouldCollapse = shouldCollapseUserMessage(content, collapseThreshold);
1414
- const Icon = isExpanded ? import_lucide_react4.ChevronUp : import_lucide_react4.ChevronDown;
2341
+ const Icon = isExpanded ? import_lucide_react5.ChevronUp : import_lucide_react5.ChevronDown;
1415
2342
  const clampedStyle = shouldCollapse && !isExpanded ? {
1416
2343
  WebkitBoxOrient: "vertical",
1417
2344
  WebkitLineClamp: Math.max(1, collapsedLines),
1418
2345
  display: "-webkit-box",
1419
2346
  overflow: "hidden"
1420
2347
  } : void 0;
1421
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-w-0 flex-col gap-3", children: [
1422
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { id: contentId, className: "min-w-0", style: clampedStyle, children: content }),
1423
- shouldCollapse ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2348
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex min-w-0 flex-col gap-3", children: [
2349
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { id: contentId, className: "min-w-0", style: clampedStyle, children: content }),
2350
+ shouldCollapse ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1424
2351
  "button",
1425
2352
  {
1426
2353
  "aria-controls": contentId,
@@ -1433,7 +2360,7 @@ function AgentChatUserMessageContent({
1433
2360
  type: "button",
1434
2361
  children: [
1435
2362
  isExpanded ? showLessLabel : showMoreLabel,
1436
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { "aria-hidden": "true", className: "size-4", strokeWidth: 2 })
2363
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon, { "aria-hidden": "true", className: "size-4", strokeWidth: 2 })
1437
2364
  ]
1438
2365
  }
1439
2366
  ) : null
@@ -1441,6 +2368,7 @@ function AgentChatUserMessageContent({
1441
2368
  }
1442
2369
  function AgentChatMessage({
1443
2370
  actionHandlers,
2371
+ additionalActions,
1444
2372
  actions,
1445
2373
  actionsClassName,
1446
2374
  className,
@@ -1448,9 +2376,10 @@ function AgentChatMessage({
1448
2376
  dir,
1449
2377
  getHitlActionState,
1450
2378
  message,
2379
+ nodeActivityMode,
1451
2380
  onHitlAction,
2381
+ Timeline = AgentResponseTimeline,
1452
2382
  renderAssistantContent,
1453
- renderPart,
1454
2383
  renderUserContent,
1455
2384
  showAssistantAttachments = false,
1456
2385
  streamingMessageId,
@@ -1465,9 +2394,17 @@ function AgentChatMessage({
1465
2394
  const resolvedUserContent = userMessageContent ?? message.content;
1466
2395
  const shouldRenderContent = isAssistant || Boolean(renderUserContent || resolvedUserContent);
1467
2396
  const showAttachments = Boolean(message.attachments?.length) && (message.role === "user" || showAssistantAttachments);
1468
- const renderedActions = actions ?? (actionHandlers ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AgentChatDefaultActions, { handlers: actionHandlers, message }) : null);
1469
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(ChatMessage, { className, dir, from: message.role, children: [
1470
- showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChatAttachments, { className: "mb-2", dir, children: message.attachments?.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2397
+ const renderedActions = actions ?? (actionHandlers ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2398
+ AgentChatDefaultActions,
2399
+ {
2400
+ additionalActions,
2401
+ dir,
2402
+ handlers: actionHandlers,
2403
+ message
2404
+ }
2405
+ ) : null);
2406
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ChatMessage, { className, dir, from: message.role, children: [
2407
+ showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatAttachments, { className: "mb-2", dir, children: message.attachments?.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1471
2408
  ChatAttachment,
1472
2409
  {
1473
2410
  data: attachment,
@@ -1475,23 +2412,25 @@ function AgentChatMessage({
1475
2412
  },
1476
2413
  String(attachment.url || attachment.filename || index)
1477
2414
  )) }) : null,
1478
- shouldRenderContent ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2415
+ shouldRenderContent ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1479
2416
  ChatMessageContent,
1480
2417
  {
1481
2418
  className: cn(isAssistant ? "bg-transparent p-0" : void 0, contentClassName),
1482
2419
  dir,
2420
+ from: message.role,
1483
2421
  children: [
1484
- isAssistant ? renderAssistantContent ? renderAssistantContent({ isStreaming, message }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1485
- AgentResponseTimeline,
2422
+ isAssistant ? renderAssistantContent ? renderAssistantContent({ isStreaming, message }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2423
+ Timeline,
1486
2424
  {
1487
2425
  getHitlActionState,
1488
2426
  isLoading: isStreaming,
1489
2427
  message,
2428
+ nodeActivityMode,
1490
2429
  onHitlAction,
1491
- renderPart
2430
+ parts: message.parts
1492
2431
  }
1493
2432
  ) : null,
1494
- !isAssistant ? renderUserContent ? renderUserContent({ message }) : resolvedUserContent ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2433
+ !isAssistant ? renderUserContent ? renderUserContent({ message }) : resolvedUserContent ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1495
2434
  AgentChatUserMessageContent,
1496
2435
  {
1497
2436
  collapsedLines: userMessageCollapsedLines,
@@ -1504,16 +2443,117 @@ function AgentChatMessage({
1504
2443
  ]
1505
2444
  }
1506
2445
  ) : null,
1507
- renderedActions ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: cn(dir === "rtl" ? "mt-1 h-9 w-full translate-x-2" : "mt-1 h-9 w-full -translate-x-2", actionsClassName), children: renderedActions }) : null
2446
+ renderedActions ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: cn(dir === "rtl" ? "mt-1 h-9 w-full" : "mt-1 h-9 w-full -translate-x-2", actionsClassName), children: renderedActions }) : null
1508
2447
  ] });
1509
2448
  }
1510
2449
 
2450
+ // src/ui/agent-chat-context-status.tsx
2451
+ var React9 = __toESM(require("react"), 1);
2452
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2453
+ var COMPACT_TOKENS = new Intl.NumberFormat("en-US", { notation: "compact" });
2454
+ var PERCENT = new Intl.NumberFormat("en-US", {
2455
+ style: "percent",
2456
+ maximumFractionDigits: 1
2457
+ });
2458
+ var EXACT_SOURCES = /* @__PURE__ */ new Set(["exact", "provider_usage"]);
2459
+ function contextStatusMetrics(contextWindow) {
2460
+ const maximum = Number(contextWindow?.max_tokens || 0);
2461
+ const input = Math.max(0, Number(contextWindow?.input_tokens || 0));
2462
+ const admissionInput = Math.max(0, Number(contextWindow?.admission_input_tokens ?? input));
2463
+ const usageRatio = Math.max(0, Math.min(1, Number(
2464
+ contextWindow?.usage_ratio ?? input / maximum
2465
+ )));
2466
+ const admissionRatio = Math.max(0, Math.min(1, Number(
2467
+ contextWindow?.admission_ratio ?? admissionInput / maximum
2468
+ )));
2469
+ return { maximum, input, admissionInput, usageRatio, admissionRatio };
2470
+ }
2471
+ function AgentChatContextStatus({
2472
+ className,
2473
+ contextWindow
2474
+ }) {
2475
+ const tooltipId = React9.useId();
2476
+ const [open, setOpen] = React9.useState(false);
2477
+ const { maximum, input, admissionInput, usageRatio, admissionRatio } = contextStatusMetrics(contextWindow);
2478
+ const percentage = Math.round(usageRatio * 100);
2479
+ const exact = EXACT_SOURCES.has(contextWindow?.source || "unknown");
2480
+ if (!Number.isFinite(maximum) || maximum <= 0) return null;
2481
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2482
+ "div",
2483
+ {
2484
+ className: cn("a24-context-status", className),
2485
+ onBlurCapture: (event) => {
2486
+ if (!event.currentTarget.contains(event.relatedTarget)) setOpen(false);
2487
+ },
2488
+ onFocusCapture: () => setOpen(true),
2489
+ onMouseEnter: () => setOpen(true),
2490
+ onMouseLeave: () => setOpen(false),
2491
+ children: [
2492
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2493
+ "button",
2494
+ {
2495
+ "aria-describedby": open ? tooltipId : void 0,
2496
+ "aria-label": `Context window ${percentage}% used`,
2497
+ className: "a24-context-status__trigger",
2498
+ onKeyDown: (event) => {
2499
+ if (event.key === "Escape") setOpen(false);
2500
+ },
2501
+ type: "button",
2502
+ children: [
2503
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("svg", { "aria-hidden": "true", className: "a24-context-status__ring", viewBox: "0 0 24 24", children: [
2504
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("circle", { className: "a24-context-status__track", cx: "12", cy: "12", r: "10" }),
2505
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2506
+ "circle",
2507
+ {
2508
+ className: "a24-context-status__value",
2509
+ cx: "12",
2510
+ cy: "12",
2511
+ pathLength: "100",
2512
+ r: "10",
2513
+ strokeDasharray: `${percentage} 100`
2514
+ }
2515
+ )
2516
+ ] }),
2517
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
2518
+ percentage,
2519
+ "%"
2520
+ ] })
2521
+ ]
2522
+ }
2523
+ ),
2524
+ open ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "a24-context-status__popover", id: tooltipId, role: "tooltip", children: [
2525
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "a24-context-status__sr-only", children: "Context window" }),
2526
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("strong", { children: [
2527
+ PERCENT.format(usageRatio),
2528
+ " context used"
2529
+ ] }),
2530
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
2531
+ exact ? "Input" : "Estimated input",
2532
+ ": ",
2533
+ COMPACT_TOKENS.format(input),
2534
+ " / ",
2535
+ COMPACT_TOKENS.format(maximum),
2536
+ " tokens"
2537
+ ] }),
2538
+ admissionInput !== input ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
2539
+ "Safety bound: ",
2540
+ COMPACT_TOKENS.format(admissionInput),
2541
+ " tokens (",
2542
+ PERCENT.format(admissionRatio),
2543
+ ")"
2544
+ ] }) : null
2545
+ ] }) : null
2546
+ ]
2547
+ }
2548
+ );
2549
+ }
2550
+
1511
2551
  // src/ui/bubble.tsx
1512
2552
  var import_class_variance_authority2 = require("class-variance-authority");
1513
2553
  var import_react_slot2 = require("@radix-ui/react-slot");
1514
- var import_jsx_runtime8 = require("react/jsx-runtime");
2554
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1515
2555
  function BubbleGroup({ className, ...props }) {
1516
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2556
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1517
2557
  "div",
1518
2558
  {
1519
2559
  "data-slot": "bubble-group",
@@ -1547,7 +2587,7 @@ function Bubble({
1547
2587
  className,
1548
2588
  ...props
1549
2589
  }) {
1550
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2590
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1551
2591
  "div",
1552
2592
  {
1553
2593
  "data-slot": "bubble",
@@ -1564,7 +2604,7 @@ function BubbleContent({
1564
2604
  ...props
1565
2605
  }) {
1566
2606
  const Comp = asChild ? import_react_slot2.Slot : "div";
1567
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2607
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1568
2608
  Comp,
1569
2609
  {
1570
2610
  "data-slot": "bubble-content",
@@ -1601,7 +2641,7 @@ function BubbleReactions({
1601
2641
  className,
1602
2642
  ...props
1603
2643
  }) {
1604
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2644
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1605
2645
  "div",
1606
2646
  {
1607
2647
  "data-slot": "bubble-reactions",
@@ -1616,7 +2656,7 @@ function BubbleReactions({
1616
2656
  // src/ui/marker.tsx
1617
2657
  var import_class_variance_authority3 = require("class-variance-authority");
1618
2658
  var import_react_slot3 = require("@radix-ui/react-slot");
1619
- var import_jsx_runtime9 = require("react/jsx-runtime");
2659
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1620
2660
  var markerVariants = (0, import_class_variance_authority3.cva)(
1621
2661
  "group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-foreground",
1622
2662
  {
@@ -1636,7 +2676,7 @@ function Marker({
1636
2676
  ...props
1637
2677
  }) {
1638
2678
  const Comp = asChild ? import_react_slot3.Slot : "div";
1639
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2679
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1640
2680
  Comp,
1641
2681
  {
1642
2682
  "data-slot": "marker",
@@ -1647,7 +2687,7 @@ function Marker({
1647
2687
  );
1648
2688
  }
1649
2689
  function MarkerIcon({ className, ...props }) {
1650
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2690
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1651
2691
  "span",
1652
2692
  {
1653
2693
  "data-slot": "marker-icon",
@@ -1661,7 +2701,7 @@ function MarkerIcon({ className, ...props }) {
1661
2701
  );
1662
2702
  }
1663
2703
  function MarkerContent({ className, ...props }) {
1664
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2704
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1665
2705
  "span",
1666
2706
  {
1667
2707
  "data-slot": "marker-content",
@@ -1675,7 +2715,7 @@ function MarkerContent({ className, ...props }) {
1675
2715
  }
1676
2716
 
1677
2717
  // src/ui/mcp-connect-required-card.tsx
1678
- var import_jsx_runtime10 = require("react/jsx-runtime");
2718
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1679
2719
  var STATUS_LABELS = {
1680
2720
  pending: "Connect",
1681
2721
  connecting: "Connecting",
@@ -1699,7 +2739,7 @@ function McpConnectRequiredCard({
1699
2739
  const isBusy = loading || status === "connecting" || status === "resuming";
1700
2740
  const isConnected = status === "connected";
1701
2741
  const buttonDisabled = disabled || isBusy || isConnected || !onConnect;
1702
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2742
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1703
2743
  "div",
1704
2744
  {
1705
2745
  "data-agents24-mcp-connect-card": true,
@@ -1709,13 +2749,13 @@ function McpConnectRequiredCard({
1709
2749
  className
1710
2750
  ),
1711
2751
  ...props,
1712
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex min-w-0 items-start justify-between gap-3", children: [
1713
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "min-w-0", children: [
1714
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "truncate text-sm font-medium", children: label }),
1715
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-1 text-sm text-muted-foreground", children: message || "Connect your account to continue." }),
1716
- status === "error" && error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-2 text-sm text-destructive", children: error }) : null
2752
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex min-w-0 items-start justify-between gap-3", children: [
2753
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "min-w-0", children: [
2754
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "truncate text-sm font-medium", children: label }),
2755
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "mt-1 text-sm text-muted-foreground", children: message || "Connect your account to continue." }),
2756
+ status === "error" && error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "mt-2 text-sm text-destructive", children: error }) : null
1717
2757
  ] }),
1718
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2758
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1719
2759
  "button",
1720
2760
  {
1721
2761
  type: "button",
@@ -1732,14 +2772,87 @@ function McpConnectRequiredCard({
1732
2772
  }
1733
2773
  );
1734
2774
  }
2775
+
2776
+ // src/ui/use-audio-recorder.ts
2777
+ var React10 = __toESM(require("react"), 1);
2778
+ var RECORDING_CANDIDATES = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4"];
2779
+ function recordingExtension(mimeType) {
2780
+ return mimeType.startsWith("audio/mp4") ? "m4a" : "webm";
2781
+ }
2782
+ function useAudioRecorder(onRecorded, options = {}) {
2783
+ const recorderRef = React10.useRef(null);
2784
+ const streamRef = React10.useRef(null);
2785
+ const onRecordedRef = React10.useRef(onRecorded);
2786
+ const onStreamChangeRef = React10.useRef(options.onStreamChange);
2787
+ onRecordedRef.current = onRecorded;
2788
+ onStreamChangeRef.current = options.onStreamChange;
2789
+ const [isRecording, setIsRecording] = React10.useState(false);
2790
+ const [error, setError] = React10.useState(null);
2791
+ const stop = React10.useCallback(() => recorderRef.current?.stop(), []);
2792
+ const start = React10.useCallback(async () => {
2793
+ setError(null);
2794
+ if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === "undefined") {
2795
+ setError("Audio recording is not supported in this browser.");
2796
+ return;
2797
+ }
2798
+ try {
2799
+ const mimeType = RECORDING_CANDIDATES.find((candidate) => MediaRecorder.isTypeSupported(candidate));
2800
+ if (!mimeType) {
2801
+ setError("Audio recording is not supported in this browser.");
2802
+ return;
2803
+ }
2804
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
2805
+ onStreamChangeRef.current?.(stream);
2806
+ const recorder = new MediaRecorder(stream, { mimeType });
2807
+ const chunks = [];
2808
+ recorder.ondataavailable = (event) => {
2809
+ if (event.data.size) chunks.push(event.data);
2810
+ };
2811
+ recorder.onstop = () => {
2812
+ const actualType = recorder.mimeType || "audio/webm";
2813
+ onRecordedRef.current(new File([new Blob(chunks, { type: actualType })], `recording-${Date.now()}.${recordingExtension(actualType)}`, { type: actualType }));
2814
+ stream.getTracks().forEach((track) => track.stop());
2815
+ onStreamChangeRef.current?.(null);
2816
+ recorderRef.current = null;
2817
+ streamRef.current = null;
2818
+ setIsRecording(false);
2819
+ };
2820
+ recorderRef.current = recorder;
2821
+ streamRef.current = stream;
2822
+ recorder.start();
2823
+ setIsRecording(true);
2824
+ } catch {
2825
+ streamRef.current?.getTracks().forEach((track) => track.stop());
2826
+ onStreamChangeRef.current?.(null);
2827
+ setError("Microphone access is unavailable.");
2828
+ }
2829
+ }, []);
2830
+ React10.useEffect(() => () => {
2831
+ recorderRef.current?.stop();
2832
+ streamRef.current?.getTracks().forEach((track) => track.stop());
2833
+ onStreamChangeRef.current?.(null);
2834
+ }, []);
2835
+ return { error, isRecording, start, stop };
2836
+ }
1735
2837
  // Annotate the CommonJS export names for ESM import in node:
1736
2838
  0 && (module.exports = {
2839
+ AgentChatActivityRow,
1737
2840
  AgentChatComposer,
2841
+ AgentChatContextCompressionRow,
2842
+ AgentChatContextStatus,
1738
2843
  AgentChatDefaultActions,
1739
2844
  AgentChatMessage,
1740
- AgentChatPartRow,
2845
+ AgentChatModelTurnRow,
2846
+ AgentChatNodeActivityRow,
2847
+ AgentChatPart,
2848
+ AgentChatSubagentGroup,
2849
+ AgentChatToolGroup,
2850
+ AgentChatToolPart,
2851
+ AgentChatToolRow,
2852
+ AgentChatToolSession,
1741
2853
  AgentChatUserMessageContent,
1742
2854
  AgentResponseTimeline,
2855
+ AskUserInteraction,
1743
2856
  Attachment,
1744
2857
  AttachmentAction,
1745
2858
  AttachmentActions,
@@ -1776,14 +2889,21 @@ function McpConnectRequiredCard({
1776
2889
  MessageResponse,
1777
2890
  ShadcnMessage,
1778
2891
  ShadcnMessageContent,
2892
+ agentChatToolLabel,
1779
2893
  attachmentMediaVariants,
1780
2894
  attachmentVariants,
1781
2895
  bubbleVariants,
1782
2896
  cn,
2897
+ contextStatusMetrics,
1783
2898
  hitlActionLabel,
1784
2899
  hitlTitle,
2900
+ isAgentChatSubagent,
2901
+ isAgentChatToolRunning,
2902
+ latestRunningAgentChatToolId,
1785
2903
  markerVariants,
2904
+ partitionAgentChatToolTimeline,
1786
2905
  resolvedHitlLabel,
1787
- shouldCollapseUserMessage
2906
+ shouldCollapseUserMessage,
2907
+ useAudioRecorder
1788
2908
  });
1789
2909
  //# sourceMappingURL=index.cjs.map