@agents24/chat-react 0.5.29 → 0.5.31

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 (52) hide show
  1. package/README.md +28 -1
  2. package/dist/chunk-INO5DSSJ.js +86 -0
  3. package/dist/chunk-INO5DSSJ.js.map +1 -0
  4. package/dist/generated/delegation-types.d.ts +129 -0
  5. package/dist/registry/agents24-chat.json +267 -0
  6. package/dist/runtime/index.cjs +53 -1
  7. package/dist/runtime/index.cjs.map +1 -1
  8. package/dist/runtime/index.d.ts +2 -0
  9. package/dist/runtime/index.js +62 -78
  10. package/dist/runtime/index.js.map +1 -1
  11. package/dist/runtime/input-contract.d.ts +3 -1
  12. package/dist/runtime/use-delegation-tasks.d.ts +20 -0
  13. package/dist/scaffold/app.d.ts +3 -0
  14. package/dist/scaffold/components/chat/chat-composer.d.ts +5 -3
  15. package/dist/scaffold/components/chat/subagent-tasks.d.ts +3 -0
  16. package/dist/scaffold/components/chat/theme-menu.d.ts +1 -1
  17. package/dist/scaffold/components/theme-provider.d.ts +3 -1
  18. package/dist/scaffold/index.cjs +420 -333
  19. package/dist/scaffold/index.cjs.map +1 -1
  20. package/dist/scaffold/index.d.ts +5 -2
  21. package/dist/scaffold/index.js +415 -332
  22. package/dist/scaffold/index.js.map +1 -1
  23. package/dist/styles.css +14 -0
  24. package/dist/ui/adapters.d.ts +5 -2
  25. package/dist/ui/agent-chat-composer.d.ts +8 -2
  26. package/dist/ui/agent-chat-queue.d.ts +0 -2
  27. package/dist/ui/index.cjs +817 -623
  28. package/dist/ui/index.cjs.map +1 -1
  29. package/dist/ui/index.d.ts +1 -0
  30. package/dist/ui/index.js +776 -622
  31. package/dist/ui/index.js.map +1 -1
  32. package/dist/ui/tool-sessions.d.ts +10 -2
  33. package/dist/ui/use-agent-chat-composer.d.ts +47 -0
  34. package/dist/ui/use-audio-recorder.d.ts +5 -3
  35. package/package.json +11 -6
  36. package/scaffold/host-styles.css +3 -0
  37. package/scaffold/inherit.css +4 -0
  38. package/scaffold/manifest.json +2 -2
  39. package/scaffold/src/app.tsx +5 -0
  40. package/scaffold/src/chat.css +35 -1
  41. package/scaffold/src/components/chat/chat-composer.tsx +31 -97
  42. package/scaffold/src/components/chat/chat-shell.tsx +7 -5
  43. package/scaffold/src/components/chat/external-link-dialog.tsx +6 -5
  44. package/scaffold/src/components/chat/identity-control.tsx +0 -1
  45. package/scaffold/src/components/chat/subagent-tasks.tsx +109 -0
  46. package/scaffold/src/components/chat/theme-menu.tsx +3 -1
  47. package/scaffold/src/components/theme-provider.tsx +28 -23
  48. package/scaffold/src/components/ui/tooltip.tsx +3 -0
  49. package/scaffold/src/host.css +11 -0
  50. package/scaffold/src/index.css +1 -213
  51. package/scaffold/src/index.tsx +9 -2
  52. package/scaffold/src/theme.css +215 -0
package/dist/ui/index.js CHANGED
@@ -2,10 +2,16 @@ import {
2
2
  getActiveStreamingTextPartId,
3
3
  useStreamingText
4
4
  } from "../chunk-5PWUQTLK.js";
5
+ import {
6
+ attachmentFileAccept,
7
+ canonicalInputMimeType,
8
+ inputMimeTypes,
9
+ validateAgentChatSubmission
10
+ } from "../chunk-INO5DSSJ.js";
5
11
 
6
12
  // src/ui/agent-chat-composer.tsx
7
- import * as React3 from "react";
8
- import { ArrowUp, Loader2, Plus, Square } from "lucide-react";
13
+ import * as React6 from "react";
14
+ import { ArrowUp, Loader2, Mic, Plus, Square } from "lucide-react";
9
15
 
10
16
  // src/ui/adapters.ts
11
17
  import * as React from "react";
@@ -483,6 +489,7 @@ function attachmentRenderKey(data, index, rowKind) {
483
489
  function ChatImageAttachment({
484
490
  className,
485
491
  data,
492
+ presentation = "message",
486
493
  onRemove,
487
494
  resolveContent,
488
495
  ...props
@@ -562,7 +569,7 @@ function ChatImageAttachment({
562
569
  Attachment,
563
570
  {
564
571
  ...props,
565
- className: cn("a24-chat-image-attachment", className),
572
+ className: cn("a24-chat-image-attachment", presentation === "composer" && "a24-composer-attachment-card", className),
566
573
  orientation: "vertical",
567
574
  ref: rootRef,
568
575
  state: status === "error" ? "error" : status === "done" ? "done" : "processing",
@@ -585,6 +592,7 @@ function ChatImageAttachment({
585
592
  status !== "done" && status !== "error" ? React.createElement("span", { className: "sr-only", role: "status" }, `Loading ${filename}`) : null,
586
593
  status === "error" ? React.createElement("span", { className: "sr-only" }, `${filename} preview unavailable`) : null
587
594
  ),
595
+ presentation === "composer" ? React.createElement("span", { className: "a24-composer-image-caption", title: filename }, filename) : null,
588
596
  onRemove ? React.createElement(
589
597
  "button",
590
598
  {
@@ -603,6 +611,7 @@ function ChatImageAttachment({
603
611
  function ChatAttachment({
604
612
  className,
605
613
  data,
614
+ presentation = "message",
606
615
  onRemove,
607
616
  orientation,
608
617
  resolveContent,
@@ -615,6 +624,7 @@ function ChatAttachment({
615
624
  ...props,
616
625
  className,
617
626
  data,
627
+ presentation,
618
628
  onRemove,
619
629
  resolveContent
620
630
  });
@@ -622,11 +632,16 @@ function ChatAttachment({
622
632
  return React.createElement(
623
633
  Attachment,
624
634
  {
625
- className: cn("a24-chat-attachment", className),
635
+ className: cn("a24-chat-attachment", presentation === "composer" && "a24-composer-attachment-card", className),
626
636
  orientation: orientation || "horizontal",
627
637
  size: "sm",
628
638
  ...props
629
639
  },
640
+ presentation === "composer" ? React.createElement(
641
+ "div",
642
+ { className: "a24-composer-file-preview", "aria-hidden": true },
643
+ React.createElement(FileIcon, { size: 64, type: fileIconType, variant: "solid" })
644
+ ) : null,
630
645
  React.createElement(
631
646
  AttachmentMedia,
632
647
  Object.assign(
@@ -684,6 +699,7 @@ function ChatAttachments({
684
699
  }
685
700
  function ChatAttachmentRows({
686
701
  attachments,
702
+ layout = "message",
687
703
  className,
688
704
  dir,
689
705
  onRemove,
@@ -699,6 +715,7 @@ function ChatAttachmentRows({
699
715
  items.map(
700
716
  (attachment, index) => React.createElement(ChatAttachment, {
701
717
  data: attachment,
718
+ presentation: layout,
702
719
  dir: "ltr",
703
720
  key: attachmentRenderKey(attachment, index, kind),
704
721
  onRemove: onRemove ? () => onRemove(attachment) : void 0,
@@ -709,15 +726,18 @@ function ChatAttachmentRows({
709
726
  return React.createElement(
710
727
  "div",
711
728
  {
712
- className: cn("a24-chat-attachment-rows", className),
729
+ className: cn("a24-chat-attachment-rows", layout === "composer" && "a24-composer-attachment-row", className),
713
730
  dir,
714
731
  ...props
715
732
  },
716
- renderRow(imageAttachments, "images"),
717
- renderRow(fileAttachments, "files")
733
+ layout === "composer" ? renderRow(attachments, "mixed") : renderRow(imageAttachments, "images"),
734
+ layout === "message" ? renderRow(fileAttachments, "files") : null
718
735
  );
719
736
  }
720
737
 
738
+ // src/ui/use-agent-chat-composer.ts
739
+ import * as React4 from "react";
740
+
721
741
  // src/ui/composer-attachments.ts
722
742
  import { normalizeAttachmentMimeType } from "@agents24/client";
723
743
  var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
@@ -779,8 +799,317 @@ function useChatComposerFocus({ textareaRef, disabled, focusKey, restoredDraft,
779
799
  return focus;
780
800
  }
781
801
 
782
- // src/ui/agent-chat-composer.tsx
802
+ // src/ui/use-audio-recorder.ts
803
+ import * as React3 from "react";
804
+ var RECORDING_CANDIDATES = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4"];
805
+ function useAudioRecorder(onRecorded, options = {}) {
806
+ const recorderRef = React3.useRef(null);
807
+ const streamRef = React3.useRef(null);
808
+ const pendingRef = React3.useRef(false);
809
+ const generationRef = React3.useRef(0);
810
+ const onRecordedRef = React3.useRef(onRecorded);
811
+ const onStreamChangeRef = React3.useRef(options.onStreamChange);
812
+ React3.useEffect(() => {
813
+ onRecordedRef.current = onRecorded;
814
+ onStreamChangeRef.current = options.onStreamChange;
815
+ });
816
+ const [isRecording, setIsRecording] = React3.useState(false);
817
+ const [error, setError] = React3.useState(null);
818
+ const release = React3.useCallback(() => {
819
+ streamRef.current?.getTracks().forEach((track) => track.stop());
820
+ streamRef.current = null;
821
+ recorderRef.current = null;
822
+ onStreamChangeRef.current?.(null);
823
+ }, []);
824
+ const stop = React3.useCallback(() => {
825
+ const recorder = recorderRef.current;
826
+ if (recorder && recorder.state !== "inactive") recorder.stop();
827
+ }, []);
828
+ const start = React3.useCallback(async () => {
829
+ if (pendingRef.current || recorderRef.current) return;
830
+ setError(null);
831
+ if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === "undefined") {
832
+ setError("Audio recording is not supported in this browser.");
833
+ return;
834
+ }
835
+ pendingRef.current = true;
836
+ const generation = generationRef.current;
837
+ try {
838
+ const mimeType = RECORDING_CANDIDATES.find((candidate) => MediaRecorder.isTypeSupported(candidate));
839
+ if (!mimeType) {
840
+ setError("Audio recording is not supported in this browser.");
841
+ return;
842
+ }
843
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
844
+ if (generation !== generationRef.current) {
845
+ stream.getTracks().forEach((track) => track.stop());
846
+ return;
847
+ }
848
+ streamRef.current = stream;
849
+ const recorder = new MediaRecorder(stream, { mimeType });
850
+ recorderRef.current = recorder;
851
+ const chunks = [];
852
+ recorder.ondataavailable = (event) => {
853
+ if (event.data.size) chunks.push(event.data);
854
+ };
855
+ recorder.onstop = () => {
856
+ if (generation !== generationRef.current) return;
857
+ const actualType = recorder.mimeType || mimeType;
858
+ const extension = actualType.startsWith("audio/mp4") ? "m4a" : "webm";
859
+ try {
860
+ onRecordedRef.current(new File([new Blob(chunks, { type: actualType })], `recording-${Date.now()}.${extension}`, { type: actualType }));
861
+ } finally {
862
+ release();
863
+ setIsRecording(false);
864
+ }
865
+ };
866
+ recorder.onerror = () => {
867
+ recorder.onstop = null;
868
+ if (recorder.state !== "inactive") recorder.stop();
869
+ release();
870
+ setIsRecording(false);
871
+ setError("Audio recording failed. Try again.");
872
+ };
873
+ recorder.start();
874
+ setIsRecording(true);
875
+ onStreamChangeRef.current?.(stream);
876
+ } catch {
877
+ if (generation === generationRef.current) {
878
+ release();
879
+ setIsRecording(false);
880
+ setError("Microphone access is unavailable.");
881
+ }
882
+ } finally {
883
+ if (generation === generationRef.current) pendingRef.current = false;
884
+ }
885
+ }, [release]);
886
+ React3.useEffect(() => () => {
887
+ generationRef.current += 1;
888
+ pendingRef.current = false;
889
+ const recorder = recorderRef.current;
890
+ if (recorder) {
891
+ recorder.onstop = null;
892
+ recorder.onerror = null;
893
+ if (recorder.state !== "inactive") recorder.stop();
894
+ }
895
+ release();
896
+ }, [release]);
897
+ return { error, isRecording, start, stop };
898
+ }
899
+
900
+ // src/ui/use-agent-chat-composer.ts
901
+ function useAgentChatComposer(options) {
902
+ const [text, setTextState] = React4.useState("");
903
+ const [files, setFiles] = React4.useState([]);
904
+ const [attachmentError, setAttachmentError] = React4.useState(null);
905
+ const [isSubmitting, setIsSubmitting] = React4.useState(false);
906
+ const localTextareaRef = React4.useRef(null);
907
+ const textareaRef = options.textareaRef ?? localTextareaRef;
908
+ const filesRef = React4.useRef(files);
909
+ const inFlightFilesRef = React4.useRef([]);
910
+ const revisionRef = React4.useRef(0);
911
+ const submittingRef = React4.useRef(false);
912
+ React4.useEffect(() => {
913
+ filesRef.current = files;
914
+ }, [files]);
915
+ React4.useEffect(() => () => {
916
+ revokeAgentChatComposerFiles(filesRef.current);
917
+ revokeAgentChatComposerFiles(inFlightFilesRef.current);
918
+ }, []);
919
+ const focusComposer = useChatComposerFocus({
920
+ textareaRef,
921
+ disabled: Boolean(options.disabled),
922
+ focusKey: options.focusKey,
923
+ restoredDraft: options.restoredDraft,
924
+ onRestore: (draft) => {
925
+ revisionRef.current += 1;
926
+ revokeAgentChatComposerFiles(filesRef.current);
927
+ setTextState(draft.text);
928
+ setFiles(draft.files);
929
+ }
930
+ });
931
+ const attachmentMimeTypes = inputMimeTypes(options.inputContract);
932
+ const setText = (value) => {
933
+ revisionRef.current += 1;
934
+ setTextState(value);
935
+ };
936
+ const addFiles = (selected) => {
937
+ const accepted = selected.filter((file) => attachmentMimeTypes.includes(canonicalInputMimeType(file.type, file.name)));
938
+ revisionRef.current += 1;
939
+ const additions = accepted.map(createAgentChatComposerFile);
940
+ setFiles((current) => [...current, ...additions]);
941
+ setAttachmentError(accepted.length === selected.length ? null : "One or more selected file types are not supported by this Agent.");
942
+ };
943
+ const removeFile = (id) => {
944
+ revisionRef.current += 1;
945
+ const removed = filesRef.current.find((file) => file.id === id);
946
+ if (removed) revokeAgentChatComposerFiles([removed]);
947
+ setFiles((current) => current.filter((file) => file.id !== id));
948
+ };
949
+ const recorder = useAudioRecorder((file) => addFiles([file]), { onStreamChange: options.onRecordingStreamChange });
950
+ const validationError = options.inputContract ? validateAgentChatSubmission(
951
+ options.inputContract,
952
+ text,
953
+ files.map((file) => ({ data: file.source || new Uint8Array(), mediaType: file.mediaType, name: file.filename }))
954
+ ) : null;
955
+ const canSubmit = Boolean(text.trim() || files.length) && !options.disabled && !options.isCancelling && !isSubmitting && !recorder.isRecording && !validationError;
956
+ const submit = async () => {
957
+ if (!canSubmit || submittingRef.current) return;
958
+ const value = text;
959
+ const selectedFiles = files;
960
+ const clearedRevision = ++revisionRef.current;
961
+ submittingRef.current = true;
962
+ setIsSubmitting(true);
963
+ inFlightFilesRef.current = selectedFiles;
964
+ setTextState("");
965
+ setFiles([]);
966
+ focusComposer();
967
+ try {
968
+ await options.onSubmit(value, selectedFiles);
969
+ revokeAgentChatComposerFiles(selectedFiles);
970
+ } catch {
971
+ if (revisionRef.current === clearedRevision) {
972
+ revisionRef.current += 1;
973
+ setTextState(value);
974
+ setFiles(selectedFiles);
975
+ } else revokeAgentChatComposerFiles(selectedFiles);
976
+ } finally {
977
+ inFlightFilesRef.current = [];
978
+ submittingRef.current = false;
979
+ setIsSubmitting(false);
980
+ }
981
+ };
982
+ return {
983
+ text,
984
+ setText,
985
+ files,
986
+ addFiles,
987
+ removeFile,
988
+ textareaRef,
989
+ submit,
990
+ canSubmit,
991
+ isSubmitting,
992
+ attachmentError,
993
+ attachmentMimeTypes,
994
+ validationError,
995
+ recorder,
996
+ recordingAllowed: options.recordingEnabled ?? options.inputContract?.modalities.audio.recording_enabled ?? false,
997
+ fileAccept: attachmentFileAccept(attachmentMimeTypes),
998
+ onKeyDown: (event) => {
999
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
1000
+ event.preventDefault();
1001
+ void submit();
1002
+ }
1003
+ },
1004
+ allowAttachments: attachmentMimeTypes.length > 0,
1005
+ isExpanded: Boolean(options.forceExpanded || text.includes("\n") || text.length > 62),
1006
+ action: chatComposerAction({ hasDraft: Boolean(text.trim() || files.length), isRunning: Boolean(options.isRunning), isCancelling: Boolean(options.isCancelling) })
1007
+ };
1008
+ }
1009
+
1010
+ // src/ui/agent-chat-context-status.tsx
1011
+ import * as React5 from "react";
783
1012
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
1013
+ var COMPACT_TOKENS = new Intl.NumberFormat("en-US", { notation: "compact" });
1014
+ var PERCENT = new Intl.NumberFormat("en-US", {
1015
+ style: "percent",
1016
+ maximumFractionDigits: 1
1017
+ });
1018
+ var EXACT_SOURCES = /* @__PURE__ */ new Set(["exact", "provider_usage"]);
1019
+ function contextStatusMetrics(contextWindow) {
1020
+ const maximum = Number(contextWindow?.max_tokens || 0);
1021
+ const input = Math.max(0, Number(contextWindow?.input_tokens || 0));
1022
+ const admissionInput = Math.max(0, Number(contextWindow?.admission_input_tokens ?? input));
1023
+ const usageRatio = Math.max(0, Math.min(1, Number(
1024
+ contextWindow?.usage_ratio ?? input / maximum
1025
+ )));
1026
+ const admissionRatio = Math.max(0, Math.min(1, Number(
1027
+ contextWindow?.admission_ratio ?? admissionInput / maximum
1028
+ )));
1029
+ return { maximum, input, admissionInput, usageRatio, admissionRatio };
1030
+ }
1031
+ function AgentChatContextStatus({
1032
+ className,
1033
+ contextWindow
1034
+ }) {
1035
+ const tooltipId = React5.useId();
1036
+ const [open, setOpen] = React5.useState(false);
1037
+ const { maximum, input, admissionInput, usageRatio, admissionRatio } = contextStatusMetrics(contextWindow);
1038
+ const percentage = Math.round(usageRatio * 100);
1039
+ const exact = EXACT_SOURCES.has(contextWindow?.source || "unknown");
1040
+ if (!Number.isFinite(maximum) || maximum <= 0) return null;
1041
+ return /* @__PURE__ */ jsxs(
1042
+ "div",
1043
+ {
1044
+ className: cn("a24-context-status", className),
1045
+ onBlurCapture: (event) => {
1046
+ if (!event.currentTarget.contains(event.relatedTarget)) setOpen(false);
1047
+ },
1048
+ onFocusCapture: () => setOpen(true),
1049
+ onMouseEnter: () => setOpen(true),
1050
+ onMouseLeave: () => setOpen(false),
1051
+ children: [
1052
+ /* @__PURE__ */ jsxs(
1053
+ "button",
1054
+ {
1055
+ "aria-describedby": open ? tooltipId : void 0,
1056
+ "aria-label": `Context window ${percentage}% used`,
1057
+ className: "a24-context-status__trigger",
1058
+ onKeyDown: (event) => {
1059
+ if (event.key === "Escape") setOpen(false);
1060
+ },
1061
+ type: "button",
1062
+ children: [
1063
+ /* @__PURE__ */ jsxs("svg", { "aria-hidden": "true", className: "a24-context-status__ring", viewBox: "0 0 24 24", children: [
1064
+ /* @__PURE__ */ jsx3("circle", { className: "a24-context-status__track", cx: "12", cy: "12", r: "10" }),
1065
+ /* @__PURE__ */ jsx3(
1066
+ "circle",
1067
+ {
1068
+ className: "a24-context-status__value",
1069
+ cx: "12",
1070
+ cy: "12",
1071
+ pathLength: "100",
1072
+ r: "10",
1073
+ strokeDasharray: `${percentage} 100`
1074
+ }
1075
+ )
1076
+ ] }),
1077
+ /* @__PURE__ */ jsxs("span", { children: [
1078
+ percentage,
1079
+ "%"
1080
+ ] })
1081
+ ]
1082
+ }
1083
+ ),
1084
+ open ? /* @__PURE__ */ jsxs("div", { className: "a24-context-status__popover", id: tooltipId, role: "tooltip", children: [
1085
+ /* @__PURE__ */ jsx3("span", { className: "a24-context-status__sr-only", children: "Context window" }),
1086
+ /* @__PURE__ */ jsxs("strong", { children: [
1087
+ PERCENT.format(usageRatio),
1088
+ " context used"
1089
+ ] }),
1090
+ /* @__PURE__ */ jsxs("span", { children: [
1091
+ exact ? "Input" : "Estimated input",
1092
+ ": ",
1093
+ COMPACT_TOKENS.format(input),
1094
+ " / ",
1095
+ COMPACT_TOKENS.format(maximum),
1096
+ " tokens"
1097
+ ] }),
1098
+ admissionInput !== input ? /* @__PURE__ */ jsxs("span", { children: [
1099
+ "Safety bound: ",
1100
+ COMPACT_TOKENS.format(admissionInput),
1101
+ " tokens (",
1102
+ PERCENT.format(admissionRatio),
1103
+ ")"
1104
+ ] }) : null
1105
+ ] }) : null
1106
+ ]
1107
+ }
1108
+ );
1109
+ }
1110
+
1111
+ // src/ui/agent-chat-composer.tsx
1112
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
784
1113
  function formatFileCount(files) {
785
1114
  if (files.length === 1) return files[0].filename;
786
1115
  return `${files.length} files`;
@@ -788,9 +1117,11 @@ function formatFileCount(files) {
788
1117
  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";
789
1118
  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";
790
1119
  function AgentChatComposer({
1120
+ inputContract,
1121
+ recordingEnabled,
1122
+ contextWindow,
791
1123
  accept,
792
1124
  allowAttachments = true,
793
- attachmentLayout = "flow",
794
1125
  className,
795
1126
  disabled = false,
796
1127
  forceExpanded = false,
@@ -805,16 +1136,22 @@ function AgentChatComposer({
805
1136
  restoredDraft,
806
1137
  textareaRef
807
1138
  }) {
808
- const fileInputRef = React3.useRef(null);
809
- const filesRef = React3.useRef([]);
810
- const inFlightFilesRef = React3.useRef([]);
811
- const localTextareaRef = React3.useRef(null);
812
- const draftRevisionRef = React3.useRef(0);
813
- const [text, setText] = React3.useState("");
814
- const [files, setFiles] = React3.useState([]);
815
- const [isSubmitting, setIsSubmitting] = React3.useState(false);
1139
+ const fileInputRef = React6.useRef(null);
1140
+ const localTextareaRef = React6.useRef(null);
1141
+ const composer = useAgentChatComposer({
1142
+ inputContract,
1143
+ recordingEnabled,
1144
+ disabled,
1145
+ isRunning,
1146
+ isCancelling,
1147
+ focusKey,
1148
+ restoredDraft,
1149
+ textareaRef: localTextareaRef,
1150
+ onSubmit: (text2, files2) => onSubmit({ text: text2.trim(), files: files2, attachmentIds: files2.flatMap((file) => file.id ? [file.id] : []) })
1151
+ });
1152
+ const { text, files, setText, isSubmitting, canSubmit, action, submit } = composer;
816
1153
  const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
817
- const setTextareaRef = React3.useCallback(
1154
+ const setTextareaRef = React6.useCallback(
818
1155
  (node) => {
819
1156
  localTextareaRef.current = node;
820
1157
  if (typeof textareaRef === "function") {
@@ -825,27 +1162,10 @@ function AgentChatComposer({
825
1162
  },
826
1163
  [textareaRef]
827
1164
  );
828
- React3.useEffect(() => {
829
- filesRef.current = files;
1165
+ React6.useEffect(() => {
830
1166
  onAttachmentCountChange?.(files.length);
831
1167
  }, [files, onAttachmentCountChange]);
832
- React3.useEffect(() => () => {
833
- revokeAgentChatComposerFiles(filesRef.current);
834
- revokeAgentChatComposerFiles(inFlightFilesRef.current);
835
- }, []);
836
- const focusComposer = useChatComposerFocus({
837
- textareaRef: localTextareaRef,
838
- disabled,
839
- focusKey,
840
- restoredDraft,
841
- onRestore: (draft) => {
842
- draftRevisionRef.current += 1;
843
- revokeAgentChatComposerFiles(filesRef.current);
844
- setText(draft.text);
845
- setFiles(draft.files);
846
- }
847
- });
848
- React3.useLayoutEffect(() => {
1168
+ React6.useLayoutEffect(() => {
849
1169
  const textarea = localTextareaRef.current;
850
1170
  if (!textarea) return;
851
1171
  if (isExpanded) {
@@ -858,83 +1178,13 @@ function AgentChatComposer({
858
1178
  textarea.style.height = "";
859
1179
  textarea.style.overflowY = "hidden";
860
1180
  }, [isExpanded, text]);
861
- const canSubmit = !disabled && !isCancelling && !isSubmitting && (text.trim().length > 0 || files.length > 0);
862
- const action = chatComposerAction({ hasDraft: text.trim().length > 0 || files.length > 0, isRunning, isCancelling });
863
1181
  const handleFilesChange = (event) => {
864
- const selected = Array.from(event.target.files || []).map(createAgentChatComposerFile);
865
- if (selected.length > 0) {
866
- draftRevisionRef.current += 1;
867
- setFiles((current) => [...current, ...selected]);
868
- }
1182
+ composer.addFiles(Array.from(event.target.files || []));
869
1183
  event.currentTarget.value = "";
870
1184
  };
871
- const removeFile = (fileId) => {
872
- draftRevisionRef.current += 1;
873
- setFiles((current) => {
874
- const target = current.find((file) => file.id === fileId);
875
- if (target) revokeAgentChatComposerFiles([target]);
876
- return current.filter((file) => file.id !== fileId);
877
- });
878
- };
879
- const submit = async () => {
880
- if (!canSubmit) return;
881
- const submittedText = text.trim();
882
- const submittedFiles = files;
883
- const clearedRevision = draftRevisionRef.current + 1;
884
- draftRevisionRef.current = clearedRevision;
885
- inFlightFilesRef.current = submittedFiles;
886
- setIsSubmitting(true);
887
- setText("");
888
- setFiles([]);
889
- focusComposer();
890
- try {
891
- await onSubmit({
892
- text: submittedText,
893
- files: submittedFiles,
894
- attachmentIds: submittedFiles.flatMap((file) => file.persistedAttachmentId ? [file.persistedAttachmentId] : [])
895
- });
896
- revokeAgentChatComposerFiles(submittedFiles);
897
- inFlightFilesRef.current = [];
898
- } catch {
899
- if (draftRevisionRef.current === clearedRevision) {
900
- draftRevisionRef.current += 1;
901
- setText(submittedText);
902
- setFiles(submittedFiles);
903
- inFlightFilesRef.current = [];
904
- } else {
905
- revokeAgentChatComposerFiles(submittedFiles);
906
- inFlightFilesRef.current = [];
907
- }
908
- } finally {
909
- setIsSubmitting(false);
910
- }
911
- };
912
- const handleKeyDown = (event) => {
913
- if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
914
- event.preventDefault();
915
- void submit();
916
- }
917
- };
918
- return /* @__PURE__ */ jsxs("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), "data-agents24-chat-composer": "", children: [
919
- files.length > 0 ? /* @__PURE__ */ jsx3(
920
- "div",
921
- {
922
- "data-testid": "agent-chat-composer-attachments",
923
- "data-layout": attachmentLayout,
924
- className: cn(
925
- "a24-composer__attachments min-w-0 py-1",
926
- attachmentLayout === "overlay" ? "absolute bottom-full left-0 right-0 mb-1.5 flex-nowrap overflow-x-auto overflow-y-hidden overscroll-x-contain px-2" : "mb-2 flex-wrap px-1"
927
- ),
928
- children: /* @__PURE__ */ jsx3(
929
- ChatAttachmentRows,
930
- {
931
- attachments: files,
932
- onRemove: (attachment) => attachment.id && removeFile(attachment.id)
933
- }
934
- )
935
- }
936
- ) : null,
937
- /* @__PURE__ */ jsx3(
1185
+ return /* @__PURE__ */ jsxs2("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), "data-agents24-chat-composer": "", children: [
1186
+ composer.attachmentError || composer.recorder.error ? /* @__PURE__ */ jsx4("p", { role: "alert", children: composer.attachmentError || composer.recorder.error }) : null,
1187
+ /* @__PURE__ */ jsxs2(
938
1188
  "div",
939
1189
  {
940
1190
  "data-expanded": isExpanded ? "" : void 0,
@@ -942,101 +1192,142 @@ function AgentChatComposer({
942
1192
  "a24-composer__surface relative w-full border border-input bg-background shadow-sm",
943
1193
  isExpanded ? "rounded-xl p-2 pb-1.5" : "rounded-xl px-2 py-[3px]"
944
1194
  ),
945
- children: /* @__PURE__ */ jsxs(
946
- "div",
947
- {
948
- className: "a24-composer__grid w-full",
949
- style: {
950
- alignItems: "center",
951
- display: "grid",
952
- gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
953
- gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
954
- gap: isExpanded ? "3px 10px" : "8px"
955
- },
956
- children: [
957
- /* @__PURE__ */ jsxs("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
958
- /* @__PURE__ */ jsx3(
959
- "input",
1195
+ children: [
1196
+ files.length > 0 ? /* @__PURE__ */ jsx4(
1197
+ ChatAttachmentRows,
1198
+ {
1199
+ "data-testid": "agent-chat-composer-attachments",
1200
+ layout: "composer",
1201
+ attachments: files,
1202
+ onRemove: (attachment) => attachment.id && composer.removeFile(attachment.id)
1203
+ }
1204
+ ) : null,
1205
+ /* @__PURE__ */ jsxs2(
1206
+ "div",
1207
+ {
1208
+ className: "a24-composer__grid w-full",
1209
+ style: {
1210
+ alignItems: "center",
1211
+ display: "grid",
1212
+ gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
1213
+ gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
1214
+ gap: isExpanded ? "3px 10px" : "8px"
1215
+ },
1216
+ children: [
1217
+ /* @__PURE__ */ jsxs2("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
1218
+ /* @__PURE__ */ jsx4(
1219
+ "input",
1220
+ {
1221
+ ref: fileInputRef,
1222
+ accept: composer.fileAccept ?? accept,
1223
+ "aria-label": "Attach files",
1224
+ className: "hidden",
1225
+ multiple: true,
1226
+ name: "attachments",
1227
+ onChange: handleFilesChange,
1228
+ type: "file"
1229
+ }
1230
+ ),
1231
+ /* @__PURE__ */ jsx4(
1232
+ "button",
1233
+ {
1234
+ "aria-label": "Attach files",
1235
+ className: cn("a24-composer__attach", pillButtonClass),
1236
+ disabled: disabled || isSubmitting || !allowAttachments || !composer.allowAttachments,
1237
+ onClick: () => fileInputRef.current?.click(),
1238
+ title: "Attach files",
1239
+ type: "button",
1240
+ children: /* @__PURE__ */ jsx4(Plus, { className: "size-4" })
1241
+ }
1242
+ )
1243
+ ] }),
1244
+ /* @__PURE__ */ jsx4("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ jsx4(
1245
+ "textarea",
960
1246
  {
961
- ref: fileInputRef,
962
- accept,
963
- "aria-label": "Attach files",
964
- className: "hidden",
965
- multiple: true,
966
- name: "attachments",
967
- onChange: handleFilesChange,
968
- type: "file"
1247
+ ref: setTextareaRef,
1248
+ "aria-label": "Message",
1249
+ autoComplete: "off",
1250
+ className: cn(
1251
+ "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",
1252
+ isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
1253
+ ),
1254
+ disabled,
1255
+ name: "message",
1256
+ onChange: (event) => setText(event.target.value),
1257
+ onKeyDown: composer.onKeyDown,
1258
+ placeholder,
1259
+ rows: isExpanded ? 2 : 1,
1260
+ value: text
969
1261
  }
970
- ),
971
- /* @__PURE__ */ jsx3(
1262
+ ) }),
1263
+ isExpanded ? /* @__PURE__ */ jsxs2("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: [
1264
+ inputToolbarContent,
1265
+ /* @__PURE__ */ jsx4(AgentChatContextStatus, { contextWindow }),
1266
+ composer.recordingAllowed ? /* @__PURE__ */ jsx4(
1267
+ "button",
1268
+ {
1269
+ type: "button",
1270
+ className: pillButtonClass,
1271
+ "aria-label": composer.recorder.isRecording ? "Stop recording" : "Record audio",
1272
+ disabled: disabled || isRunning || isSubmitting,
1273
+ onClick: () => {
1274
+ if (composer.recorder.isRecording) composer.recorder.stop();
1275
+ else void composer.recorder.start();
1276
+ },
1277
+ children: composer.recorder.isRecording ? /* @__PURE__ */ jsx4(Square, {}) : /* @__PURE__ */ jsx4(Mic, {})
1278
+ }
1279
+ ) : null
1280
+ ] }) : null,
1281
+ !isExpanded ? /* @__PURE__ */ jsx4("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
1282
+ !isExpanded ? /* @__PURE__ */ jsxs2("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end", children: [
1283
+ /* @__PURE__ */ jsx4(AgentChatContextStatus, { contextWindow }),
1284
+ composer.recordingAllowed ? /* @__PURE__ */ jsx4(
1285
+ "button",
1286
+ {
1287
+ type: "button",
1288
+ className: pillButtonClass,
1289
+ "aria-label": composer.recorder.isRecording ? "Stop recording" : "Record audio",
1290
+ disabled: disabled || isRunning || isSubmitting,
1291
+ onClick: () => {
1292
+ if (composer.recorder.isRecording) composer.recorder.stop();
1293
+ else void composer.recorder.start();
1294
+ },
1295
+ children: composer.recorder.isRecording ? /* @__PURE__ */ jsx4(Square, {}) : /* @__PURE__ */ jsx4(Mic, {})
1296
+ }
1297
+ ) : null
1298
+ ] }) : null,
1299
+ /* @__PURE__ */ jsx4("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end gap-1", children: /* @__PURE__ */ jsx4(
972
1300
  "button",
973
1301
  {
974
- "aria-label": "Attach files",
975
- className: cn("a24-composer__attach", pillButtonClass),
976
- disabled: disabled || isSubmitting || !allowAttachments,
977
- onClick: () => fileInputRef.current?.click(),
978
- title: "Attach files",
1302
+ "aria-label": isCancelling ? "Cancelling response" : action === "stop" ? "Stop generating" : files.length > 0 ? `Send ${formatFileCount(files)}` : isRunning ? "Queue message" : "Send message",
1303
+ className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
1304
+ disabled: isCancelling || (action === "stop" ? disabled : !canSubmit),
1305
+ onClick: () => {
1306
+ if (action === "stop") onStop?.();
1307
+ else void submit();
1308
+ },
1309
+ title: composer.validationError || (action === "stop" ? "Stop" : isRunning ? "Queue message" : "Send"),
979
1310
  type: "button",
980
- children: /* @__PURE__ */ jsx3(Plus, { className: "size-4" })
1311
+ children: isCancelling ? /* @__PURE__ */ jsx4(Loader2, { className: "size-3.5 animate-spin" }) : action === "stop" ? /* @__PURE__ */ jsx4(Square, { className: "size-3.5 fill-current" }) : /* @__PURE__ */ jsx4(ArrowUp, { className: "size-4", strokeWidth: 2.5 })
981
1312
  }
982
- )
983
- ] }),
984
- /* @__PURE__ */ jsx3("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ jsx3(
985
- "textarea",
986
- {
987
- ref: setTextareaRef,
988
- "aria-label": "Message",
989
- autoComplete: "off",
990
- className: cn(
991
- "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",
992
- isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
993
- ),
994
- disabled,
995
- name: "message",
996
- onChange: (event) => {
997
- draftRevisionRef.current += 1;
998
- setText(event.target.value);
999
- },
1000
- onKeyDown: handleKeyDown,
1001
- placeholder,
1002
- rows: isExpanded ? 2 : 1,
1003
- value: text
1004
- }
1005
- ) }),
1006
- isExpanded ? /* @__PURE__ */ jsx3("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
1007
- !isExpanded ? /* @__PURE__ */ jsx3("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
1008
- !isExpanded ? /* @__PURE__ */ jsx3("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
1009
- /* @__PURE__ */ jsx3("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end gap-1", children: /* @__PURE__ */ jsx3(
1010
- "button",
1011
- {
1012
- "aria-label": isCancelling ? "Cancelling response" : action === "stop" ? "Stop generating" : files.length > 0 ? `Send ${formatFileCount(files)}` : isRunning ? "Queue message" : "Send message",
1013
- className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
1014
- disabled: isCancelling || (action === "stop" ? disabled : !canSubmit),
1015
- onClick: () => {
1016
- if (action === "stop") onStop?.();
1017
- else void submit();
1018
- },
1019
- title: action === "stop" ? "Stop" : isRunning ? "Queue message" : "Send",
1020
- type: "button",
1021
- children: isCancelling ? /* @__PURE__ */ jsx3(Loader2, { className: "size-3.5 animate-spin" }) : action === "stop" ? /* @__PURE__ */ jsx3(Square, { className: "size-3.5 fill-current" }) : /* @__PURE__ */ jsx3(ArrowUp, { className: "size-4", strokeWidth: 2.5 })
1022
- }
1023
- ) })
1024
- ]
1025
- }
1026
- )
1313
+ ) })
1314
+ ]
1315
+ }
1316
+ )
1317
+ ]
1027
1318
  }
1028
1319
  ),
1029
- !isExpanded && inputToolbarContent ? /* @__PURE__ */ jsx3("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
1320
+ !isExpanded && inputToolbarContent ? /* @__PURE__ */ jsx4("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
1030
1321
  ] });
1031
1322
  }
1032
1323
 
1033
1324
  // src/ui/agent-chat-queue.tsx
1034
- import * as React4 from "react";
1325
+ import * as React7 from "react";
1035
1326
  import { CornerDownLeft, Ellipsis, Trash2 } from "lucide-react";
1036
- import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1327
+ import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1037
1328
  function QueueRow({ message, onRemove, onEdit }) {
1038
- const [busy, setBusy] = React4.useState(false);
1039
- const [error, setError] = React4.useState(null);
1329
+ const [busy, setBusy] = React7.useState(false);
1330
+ const [error, setError] = React7.useState(null);
1040
1331
  const editable = message.revision > 0 && (message.status === "queued" || message.status === "blocked");
1041
1332
  const act = async (operation) => {
1042
1333
  setBusy(true);
@@ -1049,14 +1340,14 @@ function QueueRow({ message, onRemove, onEdit }) {
1049
1340
  setBusy(false);
1050
1341
  }
1051
1342
  };
1052
- return /* @__PURE__ */ jsxs2("div", { "data-message-id": message.id, className: "a24-queue__row min-w-0 border-b border-border/40 last:border-b-0", children: [
1053
- /* @__PURE__ */ jsxs2("div", { className: "a24-queue__row-content flex min-h-9 items-center gap-2 px-3 text-sm", children: [
1054
- /* @__PURE__ */ jsx4(CornerDownLeft, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }),
1055
- /* @__PURE__ */ jsx4("span", { className: "a24-queue__text min-w-0 flex-1 truncate text-foreground", title: message.text, children: message.text || `${message.attachment_ids.length} attachment${message.attachment_ids.length === 1 ? "" : "s"}` }),
1056
- message.status === "blocked" ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-xs text-amber-700", children: "Needs attention" }) : null,
1057
- ["steer_pending", "delivering"].includes(message.status) ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-xs text-muted-foreground", children: "Steering\u2026" }) : null,
1058
- editable ? /* @__PURE__ */ jsxs2(Fragment, { children: [
1059
- /* @__PURE__ */ jsx4(
1343
+ return /* @__PURE__ */ jsxs3("div", { "data-message-id": message.id, className: "a24-queue__row min-w-0 border-b border-border/40 last:border-b-0", children: [
1344
+ /* @__PURE__ */ jsxs3("div", { className: "a24-queue__row-content flex min-h-9 items-center gap-2 px-3 text-sm", children: [
1345
+ /* @__PURE__ */ jsx5(CornerDownLeft, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }),
1346
+ /* @__PURE__ */ jsx5("span", { className: "a24-queue__text min-w-0 flex-1 truncate text-foreground", title: message.text, children: message.text || `${message.attachment_ids.length} attachment${message.attachment_ids.length === 1 ? "" : "s"}` }),
1347
+ message.status === "blocked" ? /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-amber-700", children: "Needs attention" }) : null,
1348
+ ["steer_pending", "delivering"].includes(message.status) ? /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-muted-foreground", children: "Sending\u2026" }) : null,
1349
+ editable ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1350
+ /* @__PURE__ */ jsx5(
1060
1351
  "button",
1061
1352
  {
1062
1353
  type: "button",
@@ -1064,12 +1355,12 @@ function QueueRow({ message, onRemove, onEdit }) {
1064
1355
  disabled: busy,
1065
1356
  onClick: () => void act(() => onRemove(message)),
1066
1357
  className: "a24-queue__icon flex size-8 shrink-0 items-center justify-center text-muted-foreground hover:text-foreground disabled:opacity-50",
1067
- children: /* @__PURE__ */ jsx4(Trash2, { "aria-hidden": true, className: "size-4" })
1358
+ children: /* @__PURE__ */ jsx5(Trash2, { "aria-hidden": true, className: "size-4" })
1068
1359
  }
1069
1360
  ),
1070
- /* @__PURE__ */ jsxs2("details", { className: "a24-queue__menu relative group/queue-menu", children: [
1071
- /* @__PURE__ */ jsx4("summary", { "aria-label": "Queued message options", className: "a24-queue__icon flex size-8 cursor-pointer list-none items-center justify-center text-muted-foreground hover:text-foreground [&::-webkit-details-marker]:hidden", children: /* @__PURE__ */ jsx4(Ellipsis, { "aria-hidden": true, className: "size-4" }) }),
1072
- /* @__PURE__ */ jsx4("div", { className: "a24-queue__menu-content mt-1 min-w-28 rounded-md border bg-background p-1", children: /* @__PURE__ */ jsx4(
1361
+ /* @__PURE__ */ jsxs3("details", { className: "a24-queue__menu relative group/queue-menu", children: [
1362
+ /* @__PURE__ */ jsx5("summary", { "aria-label": "Queued message options", className: "a24-queue__icon flex size-8 cursor-pointer list-none items-center justify-center text-muted-foreground hover:text-foreground [&::-webkit-details-marker]:hidden", children: /* @__PURE__ */ jsx5(Ellipsis, { "aria-hidden": true, className: "size-4" }) }),
1363
+ /* @__PURE__ */ jsx5("div", { className: "a24-queue__menu-content mt-1 min-w-28 rounded-md border bg-background p-1", children: /* @__PURE__ */ jsx5(
1073
1364
  "button",
1074
1365
  {
1075
1366
  type: "button",
@@ -1082,29 +1373,29 @@ function QueueRow({ message, onRemove, onEdit }) {
1082
1373
  ] })
1083
1374
  ] }) : null
1084
1375
  ] }),
1085
- error ? /* @__PURE__ */ jsx4("p", { role: "alert", className: "px-9 pb-2 text-xs text-destructive", children: error }) : null
1376
+ error ? /* @__PURE__ */ jsx5("p", { role: "alert", className: "px-9 pb-2 text-xs text-destructive", children: error }) : null
1086
1377
  ] });
1087
1378
  }
1088
1379
  function AgentChatQueue({ pending, queue, onRemove, onEdit, onResume, className }) {
1089
- const [resumeError, setResumeError] = React4.useState(null);
1380
+ const [resumeError, setResumeError] = React7.useState(null);
1090
1381
  if (!pending.length && !queue?.paused) return null;
1091
- return /* @__PURE__ */ jsxs2("section", { "aria-label": "Message queue", className: cn("a24-queue relative z-0 -mb-2 overflow-visible rounded-t-xl border border-b-0 border-border/50 bg-background pb-2", className), children: [
1092
- queue?.paused ? /* @__PURE__ */ jsxs2("div", { className: "flex min-h-9 items-center justify-between gap-2 px-3 text-xs text-muted-foreground", children: [
1093
- /* @__PURE__ */ jsx4("span", { children: "Queue paused" }),
1094
- /* @__PURE__ */ jsx4("button", { type: "button", onClick: () => {
1382
+ return /* @__PURE__ */ jsxs3("section", { "aria-label": "Message queue", className: cn("a24-queue relative z-0 -mb-2 overflow-visible rounded-t-xl border border-b-0 border-border/50 bg-background pb-2", className), children: [
1383
+ queue?.paused ? /* @__PURE__ */ jsxs3("div", { className: "flex min-h-9 items-center justify-between gap-2 px-3 text-xs text-muted-foreground", children: [
1384
+ /* @__PURE__ */ jsx5("span", { children: "Queue paused" }),
1385
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: () => {
1095
1386
  void onResume().then(() => setResumeError(null)).catch((cause) => setResumeError(cause instanceof Error ? cause.message : "Could not resume queue"));
1096
1387
  }, className: "font-medium text-foreground hover:underline", children: "Resume queue" })
1097
1388
  ] }) : null,
1098
- resumeError ? /* @__PURE__ */ jsx4("p", { role: "alert", className: "px-3 pb-2 text-xs text-destructive", children: resumeError }) : null,
1099
- /* @__PURE__ */ jsx4("div", { className: "max-h-40 overflow-y-auto", children: pending.map((message) => /* @__PURE__ */ jsx4(QueueRow, { message, onRemove, onEdit }, message.client_submission_id || message.id)) })
1389
+ resumeError ? /* @__PURE__ */ jsx5("p", { role: "alert", className: "px-3 pb-2 text-xs text-destructive", children: resumeError }) : null,
1390
+ /* @__PURE__ */ jsx5("div", { className: "max-h-40 overflow-y-auto", children: pending.map((message) => /* @__PURE__ */ jsx5(QueueRow, { message, onRemove, onEdit }, message.client_submission_id || message.id)) })
1100
1391
  ] });
1101
1392
  }
1102
1393
 
1103
1394
  // src/ui/agent-chat-actions.tsx
1104
- import * as React5 from "react";
1395
+ import * as React8 from "react";
1105
1396
  import { Check, Copy, RefreshCcw, ThumbsDown, ThumbsUp } from "lucide-react";
1106
1397
  import { Popover } from "radix-ui";
1107
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1398
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1108
1399
  var FEEDBACK_REASONS = [
1109
1400
  ["incorrect", "Incorrect"],
1110
1401
  ["irrelevant", "Irrelevant"],
@@ -1115,8 +1406,8 @@ var FEEDBACK_REASONS = [
1115
1406
  ];
1116
1407
  var enteredCompletionRuns = /* @__PURE__ */ new Set();
1117
1408
  function useLiveCompletionRuns(runId, isRunning) {
1118
- const liveRuns = React5.useRef(/* @__PURE__ */ new Set());
1119
- React5.useEffect(() => {
1409
+ const liveRuns = React8.useRef(/* @__PURE__ */ new Set());
1410
+ React8.useEffect(() => {
1120
1411
  if (!runId || !isRunning) return;
1121
1412
  liveRuns.current.add(runId);
1122
1413
  if (liveRuns.current.size > 256) liveRuns.current.delete(liveRuns.current.values().next().value);
@@ -1124,10 +1415,10 @@ function useLiveCompletionRuns(runId, isRunning) {
1124
1415
  return (candidate) => Boolean(candidate && liveRuns.current.has(candidate));
1125
1416
  }
1126
1417
  function useCompletionActionEntrance(isComplete, runId, wasLive = false) {
1127
- const sawIncomplete = React5.useRef(!isComplete);
1128
- const entered = React5.useRef(false);
1418
+ const sawIncomplete = React8.useRef(!isComplete);
1419
+ const entered = React8.useRef(false);
1129
1420
  const shouldEnter = isComplete && !entered.current && (runId ? wasLive && !enteredCompletionRuns.has(runId) : sawIncomplete.current);
1130
- React5.useEffect(() => {
1421
+ React8.useEffect(() => {
1131
1422
  if (!isComplete) sawIncomplete.current = true;
1132
1423
  else if (shouldEnter) {
1133
1424
  entered.current = true;
@@ -1155,19 +1446,34 @@ function AgentChatDefaultActions({
1155
1446
  handlers,
1156
1447
  message
1157
1448
  }) {
1158
- const [feedbackOpen, setFeedbackOpen] = React5.useState(false);
1159
- const [feedbackTrigger, setFeedbackTrigger] = React5.useState(null);
1160
- const [reason, setReason] = React5.useState(handlers?.feedback?.reason ?? null);
1161
- const [comment, setComment] = React5.useState(handlers?.feedback?.comment ?? "");
1162
- const [optimisticCopied, setOptimisticCopied] = React5.useState(false);
1163
- const copyResetTimer = React5.useRef(null);
1164
- React5.useEffect(() => {
1449
+ const [feedbackOpen, setFeedbackOpen] = React8.useState(false);
1450
+ const [feedbackTrigger, setFeedbackTrigger] = React8.useState(null);
1451
+ const [reason, setReason] = React8.useState(handlers?.feedback?.reason ?? null);
1452
+ const [comment, setComment] = React8.useState(handlers?.feedback?.comment ?? "");
1453
+ const [optimisticCopied, setOptimisticCopied] = React8.useState(false);
1454
+ const copyResetTimer = React8.useRef(null);
1455
+ React8.useEffect(() => {
1165
1456
  setReason(handlers?.feedback?.reason ?? null);
1166
1457
  setComment(handlers?.feedback?.comment ?? "");
1167
1458
  }, [handlers?.feedback?.comment, handlers?.feedback?.reason]);
1168
- React5.useEffect(() => () => {
1459
+ React8.useEffect(() => () => {
1169
1460
  if (copyResetTimer.current !== null) window.clearTimeout(copyResetTimer.current);
1170
1461
  }, []);
1462
+ const [feedbackPending, setFeedbackPending] = React8.useState(false);
1463
+ const feedbackInFlight = React8.useRef(false);
1464
+ const writeFeedback = async (input, close = false) => {
1465
+ if (feedbackInFlight.current) return;
1466
+ feedbackInFlight.current = true;
1467
+ setFeedbackPending(true);
1468
+ try {
1469
+ await handlers?.onSetFeedback?.(message, input);
1470
+ if (close) setFeedbackOpen(false);
1471
+ } catch {
1472
+ } finally {
1473
+ feedbackInFlight.current = false;
1474
+ setFeedbackPending(false);
1475
+ }
1476
+ };
1171
1477
  const copied = handlers?.copied ?? optimisticCopied;
1172
1478
  const copyMessage = () => {
1173
1479
  handlers?.onCopy?.(message.content || "", message.id);
@@ -1182,63 +1488,65 @@ function AgentChatDefaultActions({
1182
1488
  if (!handlers || message.isFinal === false) return null;
1183
1489
  if (message.role === "user") {
1184
1490
  const sentAt = formatSentAt(message.createdAt);
1185
- return /* @__PURE__ */ jsxs3(ChatMessageActions, { className: "a24-user-message-actions", children: [
1186
- sentAt ? /* @__PURE__ */ jsx5("time", { dateTime: sentAt.dateTime, children: sentAt.label }) : null,
1187
- handlers.onCopy ? /* @__PURE__ */ jsx5(
1491
+ return /* @__PURE__ */ jsxs4(ChatMessageActions, { className: "a24-user-message-actions", children: [
1492
+ sentAt ? /* @__PURE__ */ jsx6("time", { dateTime: sentAt.dateTime, children: sentAt.label }) : null,
1493
+ handlers.onCopy ? /* @__PURE__ */ jsx6(
1188
1494
  ChatMessageAction,
1189
1495
  {
1190
1496
  label: "Copy",
1191
1497
  onClick: copyMessage,
1192
1498
  tooltip: copied ? "Copied!" : "Copy to clipboard",
1193
1499
  className: "a24-user-message-copy",
1194
- children: copied ? /* @__PURE__ */ jsx5(Check, { className: "a24-user-message-copy__check text-green-600" }) : /* @__PURE__ */ jsx5(Copy, { className: "a24-user-message-copy__icon" })
1500
+ children: copied ? /* @__PURE__ */ jsx6(Check, { className: "a24-user-message-copy__check text-green-600" }) : /* @__PURE__ */ jsx6(Copy, { className: "a24-user-message-copy__icon" })
1195
1501
  }
1196
1502
  ) : null
1197
1503
  ] });
1198
1504
  }
1199
1505
  const canWriteFeedback = Boolean(message.runId && handlers.onSetFeedback);
1200
- return /* @__PURE__ */ jsxs3(ChatMessageActions, { className: animateCompletion ? "a24-chat-completion-actions" : void 0, children: [
1201
- handlers.onRetry ? /* @__PURE__ */ jsx5(
1506
+ return /* @__PURE__ */ jsxs4(ChatMessageActions, { className: animateCompletion ? "a24-chat-completion-actions" : void 0, children: [
1507
+ handlers.onRetry ? /* @__PURE__ */ jsx6(
1202
1508
  ChatMessageAction,
1203
1509
  {
1204
1510
  label: "Retry",
1205
1511
  onClick: () => void handlers.onRetry?.(message),
1206
1512
  tooltip: "Regenerate response",
1207
- children: /* @__PURE__ */ jsx5(RefreshCcw, { className: "size-4" })
1513
+ children: /* @__PURE__ */ jsx6(RefreshCcw, { className: "size-4" })
1208
1514
  }
1209
1515
  ) : null,
1210
- canWriteFeedback ? /* @__PURE__ */ jsx5(
1516
+ canWriteFeedback ? /* @__PURE__ */ jsx6(
1211
1517
  ChatMessageAction,
1212
1518
  {
1213
1519
  label: "Like",
1214
- onClick: () => void handlers.onSetFeedback?.(message, {
1520
+ disabled: feedbackPending,
1521
+ onClick: () => void writeFeedback({
1215
1522
  rating: handlers.feedback?.rating === "like" ? null : "like"
1216
1523
  }),
1217
1524
  tooltip: "Like this response",
1218
- children: /* @__PURE__ */ jsx5(ThumbsUp, { className: "size-4", fill: handlers.feedback?.rating === "like" ? "currentColor" : "none" })
1525
+ children: /* @__PURE__ */ jsx6(ThumbsUp, { className: "size-4", fill: handlers.feedback?.rating === "like" ? "currentColor" : "none" })
1219
1526
  }
1220
1527
  ) : null,
1221
- canWriteFeedback ? /* @__PURE__ */ jsxs3(Popover.Root, { onOpenChange: setFeedbackOpen, open: feedbackOpen, children: [
1222
- /* @__PURE__ */ jsx5(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx5(
1528
+ canWriteFeedback ? /* @__PURE__ */ jsxs4(Popover.Root, { onOpenChange: setFeedbackOpen, open: feedbackOpen, children: [
1529
+ /* @__PURE__ */ jsx6(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx6(
1223
1530
  ChatMessageAction,
1224
1531
  {
1532
+ disabled: feedbackPending,
1225
1533
  label: "Dislike",
1226
1534
  onClick: () => {
1227
1535
  if (handlers.feedback?.rating !== "dislike") {
1228
- void handlers.onSetFeedback?.(message, { rating: "dislike" });
1536
+ void writeFeedback({ rating: "dislike" });
1229
1537
  }
1230
1538
  setFeedbackOpen(true);
1231
1539
  },
1232
1540
  ref: setFeedbackTrigger,
1233
1541
  tooltip: "Dislike this response",
1234
- children: /* @__PURE__ */ jsx5(ThumbsDown, { className: "size-4", fill: handlers.feedback?.rating === "dislike" ? "currentColor" : "none" })
1542
+ children: /* @__PURE__ */ jsx6(ThumbsDown, { className: "size-4", fill: handlers.feedback?.rating === "dislike" ? "currentColor" : "none" })
1235
1543
  }
1236
1544
  ) }),
1237
- /* @__PURE__ */ jsx5(
1545
+ /* @__PURE__ */ jsx6(
1238
1546
  Popover.Portal,
1239
1547
  {
1240
1548
  container: feedbackTrigger?.closest("[data-agents24-theme-root]") ?? void 0,
1241
- children: /* @__PURE__ */ jsxs3(
1549
+ children: /* @__PURE__ */ jsxs4(
1242
1550
  Popover.Content,
1243
1551
  {
1244
1552
  align: "end",
@@ -1248,8 +1556,8 @@ function AgentChatDefaultActions({
1248
1556
  side: "top",
1249
1557
  sideOffset: 8,
1250
1558
  children: [
1251
- /* @__PURE__ */ jsx5("p", { className: "text-sm font-medium", children: "What could be better?" }),
1252
- /* @__PURE__ */ jsx5("div", { className: "mt-3 flex flex-wrap gap-1.5", children: FEEDBACK_REASONS.map(([value, label]) => /* @__PURE__ */ jsx5(
1559
+ /* @__PURE__ */ jsx6("p", { className: "text-sm font-medium", children: "What could be better?" }),
1560
+ /* @__PURE__ */ jsx6("div", { className: "mt-3 flex flex-wrap gap-1.5", children: FEEDBACK_REASONS.map(([value, label]) => /* @__PURE__ */ jsx6(
1253
1561
  "button",
1254
1562
  {
1255
1563
  "aria-pressed": reason === value,
@@ -1260,7 +1568,7 @@ function AgentChatDefaultActions({
1260
1568
  },
1261
1569
  value
1262
1570
  )) }),
1263
- /* @__PURE__ */ jsx5(
1571
+ /* @__PURE__ */ jsx6(
1264
1572
  "textarea",
1265
1573
  {
1266
1574
  "aria-label": "Optional feedback comment",
@@ -1271,24 +1579,26 @@ function AgentChatDefaultActions({
1271
1579
  value: comment
1272
1580
  }
1273
1581
  ),
1274
- /* @__PURE__ */ jsxs3("div", { className: "mt-3 flex items-center justify-between gap-2", children: [
1275
- /* @__PURE__ */ jsx5(
1582
+ /* @__PURE__ */ jsxs4("div", { className: "mt-3 flex items-center justify-between gap-2", children: [
1583
+ /* @__PURE__ */ jsx6(
1276
1584
  "button",
1277
1585
  {
1586
+ disabled: feedbackPending,
1278
1587
  className: "rounded-md px-3 py-2 text-sm font-medium hover:bg-muted",
1279
1588
  onClick: () => {
1280
- void Promise.resolve(handlers.onSetFeedback?.(message, { rating: null })).then(() => setFeedbackOpen(false)).catch(() => void 0);
1589
+ void writeFeedback({ rating: null }, true);
1281
1590
  },
1282
1591
  type: "button",
1283
1592
  children: "Remove feedback"
1284
1593
  }
1285
1594
  ),
1286
- /* @__PURE__ */ jsx5(
1595
+ /* @__PURE__ */ jsx6(
1287
1596
  "button",
1288
1597
  {
1598
+ disabled: feedbackPending,
1289
1599
  className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:opacity-90",
1290
1600
  onClick: () => {
1291
- void Promise.resolve(handlers.onSetFeedback?.(message, { rating: "dislike", reason, comment })).then(() => setFeedbackOpen(false)).catch(() => void 0);
1601
+ void writeFeedback({ rating: "dislike", reason, comment }, true);
1292
1602
  },
1293
1603
  type: "button",
1294
1604
  children: "Save"
@@ -1302,27 +1612,27 @@ function AgentChatDefaultActions({
1302
1612
  )
1303
1613
  ] }) : null,
1304
1614
  additionalActions,
1305
- handlers.onCopy ? /* @__PURE__ */ jsx5(
1615
+ handlers.onCopy ? /* @__PURE__ */ jsx6(
1306
1616
  ChatMessageAction,
1307
1617
  {
1308
1618
  label: "Copy",
1309
1619
  onClick: copyMessage,
1310
1620
  tooltip: copied ? "Copied!" : "Copy to clipboard",
1311
- children: copied ? /* @__PURE__ */ jsx5(Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ jsx5(Copy, { className: "size-4" })
1621
+ children: copied ? /* @__PURE__ */ jsx6(Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ jsx6(Copy, { className: "size-4" })
1312
1622
  }
1313
1623
  ) : null
1314
1624
  ] });
1315
1625
  }
1316
1626
 
1317
1627
  // src/ui/agent-chat-message.tsx
1318
- import * as React10 from "react";
1628
+ import * as React13 from "react";
1319
1629
  import { ChevronDown as ChevronDown3, ChevronUp } from "lucide-react";
1320
1630
 
1321
1631
  // src/ui/agent-response-timeline.tsx
1322
- import * as React9 from "react";
1632
+ import * as React12 from "react";
1323
1633
 
1324
1634
  // src/ui/ask-user-interaction.tsx
1325
- import * as React6 from "react";
1635
+ import * as React9 from "react";
1326
1636
  import {
1327
1637
  ChevronDown,
1328
1638
  ChevronLeft,
@@ -1384,7 +1694,7 @@ function askUserAnswersFromDrafts(questions, drafts) {
1384
1694
  }
1385
1695
 
1386
1696
  // src/ui/ask-user-interaction.tsx
1387
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1697
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1388
1698
  function resolvedAnswerText(question, answer) {
1389
1699
  if (!answer) return ["No response recorded"];
1390
1700
  const labels = answer.selectedOptionIds.flatMap((optionId) => {
@@ -1398,7 +1708,7 @@ function ResolvedQuestions({
1398
1708
  buttonRef,
1399
1709
  part
1400
1710
  }) {
1401
- const [open, setOpen] = React6.useState(false);
1711
+ const [open, setOpen] = React9.useState(false);
1402
1712
  const count = part.questions.length;
1403
1713
  const answers = new Map(
1404
1714
  (part.resolution?.answers || []).map((answer) => [
@@ -1406,8 +1716,8 @@ function ResolvedQuestions({
1406
1716
  answer
1407
1717
  ])
1408
1718
  );
1409
- return /* @__PURE__ */ jsxs4("div", { className: "a24-ask-user a24-ask-user--resolved w-full", children: [
1410
- /* @__PURE__ */ jsxs4(
1719
+ return /* @__PURE__ */ jsxs5("div", { className: "a24-ask-user a24-ask-user--resolved w-full", children: [
1720
+ /* @__PURE__ */ jsxs5(
1411
1721
  "button",
1412
1722
  {
1413
1723
  "aria-expanded": open,
@@ -1416,13 +1726,13 @@ function ResolvedQuestions({
1416
1726
  ref: buttonRef,
1417
1727
  type: "button",
1418
1728
  children: [
1419
- /* @__PURE__ */ jsxs4("span", { children: [
1729
+ /* @__PURE__ */ jsxs5("span", { children: [
1420
1730
  "Asked ",
1421
1731
  count,
1422
1732
  " question",
1423
1733
  count === 1 ? "" : "s"
1424
1734
  ] }),
1425
- open ? /* @__PURE__ */ jsx6(ChevronDown, { "aria-hidden": "true", className: "size-3.5" }) : /* @__PURE__ */ jsx6(
1735
+ open ? /* @__PURE__ */ jsx7(ChevronDown, { "aria-hidden": "true", className: "size-3.5" }) : /* @__PURE__ */ jsx7(
1426
1736
  ChevronRight,
1427
1737
  {
1428
1738
  "aria-hidden": "true",
@@ -1432,12 +1742,12 @@ function ResolvedQuestions({
1432
1742
  ]
1433
1743
  }
1434
1744
  ),
1435
- open ? /* @__PURE__ */ jsx6("div", { className: "mt-2 space-y-3 ps-4", children: part.questions.map((question) => /* @__PURE__ */ jsxs4("div", { className: "min-w-0", children: [
1436
- /* @__PURE__ */ jsx6("div", { className: "text-sm font-medium text-foreground", children: question.question }),
1437
- /* @__PURE__ */ jsx6("div", { className: "mt-1 space-y-1 text-sm text-muted-foreground", children: resolvedAnswerText(
1745
+ open ? /* @__PURE__ */ jsx7("div", { className: "mt-2 space-y-3 ps-4", children: part.questions.map((question) => /* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
1746
+ /* @__PURE__ */ jsx7("div", { className: "text-sm font-medium text-foreground", children: question.question }),
1747
+ /* @__PURE__ */ jsx7("div", { className: "mt-1 space-y-1 text-sm text-muted-foreground", children: resolvedAnswerText(
1438
1748
  question,
1439
1749
  answers.get(question.id)
1440
- ).map((value, index) => /* @__PURE__ */ jsx6(
1750
+ ).map((value, index) => /* @__PURE__ */ jsx7(
1441
1751
  "div",
1442
1752
  {
1443
1753
  className: "whitespace-pre-wrap break-words",
@@ -1455,23 +1765,23 @@ function AskUserInteraction({
1455
1765
  part
1456
1766
  }) {
1457
1767
  const questions = part.questions;
1458
- const [step, setStep] = React6.useState(0);
1459
- const [drafts, setDrafts] = React6.useState(
1768
+ const [step, setStep] = React9.useState(0);
1769
+ const [drafts, setDrafts] = React9.useState(
1460
1770
  () => emptyAskUserDrafts(questions)
1461
1771
  );
1462
- const headingRef = React6.useRef(null);
1463
- const questionsRef = React6.useRef(questions);
1464
- const resolvedButtonRef = React6.useRef(null);
1465
- const wasResolvedRef = React6.useRef(part.status !== "pending");
1772
+ const headingRef = React9.useRef(null);
1773
+ const questionsRef = React9.useRef(questions);
1774
+ const resolvedButtonRef = React9.useRef(null);
1775
+ const wasResolvedRef = React9.useRef(part.status !== "pending");
1466
1776
  questionsRef.current = questions;
1467
- React6.useEffect(() => {
1777
+ React9.useEffect(() => {
1468
1778
  setStep(0);
1469
1779
  setDrafts(emptyAskUserDrafts(questionsRef.current));
1470
1780
  }, [part.interruptId]);
1471
- React6.useEffect(() => {
1781
+ React9.useEffect(() => {
1472
1782
  headingRef.current?.focus();
1473
1783
  }, [step]);
1474
- React6.useEffect(() => {
1784
+ React9.useEffect(() => {
1475
1785
  const isResolved = part.status !== "pending";
1476
1786
  if (isResolved && !wasResolvedRef.current) {
1477
1787
  resolvedButtonRef.current?.focus();
@@ -1479,11 +1789,11 @@ function AskUserInteraction({
1479
1789
  wasResolvedRef.current = isResolved;
1480
1790
  }, [part.status]);
1481
1791
  if (part.status !== "pending") {
1482
- return /* @__PURE__ */ jsx6(ResolvedQuestions, { buttonRef: resolvedButtonRef, part });
1792
+ return /* @__PURE__ */ jsx7(ResolvedQuestions, { buttonRef: resolvedButtonRef, part });
1483
1793
  }
1484
1794
  const question = questions[step];
1485
1795
  if (!question) {
1486
- return /* @__PURE__ */ jsx6("div", { className: "text-sm text-destructive", role: "alert", children: "This question request is unavailable." });
1796
+ return /* @__PURE__ */ jsx7("div", { className: "text-sm text-destructive", role: "alert", children: "This question request is unavailable." });
1487
1797
  }
1488
1798
  const draft = drafts[question.id] || {
1489
1799
  customText: "",
@@ -1517,9 +1827,9 @@ function AskUserInteraction({
1517
1827
  })
1518
1828
  );
1519
1829
  };
1520
- return /* @__PURE__ */ jsxs4("div", { className: "a24-ask-user space-y-3 rounded-xl border border-border/50 bg-background p-4 shadow-sm", children: [
1521
- /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-start justify-between gap-4", children: [
1522
- /* @__PURE__ */ jsx6(
1830
+ return /* @__PURE__ */ jsxs5("div", { className: "a24-ask-user space-y-3 rounded-xl border border-border/50 bg-background p-4 shadow-sm", children: [
1831
+ /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-start justify-between gap-4", children: [
1832
+ /* @__PURE__ */ jsx7(
1523
1833
  "h3",
1524
1834
  {
1525
1835
  className: "min-w-0 flex-1 text-base font-medium leading-6 text-foreground outline-none",
@@ -1528,8 +1838,8 @@ function AskUserInteraction({
1528
1838
  children: question.question
1529
1839
  }
1530
1840
  ),
1531
- /* @__PURE__ */ jsxs4("div", { className: "flex shrink-0 items-center gap-1 text-muted-foreground", children: [
1532
- /* @__PURE__ */ jsx6(
1841
+ /* @__PURE__ */ jsxs5("div", { className: "flex shrink-0 items-center gap-1 text-muted-foreground", children: [
1842
+ /* @__PURE__ */ jsx7(
1533
1843
  "button",
1534
1844
  {
1535
1845
  "aria-label": "Previous question",
@@ -1537,7 +1847,7 @@ function AskUserInteraction({
1537
1847
  disabled: isBusy || step === 0,
1538
1848
  onClick: () => setStep((current) => Math.max(0, current - 1)),
1539
1849
  type: "button",
1540
- children: /* @__PURE__ */ jsx6(
1850
+ children: /* @__PURE__ */ jsx7(
1541
1851
  ChevronLeft,
1542
1852
  {
1543
1853
  "aria-hidden": "true",
@@ -1546,7 +1856,7 @@ function AskUserInteraction({
1546
1856
  )
1547
1857
  }
1548
1858
  ),
1549
- /* @__PURE__ */ jsxs4(
1859
+ /* @__PURE__ */ jsxs5(
1550
1860
  "span",
1551
1861
  {
1552
1862
  "aria-label": `Question ${step + 1} of ${questions.length}`,
@@ -1558,7 +1868,7 @@ function AskUserInteraction({
1558
1868
  ]
1559
1869
  }
1560
1870
  ),
1561
- /* @__PURE__ */ jsx6(
1871
+ /* @__PURE__ */ jsx7(
1562
1872
  "button",
1563
1873
  {
1564
1874
  "aria-label": isLastQuestion ? "Submit answers" : "Next question",
@@ -1566,7 +1876,7 @@ function AskUserInteraction({
1566
1876
  disabled: isBusy || !onSubmit || !canMoveForward,
1567
1877
  onClick: () => completeStep(drafts),
1568
1878
  type: "button",
1569
- children: /* @__PURE__ */ jsx6(
1879
+ children: /* @__PURE__ */ jsx7(
1570
1880
  ChevronRight,
1571
1881
  {
1572
1882
  "aria-hidden": "true",
@@ -1577,10 +1887,10 @@ function AskUserInteraction({
1577
1887
  )
1578
1888
  ] })
1579
1889
  ] }),
1580
- /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1890
+ /* @__PURE__ */ jsxs5("div", { className: "space-y-1", children: [
1581
1891
  question.options.map((option, index) => {
1582
1892
  const selected = draft.selectedOptionIds.includes(option.id);
1583
- return /* @__PURE__ */ jsxs4(
1893
+ return /* @__PURE__ */ jsxs5(
1584
1894
  "button",
1585
1895
  {
1586
1896
  "aria-pressed": selected,
@@ -1592,7 +1902,7 @@ function AskUserInteraction({
1592
1902
  onClick: () => selectOption(option.id),
1593
1903
  type: "button",
1594
1904
  children: [
1595
- /* @__PURE__ */ jsx6(
1905
+ /* @__PURE__ */ jsx7(
1596
1906
  "span",
1597
1907
  {
1598
1908
  className: cn(
@@ -1602,8 +1912,8 @@ function AskUserInteraction({
1602
1912
  children: index + 1
1603
1913
  }
1604
1914
  ),
1605
- /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 font-medium text-foreground", children: option.label }),
1606
- /* @__PURE__ */ jsx6(
1915
+ /* @__PURE__ */ jsx7("span", { className: "min-w-0 flex-1 font-medium text-foreground", children: option.label }),
1916
+ /* @__PURE__ */ jsx7(
1607
1917
  ChevronRight,
1608
1918
  {
1609
1919
  "aria-hidden": "true",
@@ -1615,9 +1925,9 @@ function AskUserInteraction({
1615
1925
  option.id
1616
1926
  );
1617
1927
  }),
1618
- /* @__PURE__ */ jsxs4("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: [
1619
- /* @__PURE__ */ jsx6("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__ */ jsx6(Pencil, { "aria-hidden": "true", className: "size-4" }) }),
1620
- /* @__PURE__ */ jsx6(
1928
+ /* @__PURE__ */ jsxs5("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: [
1929
+ /* @__PURE__ */ jsx7("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__ */ jsx7(Pencil, { "aria-hidden": "true", className: "size-4" }) }),
1930
+ /* @__PURE__ */ jsx7(
1621
1931
  "input",
1622
1932
  {
1623
1933
  "aria-label": "Other response",
@@ -1636,7 +1946,7 @@ function AskUserInteraction({
1636
1946
  value: draft.customText
1637
1947
  }
1638
1948
  ),
1639
- draft.customText.trim() ? /* @__PURE__ */ jsx6(
1949
+ draft.customText.trim() ? /* @__PURE__ */ jsx7(
1640
1950
  "button",
1641
1951
  {
1642
1952
  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",
@@ -1648,12 +1958,12 @@ function AskUserInteraction({
1648
1958
  ) : null
1649
1959
  ] })
1650
1960
  ] }),
1651
- error ? /* @__PURE__ */ jsx6("div", { className: "text-sm text-destructive", role: "alert", children: error }) : null
1961
+ error ? /* @__PURE__ */ jsx7("div", { className: "text-sm text-destructive", role: "alert", children: error }) : null
1652
1962
  ] });
1653
1963
  }
1654
1964
 
1655
1965
  // src/ui/agent-chat-context-compression-row.tsx
1656
- import { jsx as jsx7 } from "react/jsx-runtime";
1966
+ import { jsx as jsx8 } from "react/jsx-runtime";
1657
1967
  function compressionPresentation(part) {
1658
1968
  if (part.status === "failed") {
1659
1969
  return { label: "Could not compact message history", status: "failed" };
@@ -1668,7 +1978,7 @@ function AgentChatContextCompressionRow({
1668
1978
  part
1669
1979
  }) {
1670
1980
  const presentation = compressionPresentation(part);
1671
- return /* @__PURE__ */ jsx7(
1981
+ return /* @__PURE__ */ jsx8(
1672
1982
  "div",
1673
1983
  {
1674
1984
  "aria-live": "polite",
@@ -1676,11 +1986,11 @@ function AgentChatContextCompressionRow({
1676
1986
  "data-agents24-context-compression": "",
1677
1987
  "data-status": part.status,
1678
1988
  role: "status",
1679
- children: /* @__PURE__ */ jsx7("div", { className: "a24-tool-trigger-row", children: /* @__PURE__ */ jsx7("div", { className: cn(
1989
+ children: /* @__PURE__ */ jsx8("div", { className: "a24-tool-trigger-row", children: /* @__PURE__ */ jsx8("div", { className: cn(
1680
1990
  "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--without-orb",
1681
1991
  presentation.status === "failed" && "a24-tool-trigger--error",
1682
1992
  presentation.status === "running" && "a24-tool-trigger--active"
1683
- ), children: /* @__PURE__ */ jsx7("span", { className: cn(
1993
+ ), children: /* @__PURE__ */ jsx8("span", { className: cn(
1684
1994
  "a24-tool-trigger__label",
1685
1995
  presentation.status === "running" && "a24-tool-shimmer"
1686
1996
  ), children: presentation.label }) }) })
@@ -1689,7 +1999,7 @@ function AgentChatContextCompressionRow({
1689
1999
  }
1690
2000
 
1691
2001
  // src/ui/message-response.tsx
1692
- import * as React7 from "react";
2002
+ import * as React10 from "react";
1693
2003
  import { defaultRehypePlugins, Streamdown } from "streamdown";
1694
2004
 
1695
2005
  // src/active-content.ts
@@ -1714,17 +2024,17 @@ function safeChatMarkdownHref(value) {
1714
2024
  }
1715
2025
 
1716
2026
  // src/ui/message-response.tsx
1717
- import { Fragment as Fragment2, jsx as jsx8 } from "react/jsx-runtime";
2027
+ import { Fragment as Fragment2, jsx as jsx9 } from "react/jsx-runtime";
1718
2028
  function SafeMarkdownLink({ children, href, ...props }) {
1719
2029
  const domProps = { ...props };
1720
2030
  delete domProps.node;
1721
2031
  const safeHref = safeChatMarkdownHref(href);
1722
- if (!safeHref) return /* @__PURE__ */ jsx8("span", { children });
1723
- if (safeHref.startsWith("#")) return /* @__PURE__ */ jsx8("a", { ...domProps, href: safeHref, children });
1724
- return /* @__PURE__ */ jsx8("a", { ...domProps, href: safeHref, rel: "noopener noreferrer", target: "_blank", children });
2032
+ if (!safeHref) return /* @__PURE__ */ jsx9("span", { children });
2033
+ if (safeHref.startsWith("#")) return /* @__PURE__ */ jsx9("a", { ...domProps, href: safeHref, children });
2034
+ return /* @__PURE__ */ jsx9("a", { ...domProps, href: safeHref, rel: "noopener noreferrer", target: "_blank", children });
1725
2035
  }
1726
2036
  function BlockedMarkdownImage({ alt }) {
1727
- return alt ? /* @__PURE__ */ jsx8("span", { "aria-label": `Image blocked: ${alt}`, role: "img", children: alt }) : null;
2037
+ return alt ? /* @__PURE__ */ jsx9("span", { "aria-label": `Image blocked: ${alt}`, role: "img", children: alt }) : null;
1728
2038
  }
1729
2039
  var BLOCK_MARKDOWN_TAGS = /* @__PURE__ */ new Set([
1730
2040
  "blockquote",
@@ -1745,7 +2055,7 @@ function isMultilineCodeNode(node) {
1745
2055
  return node?.tagName === "code" && node.position?.start?.line !== void 0 && node.position?.end?.line !== void 0 && node.position.start.line !== node.position.end.line;
1746
2056
  }
1747
2057
  function hasBlockMarkdownChild(child) {
1748
- if (!React7.isValidElement(child)) {
2058
+ if (!React10.isValidElement(child)) {
1749
2059
  return false;
1750
2060
  }
1751
2061
  if (typeof child.type === "string" && BLOCK_MARKDOWN_TAGS.has(child.type)) {
@@ -1755,7 +2065,7 @@ function hasBlockMarkdownChild(child) {
1755
2065
  if (BLOCK_MARKDOWN_TAGS.has(props.node?.tagName || "") || isMultilineCodeNode(props.node)) {
1756
2066
  return true;
1757
2067
  }
1758
- return React7.Children.toArray(props.children).some(hasBlockMarkdownChild);
2068
+ return React10.Children.toArray(props.children).some(hasBlockMarkdownChild);
1759
2069
  }
1760
2070
  function MessageMarkdownParagraph({
1761
2071
  children,
@@ -1763,17 +2073,17 @@ function MessageMarkdownParagraph({
1763
2073
  }) {
1764
2074
  const domProps = { ...props };
1765
2075
  delete domProps.node;
1766
- const childArray = React7.Children.toArray(children).filter(
2076
+ const childArray = React10.Children.toArray(children).filter(
1767
2077
  (child) => child !== null && child !== ""
1768
2078
  );
1769
- const isImageOnly = childArray.length === 1 && React7.isValidElement(childArray[0]) && childArray[0].props.node?.tagName === "img";
2079
+ const isImageOnly = childArray.length === 1 && React10.isValidElement(childArray[0]) && childArray[0].props.node?.tagName === "img";
1770
2080
  if (isImageOnly) {
1771
- return /* @__PURE__ */ jsx8(Fragment2, { children });
2081
+ return /* @__PURE__ */ jsx9(Fragment2, { children });
1772
2082
  }
1773
2083
  if (childArray.some(hasBlockMarkdownChild)) {
1774
- return /* @__PURE__ */ jsx8("div", { ...domProps, children });
2084
+ return /* @__PURE__ */ jsx9("div", { ...domProps, children });
1775
2085
  }
1776
- return /* @__PURE__ */ jsx8("p", { ...domProps, children });
2086
+ return /* @__PURE__ */ jsx9("p", { ...domProps, children });
1777
2087
  }
1778
2088
  var MESSAGE_RESPONSE_COMPONENTS = {
1779
2089
  a: SafeMarkdownLink,
@@ -1793,7 +2103,7 @@ var MESSAGE_RESPONSE_REHYPE_PLUGINS = [
1793
2103
  ]
1794
2104
  ] : []
1795
2105
  ];
1796
- var MessageResponse = React7.memo(
2106
+ var MessageResponse = React10.memo(
1797
2107
  ({
1798
2108
  children,
1799
2109
  className,
@@ -1811,11 +2121,11 @@ var MessageResponse = React7.memo(
1811
2121
  maxCatchupChars: 32,
1812
2122
  text
1813
2123
  });
1814
- const streamdownComponents = React7.useMemo(
2124
+ const streamdownComponents = React10.useMemo(
1815
2125
  () => ({ ...MESSAGE_RESPONSE_COMPONENTS, ...components }),
1816
2126
  [components]
1817
2127
  );
1818
- return /* @__PURE__ */ jsx8(
2128
+ return /* @__PURE__ */ jsx9(
1819
2129
  Streamdown,
1820
2130
  {
1821
2131
  className: cn("a24-message-response", className),
@@ -1867,10 +2177,10 @@ function resolvedHitlLabel(part) {
1867
2177
  }
1868
2178
 
1869
2179
  // src/ui/tool-sessions.tsx
1870
- import * as React8 from "react";
2180
+ import * as React11 from "react";
1871
2181
  import { ChevronDown as ChevronDown2, ChevronRight as ChevronRight2 } from "lucide-react";
1872
2182
  import { ThinkingOrb } from "thinking-orbs";
1873
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
2183
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
1874
2184
  function toolSlotContext(isActive2, label, part, slots) {
1875
2185
  return {
1876
2186
  isActive: isActive2,
@@ -1888,7 +2198,7 @@ function ToolResourceAction({ context }) {
1888
2198
  const target = context.resourceTargets[0];
1889
2199
  if (!target) return null;
1890
2200
  const name = resourceTargetName(target);
1891
- return /* @__PURE__ */ jsx9(
2201
+ return /* @__PURE__ */ jsx10(
1892
2202
  "button",
1893
2203
  {
1894
2204
  "aria-label": `Open ${target.kind} ${target.path}`,
@@ -1902,8 +2212,8 @@ function ToolResourceAction({ context }) {
1902
2212
  );
1903
2213
  }
1904
2214
  function toolAccessory(context, slots) {
1905
- return /* @__PURE__ */ jsxs5(Fragment3, { children: [
1906
- slots?.onResourceAction ? /* @__PURE__ */ jsx9(ToolResourceAction, { context }) : null,
2215
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
2216
+ slots?.onResourceAction ? /* @__PURE__ */ jsx10(ToolResourceAction, { context }) : null,
1907
2217
  slots?.renderToolAccessory?.(context)
1908
2218
  ] });
1909
2219
  }
@@ -1980,12 +2290,12 @@ function ToolActivityReel({
1980
2290
  }) {
1981
2291
  const initial = initialLabel && initialLabel !== label ? initialLabel : label;
1982
2292
  const parsedInitialShownAt = initialShownAt ? Date.parse(initialShownAt) : Number.NaN;
1983
- const [displayed, setDisplayed] = React8.useState(initial);
1984
- const [previous, setPrevious] = React8.useState(null);
1985
- const displayedRef = React8.useRef(initial);
1986
- const latestRef = React8.useRef(label);
1987
- const shownAtRef = React8.useRef(Number.isFinite(parsedInitialShownAt) ? parsedInitialShownAt : Date.now());
1988
- React8.useEffect(() => {
2293
+ const [displayed, setDisplayed] = React11.useState(initial);
2294
+ const [previous, setPrevious] = React11.useState(null);
2295
+ const displayedRef = React11.useRef(initial);
2296
+ const latestRef = React11.useRef(label);
2297
+ const shownAtRef = React11.useRef(Number.isFinite(parsedInitialShownAt) ? parsedInitialShownAt : Date.now());
2298
+ React11.useEffect(() => {
1989
2299
  latestRef.current = label;
1990
2300
  if (displayedRef.current === label) return;
1991
2301
  const showLatest = () => {
@@ -2005,39 +2315,41 @@ function ToolActivityReel({
2005
2315
  const timer = window.setTimeout(showLatest, remaining);
2006
2316
  return () => window.clearTimeout(timer);
2007
2317
  }, [label]);
2008
- React8.useEffect(() => {
2318
+ React11.useEffect(() => {
2009
2319
  if (!previous) return;
2010
2320
  const timer = window.setTimeout(() => setPrevious(null), 200);
2011
2321
  return () => window.clearTimeout(timer);
2012
2322
  }, [previous]);
2013
- return /* @__PURE__ */ jsxs5("span", { className: "a24-tool-reel", "aria-live": announce ? "polite" : "off", children: [
2014
- previous ? /* @__PURE__ */ jsx9("span", { "aria-hidden": "true", className: "a24-tool-reel__previous", children: previous }) : null,
2015
- /* @__PURE__ */ jsx9("span", { className: previous ? "a24-tool-reel__current" : void 0, children: displayed })
2323
+ return /* @__PURE__ */ jsxs6("span", { className: "a24-tool-reel", "aria-live": announce ? "polite" : "off", children: [
2324
+ previous ? /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", className: "a24-tool-reel__previous", children: previous }) : null,
2325
+ /* @__PURE__ */ jsx10("span", { className: previous ? "a24-tool-reel__current" : void 0, children: displayed })
2016
2326
  ] });
2017
2327
  }
2018
- function ToolActivityOrb({ label }) {
2019
- return /* @__PURE__ */ jsx9("span", { className: "a24-tool-orb", children: /* @__PURE__ */ jsx9(ThinkingOrb, { "aria-label": label || "Working", size: 20, speed: 0.8, state: "searching" }) });
2328
+ function ToolActivityOrb({ label = "Working", kind = "tool", part, renderActivityIcon }) {
2329
+ return /* @__PURE__ */ jsx10("span", { className: "a24-tool-orb", children: renderActivityIcon ? renderActivityIcon({ label, kind, part }) : /* @__PURE__ */ jsx10(ThinkingOrb, { "aria-label": label, size: 20, speed: 0.8, state: "searching" }) });
2020
2330
  }
2021
2331
  function AgentChatActivityRow({
2022
2332
  label,
2023
2333
  modelTurnId,
2334
+ renderActivityIcon,
2024
2335
  onSelect
2025
2336
  }) {
2026
- const content = /* @__PURE__ */ jsxs5(Fragment3, { children: [
2027
- /* @__PURE__ */ jsx9(ToolActivityOrb, { label }),
2028
- /* @__PURE__ */ jsx9("span", { className: "a24-tool-trigger__label a24-tool-shimmer", children: /* @__PURE__ */ jsx9(ToolActivityReel, { label }) })
2337
+ const content = /* @__PURE__ */ jsxs6(Fragment3, { children: [
2338
+ /* @__PURE__ */ jsx10(ToolActivityOrb, { label, kind: "planning", renderActivityIcon }),
2339
+ /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label a24-tool-shimmer", children: /* @__PURE__ */ jsx10(ToolActivityReel, { label }) })
2029
2340
  ] });
2030
- return /* @__PURE__ */ jsx9("div", { className: "a24-tool-trigger-row a24-tool-trigger-row--root", "data-agents24-model-turn": modelTurnId, role: "status", children: onSelect ? /* @__PURE__ */ jsx9("button", { className: "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--active", onClick: onSelect, type: "button", children: content }) : /* @__PURE__ */ jsx9("div", { className: "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--active", children: content }) });
2341
+ return /* @__PURE__ */ jsx10("div", { className: "a24-tool-trigger-row a24-tool-trigger-row--root", "data-agents24-model-turn": modelTurnId, role: "status", children: onSelect ? /* @__PURE__ */ jsx10("button", { className: "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--active", onClick: onSelect, type: "button", children: content }) : /* @__PURE__ */ jsx10("div", { className: "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--active", children: content }) });
2031
2342
  }
2032
2343
  function AgentChatModelTurnRow({ part }) {
2033
- return /* @__PURE__ */ jsx9(AgentChatActivityRow, { label: "Planning next moves", modelTurnId: part.modelTurnId });
2344
+ return /* @__PURE__ */ jsx10(AgentChatActivityRow, { label: "Planning next moves", modelTurnId: part.modelTurnId });
2034
2345
  }
2035
2346
  function AgentChatNodeActivityRow({
2347
+ renderActivityIcon,
2036
2348
  onSelect,
2037
2349
  part
2038
2350
  }) {
2039
- const [open, setOpen] = React8.useState(false);
2040
- const contentId = React8.useId();
2351
+ const [open, setOpen] = React11.useState(false);
2352
+ const contentId = React11.useId();
2041
2353
  const running = part.status === "running";
2042
2354
  const failed = part.status === "failed";
2043
2355
  const failureMessage = part.failure?.message || "The node could not complete the request.";
@@ -2048,18 +2360,18 @@ function AgentChatNodeActivityRow({
2048
2360
  running && "a24-tool-trigger--active",
2049
2361
  failed && "a24-tool-trigger--error"
2050
2362
  );
2051
- const label = running ? /* @__PURE__ */ jsx9("span", { className: "a24-tool-shimmer", children: /* @__PURE__ */ jsx9(ToolActivityReel, { label: part.label }) }) : part.label;
2363
+ const label = running ? /* @__PURE__ */ jsx10("span", { className: "a24-tool-shimmer", children: /* @__PURE__ */ jsx10(ToolActivityReel, { label: part.label }) }) : part.label;
2052
2364
  if (!failed) {
2053
- return /* @__PURE__ */ jsx9("div", { className: "a24-tool-trigger-row", role: "status", children: onSelect ? /* @__PURE__ */ jsxs5("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
2054
- running ? /* @__PURE__ */ jsx9(ToolActivityOrb, { label: part.label }) : null,
2055
- /* @__PURE__ */ jsx9("span", { className: "a24-tool-trigger__label", children: label })
2056
- ] }) : /* @__PURE__ */ jsxs5("div", { className: triggerClassName, children: [
2057
- running ? /* @__PURE__ */ jsx9(ToolActivityOrb, { label: part.label }) : null,
2058
- /* @__PURE__ */ jsx9("span", { className: "a24-tool-trigger__label", children: label })
2365
+ return /* @__PURE__ */ jsx10("div", { className: "a24-tool-trigger-row", role: "status", children: onSelect ? /* @__PURE__ */ jsxs6("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
2366
+ running ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: part.label, kind: "node", part, renderActivityIcon }) : null,
2367
+ /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label", children: label })
2368
+ ] }) : /* @__PURE__ */ jsxs6("div", { className: triggerClassName, children: [
2369
+ running ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: part.label, kind: "node", part, renderActivityIcon }) : null,
2370
+ /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label", children: label })
2059
2371
  ] }) });
2060
2372
  }
2061
- return /* @__PURE__ */ jsxs5("div", { className: "a24-tool-disclosure", children: [
2062
- /* @__PURE__ */ jsx9("div", { className: "a24-tool-trigger-row", children: /* @__PURE__ */ jsxs5(
2373
+ return /* @__PURE__ */ jsxs6("div", { className: "a24-tool-disclosure", children: [
2374
+ /* @__PURE__ */ jsx10("div", { className: "a24-tool-trigger-row", children: /* @__PURE__ */ jsxs6(
2063
2375
  "button",
2064
2376
  {
2065
2377
  "aria-controls": contentId,
@@ -2071,12 +2383,12 @@ function AgentChatNodeActivityRow({
2071
2383
  },
2072
2384
  type: "button",
2073
2385
  children: [
2074
- /* @__PURE__ */ jsx9("span", { className: "a24-tool-trigger__label", children: label }),
2075
- open ? /* @__PURE__ */ jsx9(ChevronDown2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ jsx9(ChevronRight2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
2386
+ /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label", children: label }),
2387
+ open ? /* @__PURE__ */ jsx10(ChevronDown2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ jsx10(ChevronRight2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
2076
2388
  ]
2077
2389
  }
2078
2390
  ) }),
2079
- open ? /* @__PURE__ */ jsx9("div", { className: "a24-tool-detail a24-tool-detail--error", id: contentId, children: failureMessage }) : null
2391
+ open ? /* @__PURE__ */ jsx10("div", { className: "a24-tool-detail a24-tool-detail--error", id: contentId, children: failureMessage }) : null
2080
2392
  ] });
2081
2393
  }
2082
2394
  function toolRowLabel(part, separateResource = false) {
@@ -2100,27 +2412,28 @@ function ToolDisclosure({
2100
2412
  root,
2101
2413
  showOrb,
2102
2414
  statusLabel,
2415
+ renderActivityIcon,
2103
2416
  accessory
2104
2417
  }) {
2105
- const [open, setOpen] = React8.useState(false);
2106
- const contentId = React8.useId();
2107
- const expandable = React8.Children.toArray(children).length > 0;
2418
+ const [open, setOpen] = React11.useState(false);
2419
+ const contentId = React11.useId();
2420
+ const expandable = React11.Children.toArray(children).length > 0;
2108
2421
  if (!expandable) {
2109
2422
  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");
2110
- return /* @__PURE__ */ jsxs5("div", { className: cn("a24-tool-trigger-row", root && "a24-tool-trigger-row--root"), children: [
2111
- onSelect && part ? /* @__PURE__ */ jsxs5("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
2112
- showOrb ? /* @__PURE__ */ jsx9(ToolActivityOrb, { label: statusLabel }) : null,
2113
- /* @__PURE__ */ jsx9("span", { className: "a24-tool-trigger__label", children: label })
2114
- ] }) : /* @__PURE__ */ jsxs5("div", { className: triggerClassName, children: [
2115
- showOrb ? /* @__PURE__ */ jsx9(ToolActivityOrb, { label: statusLabel }) : null,
2116
- /* @__PURE__ */ jsx9("span", { className: "a24-tool-trigger__label", children: label })
2423
+ return /* @__PURE__ */ jsxs6("div", { className: cn("a24-tool-trigger-row", root && "a24-tool-trigger-row--root"), children: [
2424
+ onSelect && part ? /* @__PURE__ */ jsxs6("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
2425
+ showOrb ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: statusLabel || "Working", kind: grouped ? "group" : "tool", part, renderActivityIcon }) : null,
2426
+ /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label", children: label })
2427
+ ] }) : /* @__PURE__ */ jsxs6("div", { className: triggerClassName, children: [
2428
+ showOrb ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: statusLabel || "Working", kind: grouped ? "group" : "tool", part, renderActivityIcon }) : null,
2429
+ /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label", children: label })
2117
2430
  ] }),
2118
2431
  accessory
2119
2432
  ] });
2120
2433
  }
2121
- return /* @__PURE__ */ jsxs5("div", { className: "a24-tool-disclosure", children: [
2122
- /* @__PURE__ */ jsxs5("div", { className: cn("a24-tool-trigger-row", root && "a24-tool-trigger-row--root"), children: [
2123
- /* @__PURE__ */ jsxs5(
2434
+ return /* @__PURE__ */ jsxs6("div", { className: "a24-tool-disclosure", children: [
2435
+ /* @__PURE__ */ jsxs6("div", { className: cn("a24-tool-trigger-row", root && "a24-tool-trigger-row--root"), children: [
2436
+ /* @__PURE__ */ jsxs6(
2124
2437
  "button",
2125
2438
  {
2126
2439
  "aria-controls": contentId,
@@ -2134,25 +2447,25 @@ function ToolDisclosure({
2134
2447
  },
2135
2448
  type: "button",
2136
2449
  children: [
2137
- showOrb ? /* @__PURE__ */ jsx9(ToolActivityOrb, { label: statusLabel }) : null,
2138
- /* @__PURE__ */ jsx9("span", { className: "a24-tool-trigger__label", children: label }),
2139
- open ? /* @__PURE__ */ jsx9(ChevronDown2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ jsx9(ChevronRight2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
2450
+ showOrb ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: statusLabel || "Working", kind: grouped ? "group" : "tool", part, renderActivityIcon }) : null,
2451
+ /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label", children: label }),
2452
+ open ? /* @__PURE__ */ jsx10(ChevronDown2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ jsx10(ChevronRight2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
2140
2453
  ]
2141
2454
  }
2142
2455
  ),
2143
2456
  accessory
2144
2457
  ] }),
2145
- open ? /* @__PURE__ */ jsx9("div", { className: "a24-tool-disclosure__content", id: contentId, children }) : null
2458
+ open ? /* @__PURE__ */ jsx10("div", { className: "a24-tool-disclosure__content", id: contentId, children }) : null
2146
2459
  ] });
2147
2460
  }
2148
2461
  function ToolLabel({ active, label, part, slots }) {
2149
2462
  const rendered = slots?.renderToolLabel?.(toolSlotContext(active, label, part, slots)) ?? label;
2150
- return active ? /* @__PURE__ */ jsx9("span", { className: "a24-tool-shimmer", children: rendered }) : /* @__PURE__ */ jsx9(Fragment3, { children: rendered });
2463
+ return active ? /* @__PURE__ */ jsx10("span", { className: "a24-tool-shimmer", children: rendered }) : /* @__PURE__ */ jsx10(Fragment3, { children: rendered });
2151
2464
  }
2152
2465
  function ToolList({ children }) {
2153
- const listRef = React8.useRef(null);
2154
- const [fades, setFades] = React8.useState({ bottom: false, top: false });
2155
- const updateFades = React8.useCallback(() => {
2466
+ const listRef = React11.useRef(null);
2467
+ const [fades, setFades] = React11.useState({ bottom: false, top: false });
2468
+ const updateFades = React11.useCallback(() => {
2156
2469
  const list = listRef.current;
2157
2470
  if (!list) return;
2158
2471
  const next = {
@@ -2161,7 +2474,7 @@ function ToolList({ children }) {
2161
2474
  };
2162
2475
  setFades((current) => current.bottom === next.bottom && current.top === next.top ? current : next);
2163
2476
  }, []);
2164
- React8.useEffect(() => {
2477
+ React11.useEffect(() => {
2165
2478
  updateFades();
2166
2479
  const list = listRef.current;
2167
2480
  if (!list || typeof ResizeObserver === "undefined") return;
@@ -2169,13 +2482,13 @@ function ToolList({ children }) {
2169
2482
  observer.observe(list);
2170
2483
  return () => observer.disconnect();
2171
2484
  }, [children, updateFades]);
2172
- return /* @__PURE__ */ jsx9(
2485
+ return /* @__PURE__ */ jsx10(
2173
2486
  "div",
2174
2487
  {
2175
2488
  className: "a24-tool-list-frame",
2176
2489
  "data-scroll-fade-bottom": fades.bottom ? "" : void 0,
2177
2490
  "data-scroll-fade-top": fades.top ? "" : void 0,
2178
- children: /* @__PURE__ */ jsx9("div", { className: "a24-tool-list", onScroll: updateFades, ref: listRef, role: "list", children })
2491
+ children: /* @__PURE__ */ jsx10("div", { className: "a24-tool-list", onScroll: updateFades, ref: listRef, role: "list", children })
2179
2492
  }
2180
2493
  );
2181
2494
  }
@@ -2192,12 +2505,13 @@ function AgentChatToolRow({
2192
2505
  const detail = failed ? failureMessage : optionalText(part.presentation.detail);
2193
2506
  const hostDetails = failed ? null : slots?.renderToolDetails?.(part);
2194
2507
  const context = toolSlotContext(active, toolRowLabel(part, Boolean(slots?.onResourceAction)), part, slots);
2195
- return /* @__PURE__ */ jsx9("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: /* @__PURE__ */ jsxs5(
2508
+ return /* @__PURE__ */ jsx10("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: /* @__PURE__ */ jsxs6(
2196
2509
  ToolDisclosure,
2197
2510
  {
2511
+ renderActivityIcon: slots?.renderActivityIcon,
2198
2512
  error: failed,
2199
2513
  accessory: toolAccessory(context, slots),
2200
- label: /* @__PURE__ */ jsx9(ToolLabel, { active, label: context.label, part, slots }),
2514
+ label: /* @__PURE__ */ jsx10(ToolLabel, { active, label: context.label, part, slots }),
2201
2515
  loading: active,
2202
2516
  onExpandedChange: slots?.onExpandedChange,
2203
2517
  onSelect: slots?.onToolSelect,
@@ -2206,7 +2520,7 @@ function AgentChatToolRow({
2206
2520
  showOrb: showOrb ?? active,
2207
2521
  statusLabel: active ? context.label : void 0,
2208
2522
  children: [
2209
- detail ? /* @__PURE__ */ jsx9("div", { className: cn("a24-tool-detail", failed && "a24-tool-detail--error"), children: detail }) : null,
2523
+ detail ? /* @__PURE__ */ jsx10("div", { className: cn("a24-tool-detail", failed && "a24-tool-detail--error"), children: detail }) : null,
2210
2524
  hostDetails
2211
2525
  ]
2212
2526
  }
@@ -2226,21 +2540,22 @@ function AgentChatToolSession({
2226
2540
  parts,
2227
2541
  slots
2228
2542
  }) {
2229
- const presentedLiveRef = React8.useRef(false);
2543
+ const presentedLiveRef = React11.useRef(false);
2230
2544
  if (isLive && isLatestActivity) presentedLiveRef.current = true;
2231
2545
  const activeIndex = parts.findLastIndex((part) => isActive(part, activeToolId));
2232
2546
  const running = activeIndex >= 0;
2233
- if (parts.length === 1 && !modelTurn && !leadingModelTurn) return /* @__PURE__ */ jsx9(AgentChatToolRow, { activeToolId, part: parts[0], slots });
2547
+ if (parts.length === 1 && !modelTurn && !leadingModelTurn) return /* @__PURE__ */ jsx10(AgentChatToolRow, { activeToolId, part: parts[0], slots });
2234
2548
  const visibleIndex = running ? activeIndex : parts.length - 1;
2235
2549
  const visiblePart = parts[visibleIndex];
2236
2550
  const planning = Boolean(modelTurn);
2237
2551
  const presentingActivity = isLatestActivity && (planning || running || isLive);
2238
2552
  const failed = isLatestActivity && (visiblePart.state === "output-error" || visiblePart.state === "cancelled");
2239
2553
  const label = planning ? "Planning next moves" : isLive && isLatestActivity ? toolRowLabel(visiblePart, true) : sessionLabel(parts, visiblePart, running);
2240
- const labelContent = presentedLiveRef.current ? /* @__PURE__ */ jsx9("span", { className: presentingActivity ? "a24-tool-shimmer" : void 0, children: /* @__PURE__ */ jsx9(ToolActivityReel, { initialLabel: leadingModelTurn ? "Planning next moves" : void 0, initialShownAt: leadingModelTurn?.timestamp, label }) }) : label;
2241
- return /* @__PURE__ */ jsx9(
2554
+ const labelContent = presentedLiveRef.current ? /* @__PURE__ */ jsx10("span", { className: presentingActivity ? "a24-tool-shimmer" : void 0, children: /* @__PURE__ */ jsx10(ToolActivityReel, { initialLabel: leadingModelTurn ? "Planning next moves" : void 0, initialShownAt: leadingModelTurn?.timestamp, label }) }) : label;
2555
+ return /* @__PURE__ */ jsx10(
2242
2556
  ToolDisclosure,
2243
2557
  {
2558
+ renderActivityIcon: slots?.renderActivityIcon,
2244
2559
  error: failed,
2245
2560
  grouped: true,
2246
2561
  label: labelContent,
@@ -2250,7 +2565,7 @@ function AgentChatToolSession({
2250
2565
  root: true,
2251
2566
  showOrb: isLive && isLatestActivity,
2252
2567
  statusLabel: planning ? "Planning next moves" : label,
2253
- children: /* @__PURE__ */ jsx9(ToolList, { children: parts.map((part) => /* @__PURE__ */ jsx9("div", { role: "listitem", children: /* @__PURE__ */ jsx9(AgentChatToolRow, { activeToolId, part, root: false, showOrb: false, slots }) }, part.id)) })
2568
+ children: /* @__PURE__ */ jsx10(ToolList, { children: parts.map((part) => /* @__PURE__ */ jsx10("div", { role: "listitem", children: /* @__PURE__ */ jsx10(AgentChatToolRow, { activeToolId, part, root: false, showOrb: false, slots }) }, part.id)) })
2254
2569
  }
2255
2570
  );
2256
2571
  }
@@ -2271,7 +2586,7 @@ function subagentLabel(part) {
2271
2586
  return { label: `Agent ${name}, ran ${children.length} ${children.length === 1 ? "tool" : "tools"}`, running: false };
2272
2587
  }
2273
2588
  function AgentChatSubagentGroup({ isLatestActivity = true, isLive = false, part, slots }) {
2274
- const presentedLiveRef = React8.useRef(false);
2589
+ const presentedLiveRef = React11.useRef(false);
2275
2590
  if (isLive && isLatestActivity) presentedLiveRef.current = true;
2276
2591
  const children = part.presentation.childTools || [];
2277
2592
  const state = subagentLabel(part);
@@ -2280,11 +2595,12 @@ function AgentChatSubagentGroup({ isLatestActivity = true, isLive = false, part,
2280
2595
  const hostDetails = failed ? null : slots?.renderToolDetails?.(part);
2281
2596
  const failureMessage = part.failure?.message || null;
2282
2597
  const presentingActivity = isLatestActivity && state.running;
2283
- const labelContent = presentedLiveRef.current ? /* @__PURE__ */ jsx9("span", { className: presentingActivity ? "a24-tool-shimmer" : void 0, children: /* @__PURE__ */ jsx9(ToolActivityReel, { label: state.label }) }) : state.label;
2598
+ const labelContent = presentedLiveRef.current ? /* @__PURE__ */ jsx10("span", { className: presentingActivity ? "a24-tool-shimmer" : void 0, children: /* @__PURE__ */ jsx10(ToolActivityReel, { label: state.label }) }) : state.label;
2284
2599
  const context = toolSlotContext(presentingActivity, state.label, part, slots);
2285
- return /* @__PURE__ */ jsx9(
2600
+ return /* @__PURE__ */ jsx10(
2286
2601
  ToolDisclosure,
2287
2602
  {
2603
+ renderActivityIcon: slots?.renderActivityIcon,
2288
2604
  accessory: slots?.renderToolAccessory?.(context),
2289
2605
  error: failed,
2290
2606
  grouped: true,
@@ -2295,12 +2611,12 @@ function AgentChatSubagentGroup({ isLatestActivity = true, isLive = false, part,
2295
2611
  root: true,
2296
2612
  showOrb: isLive && isLatestActivity,
2297
2613
  statusLabel: state.label,
2298
- children: /* @__PURE__ */ jsxs5(ToolList, { children: [
2299
- children.map((child) => /* @__PURE__ */ jsx9("div", { className: cn("a24-child-tool", child.status === "failed" && "a24-child-tool--error"), role: "listitem", children: slots?.renderChildTool?.(child, part) ?? /* @__PURE__ */ jsxs5(Fragment3, { children: [
2300
- /* @__PURE__ */ jsx9("span", { className: cn(isLive && isLatestActivity && child.status === "running" && "a24-tool-shimmer"), children: childLabel(child) }),
2301
- child.detail ? /* @__PURE__ */ jsx9("span", { className: "a24-child-tool__detail", children: child.detail }) : null
2614
+ children: /* @__PURE__ */ jsxs6(ToolList, { children: [
2615
+ children.map((child) => /* @__PURE__ */ jsx10("div", { className: cn("a24-child-tool", child.status === "failed" && "a24-child-tool--error"), role: "listitem", children: slots?.renderChildTool?.(child, part) ?? /* @__PURE__ */ jsxs6(Fragment3, { children: [
2616
+ /* @__PURE__ */ jsx10("span", { className: cn(isLive && isLatestActivity && child.status === "running" && "a24-tool-shimmer"), children: childLabel(child) }),
2617
+ child.detail ? /* @__PURE__ */ jsx10("span", { className: "a24-child-tool__detail", children: child.detail }) : null
2302
2618
  ] }) }, child.id)),
2303
- failureMessage ? /* @__PURE__ */ jsx9("div", { className: "a24-tool-detail a24-tool-detail--error", children: failureMessage }) : null,
2619
+ failureMessage ? /* @__PURE__ */ jsx10("div", { className: "a24-tool-detail a24-tool-detail--error", children: failureMessage }) : null,
2304
2620
  hostDetails
2305
2621
  ] })
2306
2622
  }
@@ -2308,24 +2624,24 @@ function AgentChatSubagentGroup({ isLatestActivity = true, isLive = false, part,
2308
2624
  }
2309
2625
 
2310
2626
  // src/ui/agent-response-timeline.tsx
2311
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
2627
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
2312
2628
  function isInternalAskUserTool(part) {
2313
2629
  return [part.toolName, part.toolKey, part.action].some((value) => value?.trim().toLowerCase() === "runtime__ask_user");
2314
2630
  }
2315
2631
  function Shimmer({ children, className }) {
2316
- return /* @__PURE__ */ jsx10("span", { className: cn("inline-block animate-pulse", className), children });
2632
+ return /* @__PURE__ */ jsx11("span", { className: cn("inline-block animate-pulse", className), children });
2317
2633
  }
2318
2634
  function AgentChatToolPart({
2319
2635
  activeToolId,
2320
2636
  part
2321
2637
  }) {
2322
- return /* @__PURE__ */ jsx10(AgentChatToolRow, { activeToolId, part });
2638
+ return /* @__PURE__ */ jsx11(AgentChatToolRow, { activeToolId, part });
2323
2639
  }
2324
2640
  function AgentChatToolGroup({
2325
2641
  activeToolId,
2326
2642
  parts
2327
2643
  }) {
2328
- return /* @__PURE__ */ jsx10(AgentChatToolSession, { activeToolId, parts });
2644
+ return /* @__PURE__ */ jsx11(AgentChatToolSession, { activeToolId, parts });
2329
2645
  }
2330
2646
  function HitlPartRow({
2331
2647
  getHitlActionState,
@@ -2334,18 +2650,18 @@ function HitlPartRow({
2334
2650
  part
2335
2651
  }) {
2336
2652
  const state = getHitlActionState?.(part);
2337
- const [comment, setComment] = React9.useState("");
2653
+ const [comment, setComment] = React12.useState("");
2338
2654
  const presentation = part.presentation || {};
2339
2655
  const requireComment = presentation.require_comment === true;
2340
2656
  const isTerminal = part.status !== "pending";
2341
- const resolvedRef = React9.useRef(null);
2342
- const wasTerminal = React9.useRef(isTerminal);
2343
- React9.useEffect(() => {
2657
+ const resolvedRef = React12.useRef(null);
2658
+ const wasTerminal = React12.useRef(isTerminal);
2659
+ React12.useEffect(() => {
2344
2660
  if (isTerminal && !wasTerminal.current) resolvedRef.current?.focus();
2345
2661
  wasTerminal.current = isTerminal;
2346
2662
  }, [isTerminal]);
2347
2663
  if (part.hitlKind === "user_input") {
2348
- return /* @__PURE__ */ jsx10(
2664
+ return /* @__PURE__ */ jsx11(
2349
2665
  AskUserInteraction,
2350
2666
  {
2351
2667
  error: state?.error,
@@ -2363,11 +2679,11 @@ function HitlPartRow({
2363
2679
  );
2364
2680
  }
2365
2681
  if (isTerminal) {
2366
- return /* @__PURE__ */ jsx10("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) });
2682
+ return /* @__PURE__ */ jsx11("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) });
2367
2683
  }
2368
- return /* @__PURE__ */ jsxs6("div", { className: "a24-hitl space-y-2.5 px-1 py-2", children: [
2369
- /* @__PURE__ */ jsx10("div", { className: "a24-hitl__title text-sm font-medium text-foreground text-pretty", children: hitlTitle(part) }),
2370
- requireComment ? /* @__PURE__ */ jsx10(
2684
+ return /* @__PURE__ */ jsxs7("div", { className: "a24-hitl space-y-2.5 px-1 py-2", children: [
2685
+ /* @__PURE__ */ jsx11("div", { className: "a24-hitl__title text-sm font-medium text-foreground text-pretty", children: hitlTitle(part) }),
2686
+ requireComment ? /* @__PURE__ */ jsx11(
2371
2687
  "textarea",
2372
2688
  {
2373
2689
  "aria-label": "Approval comment",
@@ -2379,8 +2695,8 @@ function HitlPartRow({
2379
2695
  value: comment
2380
2696
  }
2381
2697
  ) : null,
2382
- state?.error ? /* @__PURE__ */ jsx10("div", { className: "a24-hitl__error text-sm text-destructive", children: state.error }) : null,
2383
- /* @__PURE__ */ jsx10("div", { className: "a24-hitl__actions flex flex-wrap items-center gap-1.5", children: part.allowedActions.map((action) => /* @__PURE__ */ jsx10(
2698
+ state?.error ? /* @__PURE__ */ jsx11("div", { className: "a24-hitl__error text-sm text-destructive", children: state.error }) : null,
2699
+ /* @__PURE__ */ jsx11("div", { className: "a24-hitl__actions flex flex-wrap items-center gap-1.5", children: part.allowedActions.map((action) => /* @__PURE__ */ jsx11(
2384
2700
  "button",
2385
2701
  {
2386
2702
  "aria-label": hitlActionLabel(part, action),
@@ -2413,7 +2729,7 @@ function AgentChatPart({
2413
2729
  toolSlots
2414
2730
  }) {
2415
2731
  if (part.kind === "text") {
2416
- return /* @__PURE__ */ jsx10(
2732
+ return /* @__PURE__ */ jsx11(
2417
2733
  MessageResponse,
2418
2734
  {
2419
2735
  ...responseProps,
@@ -2423,23 +2739,23 @@ function AgentChatPart({
2423
2739
  }
2424
2740
  );
2425
2741
  }
2426
- if (part.kind === "tool") return isAgentChatSubagent(part) ? /* @__PURE__ */ jsx10(AgentChatSubagentGroup, { part, slots: toolSlots }) : /* @__PURE__ */ jsx10(AgentChatToolRow, { activeToolId, part, slots: toolSlots });
2427
- if (part.kind === "model-turn") return /* @__PURE__ */ jsx10(AgentChatModelTurnRow, { part });
2742
+ if (part.kind === "tool") return isAgentChatSubagent(part) ? /* @__PURE__ */ jsx11(AgentChatSubagentGroup, { part, slots: toolSlots }) : /* @__PURE__ */ jsx11(AgentChatToolRow, { activeToolId, part, slots: toolSlots });
2743
+ if (part.kind === "model-turn") return /* @__PURE__ */ jsx11(AgentChatModelTurnRow, { part });
2428
2744
  if (part.kind === "reasoning") {
2429
2745
  const label = part.text || part.label || "Thinking";
2430
- return part.status === "running" || part.status === "streaming" ? /* @__PURE__ */ jsx10(Shimmer, { className: "text-sm text-muted-foreground", children: label }) : /* @__PURE__ */ jsx10("div", { className: "text-sm text-muted-foreground", children: label });
2746
+ return part.status === "running" || part.status === "streaming" ? /* @__PURE__ */ jsx11(Shimmer, { className: "text-sm text-muted-foreground", children: label }) : /* @__PURE__ */ jsx11("div", { className: "text-sm text-muted-foreground", children: label });
2431
2747
  }
2432
2748
  if (part.kind === "context-compression") {
2433
- return /* @__PURE__ */ jsx10(AgentChatContextCompressionRow, { part });
2749
+ return /* @__PURE__ */ jsx11(AgentChatContextCompressionRow, { part });
2434
2750
  }
2435
2751
  if (part.kind === "ui-blocks") {
2436
2752
  if (part.state === "output-error") {
2437
- return /* @__PURE__ */ jsx10("div", { className: "a24-timeline-error text-sm text-destructive", children: part.errorText || "Failed to render UI content." });
2753
+ return /* @__PURE__ */ jsx11("div", { className: "a24-timeline-error text-sm text-destructive", children: part.errorText || "Failed to render UI content." });
2438
2754
  }
2439
- return part.bundle ? /* @__PURE__ */ jsx10(SafeUiBlocks, { bundle: part.bundle }) : /* @__PURE__ */ jsx10("div", { className: "text-sm text-muted-foreground", children: part.state === "input-streaming" ? /* @__PURE__ */ jsx10(Shimmer, { children: "Rendering UI block" }) : "Rendered UI block" });
2755
+ return part.bundle ? /* @__PURE__ */ jsx11(SafeUiBlocks, { bundle: part.bundle }) : /* @__PURE__ */ jsx11("div", { className: "text-sm text-muted-foreground", children: part.state === "input-streaming" ? /* @__PURE__ */ jsx11(Shimmer, { children: "Rendering UI block" }) : "Rendered UI block" });
2440
2756
  }
2441
2757
  if (part.kind === "hitl") {
2442
- return /* @__PURE__ */ jsx10(
2758
+ return /* @__PURE__ */ jsx11(
2443
2759
  HitlPartRow,
2444
2760
  {
2445
2761
  getHitlActionState,
@@ -2449,18 +2765,18 @@ function AgentChatPart({
2449
2765
  }
2450
2766
  );
2451
2767
  }
2452
- if (part.kind === "error") return /* @__PURE__ */ jsx10("div", { className: "a24-timeline-error text-sm text-destructive", role: "alert", children: part.errorText });
2768
+ if (part.kind === "error") return /* @__PURE__ */ jsx11("div", { className: "a24-timeline-error text-sm text-destructive", role: "alert", children: part.errorText });
2453
2769
  if (part.kind === "source") {
2454
2770
  const href = safeExternalUrl(part.url);
2455
- return href ? /* @__PURE__ */ jsx10("a", { className: "a24-source-link", href, rel: "noopener noreferrer", target: "_blank", children: part.title }) : /* @__PURE__ */ jsx10("span", { className: "a24-source-link", children: part.title });
2771
+ return href ? /* @__PURE__ */ jsx11("a", { className: "a24-source-link", href, rel: "noopener noreferrer", target: "_blank", children: part.title }) : /* @__PURE__ */ jsx11("span", { className: "a24-source-link", children: part.title });
2456
2772
  }
2457
2773
  if (part.kind === "citation") {
2458
2774
  const href = safeExternalUrl(part.url);
2459
2775
  const label = part.label || "Citation";
2460
- return href ? /* @__PURE__ */ jsx10("a", { className: "a24-citation", href, rel: "noopener noreferrer", target: "_blank", children: label }) : /* @__PURE__ */ jsx10("span", { className: "a24-citation", children: label });
2776
+ return href ? /* @__PURE__ */ jsx11("a", { className: "a24-citation", href, rel: "noopener noreferrer", target: "_blank", children: label }) : /* @__PURE__ */ jsx11("span", { className: "a24-citation", children: label });
2461
2777
  }
2462
- if (part.kind === "attachment") return /* @__PURE__ */ jsx10("div", { className: "a24-output-attachment", children: part.attachment.filename || "Attachment" });
2463
- if (part.kind === "data") return /* @__PURE__ */ jsx10("div", { "data-agents24-data-part": part.name, className: "hidden", children: message.id });
2778
+ if (part.kind === "attachment") return /* @__PURE__ */ jsx11("div", { className: "a24-output-attachment", children: part.attachment.filename || "Attachment" });
2779
+ if (part.kind === "data") return /* @__PURE__ */ jsx11("div", { "data-agents24-data-part": part.name, className: "hidden", children: message.id });
2464
2780
  return null;
2465
2781
  }
2466
2782
  function safeExternalUrl(value) {
@@ -2474,28 +2790,28 @@ function safeExternalUrl(value) {
2474
2790
  }
2475
2791
  function SafeUiBlocks({ bundle }) {
2476
2792
  const rows = Array.isArray(bundle.rows) ? bundle.rows : [];
2477
- return /* @__PURE__ */ jsxs6("section", { "aria-label": typeof bundle.title === "string" ? bundle.title : "Agent result", className: "a24-ui-blocks", children: [
2478
- typeof bundle.title === "string" ? /* @__PURE__ */ jsx10("h3", { children: bundle.title }) : null,
2793
+ return /* @__PURE__ */ jsxs7("section", { "aria-label": typeof bundle.title === "string" ? bundle.title : "Agent result", className: "a24-ui-blocks", children: [
2794
+ typeof bundle.title === "string" ? /* @__PURE__ */ jsx11("h3", { children: bundle.title }) : null,
2479
2795
  rows.map((rawRow, rowIndex) => {
2480
2796
  const row = rawRow && typeof rawRow === "object" && !Array.isArray(rawRow) ? rawRow : {};
2481
2797
  const blocks = Array.isArray(row.blocks) ? row.blocks : [];
2482
- return /* @__PURE__ */ jsx10("div", { className: "a24-ui-blocks__row", children: blocks.map((rawBlock, blockIndex) => {
2798
+ return /* @__PURE__ */ jsx11("div", { className: "a24-ui-blocks__row", children: blocks.map((rawBlock, blockIndex) => {
2483
2799
  const block = rawBlock && typeof rawBlock === "object" && !Array.isArray(rawBlock) ? rawBlock : {};
2484
2800
  const title = typeof block.title === "string" ? block.title : "Result";
2485
2801
  const kind = String(block.kind || "note");
2486
2802
  if (kind === "table" && Array.isArray(block.columns) && Array.isArray(block.rows)) {
2487
- return /* @__PURE__ */ jsxs6("div", { className: "a24-ui-block", children: [
2488
- /* @__PURE__ */ jsx10("h4", { children: title }),
2489
- /* @__PURE__ */ jsx10("div", { className: "a24-ui-table-scroll", children: /* @__PURE__ */ jsxs6("table", { children: [
2490
- /* @__PURE__ */ jsx10("thead", { children: /* @__PURE__ */ jsx10("tr", { children: block.columns.map((column, index) => /* @__PURE__ */ jsx10("th", { children: String(column) }, index)) }) }),
2491
- /* @__PURE__ */ jsx10("tbody", { children: block.rows.map((rawCells, index) => /* @__PURE__ */ jsx10("tr", { children: (Array.isArray(rawCells) ? rawCells : []).map((cell, cellIndex) => /* @__PURE__ */ jsx10("td", { children: String(cell) }, cellIndex)) }, index)) })
2803
+ return /* @__PURE__ */ jsxs7("div", { className: "a24-ui-block", children: [
2804
+ /* @__PURE__ */ jsx11("h4", { children: title }),
2805
+ /* @__PURE__ */ jsx11("div", { className: "a24-ui-table-scroll", children: /* @__PURE__ */ jsxs7("table", { children: [
2806
+ /* @__PURE__ */ jsx11("thead", { children: /* @__PURE__ */ jsx11("tr", { children: block.columns.map((column, index) => /* @__PURE__ */ jsx11("th", { children: String(column) }, index)) }) }),
2807
+ /* @__PURE__ */ jsx11("tbody", { children: block.rows.map((rawCells, index) => /* @__PURE__ */ jsx11("tr", { children: (Array.isArray(rawCells) ? rawCells : []).map((cell, cellIndex) => /* @__PURE__ */ jsx11("td", { children: String(cell) }, cellIndex)) }, index)) })
2492
2808
  ] }) })
2493
2809
  ] }, String(block.id || blockIndex));
2494
2810
  }
2495
2811
  const value = block.value ?? block.text ?? "";
2496
- return /* @__PURE__ */ jsxs6("div", { className: "a24-ui-block", children: [
2497
- /* @__PURE__ */ jsx10("h4", { children: title }),
2498
- /* @__PURE__ */ jsx10("p", { children: String(value) })
2812
+ return /* @__PURE__ */ jsxs7("div", { className: "a24-ui-block", children: [
2813
+ /* @__PURE__ */ jsx11("h4", { children: title }),
2814
+ /* @__PURE__ */ jsx11("p", { children: String(value) })
2499
2815
  ] }, String(block.id || blockIndex));
2500
2816
  }) }, `row-${rowIndex}`);
2501
2817
  })
@@ -2529,38 +2845,38 @@ function AgentResponseTimeline({
2529
2845
  if (segment.kind === "tool") {
2530
2846
  const isLatestActivity = segmentIndex === segments.length - 1;
2531
2847
  const showOrb = toolViewMode === "closed" ? isLoading && isLatestActivity : Boolean(activeToolId && (segment.part.id === activeToolId || segment.part.toolCallId === activeToolId));
2532
- rendered.push(/* @__PURE__ */ jsx10(AgentChatToolRow, { activeToolId, part: segment.part, showOrb, slots: toolSlots }, segment.part.id));
2848
+ rendered.push(/* @__PURE__ */ jsx11(AgentChatToolRow, { activeToolId, part: segment.part, showOrb, slots: toolSlots }, segment.part.id));
2533
2849
  continue;
2534
2850
  }
2535
2851
  if (segment.kind === "tool-session") {
2536
- rendered.push(/* @__PURE__ */ jsx10(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}`));
2852
+ rendered.push(/* @__PURE__ */ jsx11(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}`));
2537
2853
  continue;
2538
2854
  }
2539
2855
  if (segment.kind === "subagent") {
2540
- rendered.push(/* @__PURE__ */ jsx10(AgentChatSubagentGroup, { isLatestActivity: segmentIndex === segments.length - 1, isLive: isLoading, part: segment.part, slots: toolSlots }, segment.part.id));
2856
+ rendered.push(/* @__PURE__ */ jsx11(AgentChatSubagentGroup, { isLatestActivity: segmentIndex === segments.length - 1, isLive: isLoading, part: segment.part, slots: toolSlots }, segment.part.id));
2541
2857
  continue;
2542
2858
  }
2543
2859
  const part = segment.part;
2544
2860
  if (part.kind === "model-turn") {
2545
2861
  const key = activeActivityClaimed ? part.id : `${message.id}-active-activity`;
2546
2862
  activeActivityClaimed = true;
2547
- rendered.push(/* @__PURE__ */ jsx10(AgentChatActivityRow, { label: "Planning next moves", modelTurnId: part.modelTurnId }, key));
2863
+ rendered.push(/* @__PURE__ */ jsx11(AgentChatActivityRow, { renderActivityIcon: toolSlots?.renderActivityIcon, label: "Planning next moves", modelTurnId: part.modelTurnId }, key));
2548
2864
  continue;
2549
2865
  }
2550
2866
  if (part.kind === "node-activity") {
2551
2867
  const key = activeActivityClaimed ? part.id : `${message.id}-active-activity`;
2552
2868
  if (part.status === "running") activeActivityClaimed = true;
2553
2869
  rendered.push(
2554
- part.status === "running" ? /* @__PURE__ */ jsx10(AgentChatActivityRow, { label: part.label, onSelect: onNodeSelect ? () => onNodeSelect(part) : void 0 }, key) : /* @__PURE__ */ jsx10(AgentChatNodeActivityRow, { onSelect: onNodeSelect, part }, part.id)
2870
+ part.status === "running" ? /* @__PURE__ */ jsx11(AgentChatActivityRow, { renderActivityIcon: toolSlots?.renderActivityIcon, label: part.label, onSelect: onNodeSelect ? () => onNodeSelect(part) : void 0 }, key) : /* @__PURE__ */ jsx11(AgentChatNodeActivityRow, { renderActivityIcon: toolSlots?.renderActivityIcon, onSelect: onNodeSelect, part }, part.id)
2555
2871
  );
2556
2872
  continue;
2557
2873
  }
2558
2874
  if (part.kind === "ui-blocks" && renderUiBlocks) {
2559
- rendered.push(/* @__PURE__ */ jsx10(React9.Fragment, { children: renderUiBlocks(part) }, part.id));
2875
+ rendered.push(/* @__PURE__ */ jsx11(React12.Fragment, { children: renderUiBlocks(part) }, part.id));
2560
2876
  continue;
2561
2877
  }
2562
2878
  rendered.push(
2563
- /* @__PURE__ */ jsx10(React9.Fragment, { children: /* @__PURE__ */ jsx10(
2879
+ /* @__PURE__ */ jsx11(React12.Fragment, { children: /* @__PURE__ */ jsx11(
2564
2880
  AgentChatPart,
2565
2881
  {
2566
2882
  activeToolId,
@@ -2577,16 +2893,16 @@ function AgentResponseTimeline({
2577
2893
  );
2578
2894
  }
2579
2895
  if (rendered.length === 0 && message.content) {
2580
- rendered.push(/* @__PURE__ */ jsx10(MessageResponse, { children: message.content }, `${message.id}-fallback`));
2896
+ rendered.push(/* @__PURE__ */ jsx11(MessageResponse, { children: message.content }, `${message.id}-fallback`));
2581
2897
  }
2582
2898
  if (rendered.length === 0 && isLoading) {
2583
- rendered.push(/* @__PURE__ */ jsx10(AgentChatActivityRow, { label: "Thinking" }, `${message.id}-active-activity`));
2899
+ rendered.push(/* @__PURE__ */ jsx11(AgentChatActivityRow, { renderActivityIcon: toolSlots?.renderActivityIcon, label: "Thinking" }, `${message.id}-active-activity`));
2584
2900
  }
2585
- return /* @__PURE__ */ jsx10("div", { className: "space-y-3", children: rendered });
2901
+ return /* @__PURE__ */ jsx11("div", { className: "space-y-3", children: rendered });
2586
2902
  }
2587
2903
 
2588
2904
  // src/ui/agent-chat-message.tsx
2589
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
2905
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2590
2906
  var DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD = 360;
2591
2907
  var DEFAULT_USER_MESSAGE_COLLAPSED_LINES = 4;
2592
2908
  function shouldCollapseUserMessage(content, threshold = DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD) {
@@ -2599,8 +2915,8 @@ function AgentChatUserMessageContent({
2599
2915
  showLessLabel = "Show less",
2600
2916
  showMoreLabel = "Show more"
2601
2917
  }) {
2602
- const [isExpanded, setIsExpanded] = React10.useState(false);
2603
- const contentId = React10.useId();
2918
+ const [isExpanded, setIsExpanded] = React13.useState(false);
2919
+ const contentId = React13.useId();
2604
2920
  const shouldCollapse = shouldCollapseUserMessage(content, collapseThreshold);
2605
2921
  const Icon = isExpanded ? ChevronUp : ChevronDown3;
2606
2922
  const clampedStyle = shouldCollapse && !isExpanded ? {
@@ -2609,9 +2925,9 @@ function AgentChatUserMessageContent({
2609
2925
  display: "-webkit-box",
2610
2926
  overflow: "hidden"
2611
2927
  } : void 0;
2612
- return /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-col gap-3", children: [
2613
- /* @__PURE__ */ jsx11("div", { id: contentId, className: "min-w-0", style: clampedStyle, children: content }),
2614
- shouldCollapse ? /* @__PURE__ */ jsxs7(
2928
+ return /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-col gap-3", children: [
2929
+ /* @__PURE__ */ jsx12("div", { id: contentId, className: "min-w-0", style: clampedStyle, children: content }),
2930
+ shouldCollapse ? /* @__PURE__ */ jsxs8(
2615
2931
  "button",
2616
2932
  {
2617
2933
  "aria-controls": contentId,
@@ -2624,7 +2940,7 @@ function AgentChatUserMessageContent({
2624
2940
  type: "button",
2625
2941
  children: [
2626
2942
  isExpanded ? showLessLabel : showMoreLabel,
2627
- /* @__PURE__ */ jsx11(Icon, { "aria-hidden": "true", className: "size-4", strokeWidth: 2 })
2943
+ /* @__PURE__ */ jsx12(Icon, { "aria-hidden": "true", className: "size-4", strokeWidth: 2 })
2628
2944
  ]
2629
2945
  }
2630
2946
  ) : null
@@ -2634,9 +2950,9 @@ function AgentChatUserMessageActionRow({
2634
2950
  children,
2635
2951
  className
2636
2952
  }) {
2637
- const rowRef = React10.useRef(null);
2638
- const [visible, setVisible] = React10.useState(false);
2639
- React10.useEffect(() => {
2953
+ const rowRef = React13.useRef(null);
2954
+ const [visible, setVisible] = React13.useState(false);
2955
+ React13.useEffect(() => {
2640
2956
  const message = rowRef.current?.closest('[data-slot="message"]');
2641
2957
  if (!message) return;
2642
2958
  const show = () => setVisible(true);
@@ -2655,7 +2971,7 @@ function AgentChatUserMessageActionRow({
2655
2971
  message.removeEventListener("focusout", hideWhenFocusLeaves);
2656
2972
  };
2657
2973
  }, []);
2658
- return /* @__PURE__ */ jsx11(
2974
+ return /* @__PURE__ */ jsx12(
2659
2975
  "div",
2660
2976
  {
2661
2977
  ref: rowRef,
@@ -2694,7 +3010,7 @@ function AgentChatMessage({
2694
3010
  const resolvedUserContent = userMessageContent ?? message.content;
2695
3011
  const shouldRenderContent = isAssistant || Boolean(renderUserContent || resolvedUserContent);
2696
3012
  const showAttachments = Boolean(message.attachments?.length) && (message.role === "user" || showAssistantAttachments);
2697
- const renderedActions = actions ?? (actionHandlers ? /* @__PURE__ */ jsx11(
3013
+ const renderedActions = actions ?? (actionHandlers ? /* @__PURE__ */ jsx12(
2698
3014
  AgentChatDefaultActions,
2699
3015
  {
2700
3016
  additionalActions,
@@ -2704,14 +3020,14 @@ function AgentChatMessage({
2704
3020
  }
2705
3021
  ) : null);
2706
3022
  const defaultUserActions = !actions && Boolean(actionHandlers) && !isAssistant;
2707
- return /* @__PURE__ */ jsxs7(
3023
+ return /* @__PURE__ */ jsxs8(
2708
3024
  ChatMessage,
2709
3025
  {
2710
3026
  className,
2711
3027
  dir,
2712
3028
  from: message.role,
2713
3029
  children: [
2714
- showAttachments ? /* @__PURE__ */ jsx11(
3030
+ showAttachments ? /* @__PURE__ */ jsx12(
2715
3031
  ChatAttachmentRows,
2716
3032
  {
2717
3033
  attachments: message.attachments || [],
@@ -2720,14 +3036,14 @@ function AgentChatMessage({
2720
3036
  resolveContent: attachmentContentResolver
2721
3037
  }
2722
3038
  ) : null,
2723
- shouldRenderContent ? /* @__PURE__ */ jsxs7(
3039
+ shouldRenderContent ? /* @__PURE__ */ jsxs8(
2724
3040
  ChatMessageContent,
2725
3041
  {
2726
3042
  className: cn(isAssistant ? "bg-transparent p-0" : void 0, contentClassName),
2727
3043
  dir,
2728
3044
  from: message.role,
2729
3045
  children: [
2730
- isAssistant ? renderAssistantContent ? renderAssistantContent({ isStreaming, message }) : /* @__PURE__ */ jsx11(
3046
+ isAssistant ? renderAssistantContent ? renderAssistantContent({ isStreaming, message }) : /* @__PURE__ */ jsx12(
2731
3047
  Timeline,
2732
3048
  {
2733
3049
  getHitlActionState,
@@ -2738,7 +3054,7 @@ function AgentChatMessage({
2738
3054
  parts: message.parts
2739
3055
  }
2740
3056
  ) : null,
2741
- !isAssistant ? renderUserContent ? renderUserContent({ message }) : resolvedUserContent ? /* @__PURE__ */ jsx11(
3057
+ !isAssistant ? renderUserContent ? renderUserContent({ message }) : resolvedUserContent ? /* @__PURE__ */ jsx12(
2742
3058
  AgentChatUserMessageContent,
2743
3059
  {
2744
3060
  collapsedLines: userMessageCollapsedLines,
@@ -2751,108 +3067,7 @@ function AgentChatMessage({
2751
3067
  ]
2752
3068
  }
2753
3069
  ) : null,
2754
- renderedActions ? defaultUserActions ? /* @__PURE__ */ jsx11(AgentChatUserMessageActionRow, { className: actionsClassName, children: renderedActions }) : /* @__PURE__ */ jsx11("div", { className: cn(dir === "rtl" ? "mt-1 h-9 w-full" : "mt-1 h-9 w-full -translate-x-2", actionsClassName), children: renderedActions }) : null
2755
- ]
2756
- }
2757
- );
2758
- }
2759
-
2760
- // src/ui/agent-chat-context-status.tsx
2761
- import * as React11 from "react";
2762
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2763
- var COMPACT_TOKENS = new Intl.NumberFormat("en-US", { notation: "compact" });
2764
- var PERCENT = new Intl.NumberFormat("en-US", {
2765
- style: "percent",
2766
- maximumFractionDigits: 1
2767
- });
2768
- var EXACT_SOURCES = /* @__PURE__ */ new Set(["exact", "provider_usage"]);
2769
- function contextStatusMetrics(contextWindow) {
2770
- const maximum = Number(contextWindow?.max_tokens || 0);
2771
- const input = Math.max(0, Number(contextWindow?.input_tokens || 0));
2772
- const admissionInput = Math.max(0, Number(contextWindow?.admission_input_tokens ?? input));
2773
- const usageRatio = Math.max(0, Math.min(1, Number(
2774
- contextWindow?.usage_ratio ?? input / maximum
2775
- )));
2776
- const admissionRatio = Math.max(0, Math.min(1, Number(
2777
- contextWindow?.admission_ratio ?? admissionInput / maximum
2778
- )));
2779
- return { maximum, input, admissionInput, usageRatio, admissionRatio };
2780
- }
2781
- function AgentChatContextStatus({
2782
- className,
2783
- contextWindow
2784
- }) {
2785
- const tooltipId = React11.useId();
2786
- const [open, setOpen] = React11.useState(false);
2787
- const { maximum, input, admissionInput, usageRatio, admissionRatio } = contextStatusMetrics(contextWindow);
2788
- const percentage = Math.round(usageRatio * 100);
2789
- const exact = EXACT_SOURCES.has(contextWindow?.source || "unknown");
2790
- if (!Number.isFinite(maximum) || maximum <= 0) return null;
2791
- return /* @__PURE__ */ jsxs8(
2792
- "div",
2793
- {
2794
- className: cn("a24-context-status", className),
2795
- onBlurCapture: (event) => {
2796
- if (!event.currentTarget.contains(event.relatedTarget)) setOpen(false);
2797
- },
2798
- onFocusCapture: () => setOpen(true),
2799
- onMouseEnter: () => setOpen(true),
2800
- onMouseLeave: () => setOpen(false),
2801
- children: [
2802
- /* @__PURE__ */ jsxs8(
2803
- "button",
2804
- {
2805
- "aria-describedby": open ? tooltipId : void 0,
2806
- "aria-label": `Context window ${percentage}% used`,
2807
- className: "a24-context-status__trigger",
2808
- onKeyDown: (event) => {
2809
- if (event.key === "Escape") setOpen(false);
2810
- },
2811
- type: "button",
2812
- children: [
2813
- /* @__PURE__ */ jsxs8("svg", { "aria-hidden": "true", className: "a24-context-status__ring", viewBox: "0 0 24 24", children: [
2814
- /* @__PURE__ */ jsx12("circle", { className: "a24-context-status__track", cx: "12", cy: "12", r: "10" }),
2815
- /* @__PURE__ */ jsx12(
2816
- "circle",
2817
- {
2818
- className: "a24-context-status__value",
2819
- cx: "12",
2820
- cy: "12",
2821
- pathLength: "100",
2822
- r: "10",
2823
- strokeDasharray: `${percentage} 100`
2824
- }
2825
- )
2826
- ] }),
2827
- /* @__PURE__ */ jsxs8("span", { children: [
2828
- percentage,
2829
- "%"
2830
- ] })
2831
- ]
2832
- }
2833
- ),
2834
- open ? /* @__PURE__ */ jsxs8("div", { className: "a24-context-status__popover", id: tooltipId, role: "tooltip", children: [
2835
- /* @__PURE__ */ jsx12("span", { className: "a24-context-status__sr-only", children: "Context window" }),
2836
- /* @__PURE__ */ jsxs8("strong", { children: [
2837
- PERCENT.format(usageRatio),
2838
- " context used"
2839
- ] }),
2840
- /* @__PURE__ */ jsxs8("span", { children: [
2841
- exact ? "Input" : "Estimated input",
2842
- ": ",
2843
- COMPACT_TOKENS.format(input),
2844
- " / ",
2845
- COMPACT_TOKENS.format(maximum),
2846
- " tokens"
2847
- ] }),
2848
- admissionInput !== input ? /* @__PURE__ */ jsxs8("span", { children: [
2849
- "Safety bound: ",
2850
- COMPACT_TOKENS.format(admissionInput),
2851
- " tokens (",
2852
- PERCENT.format(admissionRatio),
2853
- ")"
2854
- ] }) : null
2855
- ] }) : null
3070
+ renderedActions ? defaultUserActions ? /* @__PURE__ */ jsx12(AgentChatUserMessageActionRow, { className: actionsClassName, children: renderedActions }) : /* @__PURE__ */ jsx12("div", { className: cn(dir === "rtl" ? "mt-1 h-9 w-full" : "mt-1 h-9 w-full -translate-x-2", actionsClassName), children: renderedActions }) : null
2856
3071
  ]
2857
3072
  }
2858
3073
  );
@@ -3082,68 +3297,6 @@ function McpConnectRequiredCard({
3082
3297
  }
3083
3298
  );
3084
3299
  }
3085
-
3086
- // src/ui/use-audio-recorder.ts
3087
- import * as React12 from "react";
3088
- var RECORDING_CANDIDATES = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4"];
3089
- function recordingExtension(mimeType) {
3090
- return mimeType.startsWith("audio/mp4") ? "m4a" : "webm";
3091
- }
3092
- function useAudioRecorder(onRecorded, options = {}) {
3093
- const recorderRef = React12.useRef(null);
3094
- const streamRef = React12.useRef(null);
3095
- const onRecordedRef = React12.useRef(onRecorded);
3096
- const onStreamChangeRef = React12.useRef(options.onStreamChange);
3097
- onRecordedRef.current = onRecorded;
3098
- onStreamChangeRef.current = options.onStreamChange;
3099
- const [isRecording, setIsRecording] = React12.useState(false);
3100
- const [error, setError] = React12.useState(null);
3101
- const stop = React12.useCallback(() => recorderRef.current?.stop(), []);
3102
- const start = React12.useCallback(async () => {
3103
- setError(null);
3104
- if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === "undefined") {
3105
- setError("Audio recording is not supported in this browser.");
3106
- return;
3107
- }
3108
- try {
3109
- const mimeType = RECORDING_CANDIDATES.find((candidate) => MediaRecorder.isTypeSupported(candidate));
3110
- if (!mimeType) {
3111
- setError("Audio recording is not supported in this browser.");
3112
- return;
3113
- }
3114
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
3115
- onStreamChangeRef.current?.(stream);
3116
- const recorder = new MediaRecorder(stream, { mimeType });
3117
- const chunks = [];
3118
- recorder.ondataavailable = (event) => {
3119
- if (event.data.size) chunks.push(event.data);
3120
- };
3121
- recorder.onstop = () => {
3122
- const actualType = recorder.mimeType || "audio/webm";
3123
- onRecordedRef.current(new File([new Blob(chunks, { type: actualType })], `recording-${Date.now()}.${recordingExtension(actualType)}`, { type: actualType }));
3124
- stream.getTracks().forEach((track) => track.stop());
3125
- onStreamChangeRef.current?.(null);
3126
- recorderRef.current = null;
3127
- streamRef.current = null;
3128
- setIsRecording(false);
3129
- };
3130
- recorderRef.current = recorder;
3131
- streamRef.current = stream;
3132
- recorder.start();
3133
- setIsRecording(true);
3134
- } catch {
3135
- streamRef.current?.getTracks().forEach((track) => track.stop());
3136
- onStreamChangeRef.current?.(null);
3137
- setError("Microphone access is unavailable.");
3138
- }
3139
- }, []);
3140
- React12.useEffect(() => () => {
3141
- recorderRef.current?.stop();
3142
- streamRef.current?.getTracks().forEach((track) => track.stop());
3143
- onStreamChangeRef.current?.(null);
3144
- }, []);
3145
- return { error, isRecording, start, stop };
3146
- }
3147
3300
  export {
3148
3301
  AgentChatActivityRow,
3149
3302
  AgentChatComposer,
@@ -3221,6 +3374,7 @@ export {
3221
3374
  resolvedHitlLabel,
3222
3375
  revokeAgentChatComposerFiles,
3223
3376
  shouldCollapseUserMessage,
3377
+ useAgentChatComposer,
3224
3378
  useAudioRecorder,
3225
3379
  useChatComposerFocus,
3226
3380
  useCompletionActionEntrance,