@agents24/chat-react 0.5.30 → 0.5.32

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 (45) hide show
  1. package/README.md +9 -4
  2. package/dist/generated/delegation-types.d.ts +129 -0
  3. package/dist/registry/agents24-chat.json +37 -19
  4. package/dist/runtime/index.cjs +52 -0
  5. package/dist/runtime/index.cjs.map +1 -1
  6. package/dist/runtime/index.d.ts +2 -0
  7. package/dist/runtime/index.js +51 -0
  8. package/dist/runtime/index.js.map +1 -1
  9. package/dist/runtime/use-delegation-tasks.d.ts +20 -0
  10. package/dist/scaffold/app.d.ts +2 -1
  11. package/dist/scaffold/components/chat/chat-composer.d.ts +4 -1
  12. package/dist/scaffold/components/chat/subagent-tasks.d.ts +3 -0
  13. package/dist/scaffold/components/theme-provider.d.ts +4 -3
  14. package/dist/scaffold/index.cjs +425 -280
  15. package/dist/scaffold/index.cjs.map +1 -1
  16. package/dist/scaffold/index.d.ts +3 -2
  17. package/dist/scaffold/index.js +416 -271
  18. package/dist/scaffold/index.js.map +1 -1
  19. package/dist/styles.css +14 -0
  20. package/dist/ui/adapters.d.ts +5 -2
  21. package/dist/ui/agent-chat-composer.d.ts +2 -1
  22. package/dist/ui/agent-chat-queue.d.ts +0 -2
  23. package/dist/ui/index.cjs +306 -260
  24. package/dist/ui/index.cjs.map +1 -1
  25. package/dist/ui/index.js +308 -263
  26. package/dist/ui/index.js.map +1 -1
  27. package/dist/ui/tool-sessions.d.ts +10 -2
  28. package/dist/ui/use-agent-chat-composer.d.ts +25 -26
  29. package/dist/ui/use-audio-recorder.d.ts +4 -2
  30. package/package.json +4 -3
  31. package/scaffold/host-styles.css +3 -0
  32. package/scaffold/manifest.json +2 -2
  33. package/scaffold/src/app.tsx +4 -1
  34. package/scaffold/src/chat.css +11 -1
  35. package/scaffold/src/components/chat/chat-composer.tsx +36 -19
  36. package/scaffold/src/components/chat/chat-shell.tsx +5 -4
  37. package/scaffold/src/components/chat/external-link-dialog.tsx +6 -5
  38. package/scaffold/src/components/chat/identity-control.tsx +0 -1
  39. package/scaffold/src/components/chat/subagent-tasks.tsx +109 -0
  40. package/scaffold/src/components/theme-provider.tsx +30 -27
  41. package/scaffold/src/components/ui/tooltip.tsx +3 -0
  42. package/scaffold/src/host.css +11 -0
  43. package/scaffold/src/index.css +1 -213
  44. package/scaffold/src/index.tsx +4 -2
  45. package/scaffold/src/theme.css +215 -0
package/dist/ui/index.js CHANGED
@@ -4,8 +4,8 @@ import {
4
4
  } from "../chunk-5PWUQTLK.js";
5
5
  import {
6
6
  attachmentFileAccept,
7
+ canonicalInputMimeType,
7
8
  inputMimeTypes,
8
- normalizeAttachmentMimeType,
9
9
  validateAgentChatSubmission
10
10
  } from "../chunk-INO5DSSJ.js";
11
11
 
@@ -489,6 +489,7 @@ function attachmentRenderKey(data, index, rowKind) {
489
489
  function ChatImageAttachment({
490
490
  className,
491
491
  data,
492
+ presentation = "message",
492
493
  onRemove,
493
494
  resolveContent,
494
495
  ...props
@@ -568,7 +569,7 @@ function ChatImageAttachment({
568
569
  Attachment,
569
570
  {
570
571
  ...props,
571
- className: cn("a24-chat-image-attachment", className),
572
+ className: cn("a24-chat-image-attachment", presentation === "composer" && "a24-composer-attachment-card", className),
572
573
  orientation: "vertical",
573
574
  ref: rootRef,
574
575
  state: status === "error" ? "error" : status === "done" ? "done" : "processing",
@@ -591,6 +592,7 @@ function ChatImageAttachment({
591
592
  status !== "done" && status !== "error" ? React.createElement("span", { className: "sr-only", role: "status" }, `Loading ${filename}`) : null,
592
593
  status === "error" ? React.createElement("span", { className: "sr-only" }, `${filename} preview unavailable`) : null
593
594
  ),
595
+ presentation === "composer" ? React.createElement("span", { className: "a24-composer-image-caption", title: filename }, filename) : null,
594
596
  onRemove ? React.createElement(
595
597
  "button",
596
598
  {
@@ -609,6 +611,7 @@ function ChatImageAttachment({
609
611
  function ChatAttachment({
610
612
  className,
611
613
  data,
614
+ presentation = "message",
612
615
  onRemove,
613
616
  orientation,
614
617
  resolveContent,
@@ -621,6 +624,7 @@ function ChatAttachment({
621
624
  ...props,
622
625
  className,
623
626
  data,
627
+ presentation,
624
628
  onRemove,
625
629
  resolveContent
626
630
  });
@@ -628,11 +632,16 @@ function ChatAttachment({
628
632
  return React.createElement(
629
633
  Attachment,
630
634
  {
631
- className: cn("a24-chat-attachment", className),
635
+ className: cn("a24-chat-attachment", presentation === "composer" && "a24-composer-attachment-card", className),
632
636
  orientation: orientation || "horizontal",
633
637
  size: "sm",
634
638
  ...props
635
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,
636
645
  React.createElement(
637
646
  AttachmentMedia,
638
647
  Object.assign(
@@ -690,6 +699,7 @@ function ChatAttachments({
690
699
  }
691
700
  function ChatAttachmentRows({
692
701
  attachments,
702
+ layout = "message",
693
703
  className,
694
704
  dir,
695
705
  onRemove,
@@ -705,6 +715,7 @@ function ChatAttachmentRows({
705
715
  items.map(
706
716
  (attachment, index) => React.createElement(ChatAttachment, {
707
717
  data: attachment,
718
+ presentation: layout,
708
719
  dir: "ltr",
709
720
  key: attachmentRenderKey(attachment, index, kind),
710
721
  onRemove: onRemove ? () => onRemove(attachment) : void 0,
@@ -715,12 +726,12 @@ function ChatAttachmentRows({
715
726
  return React.createElement(
716
727
  "div",
717
728
  {
718
- className: cn("a24-chat-attachment-rows", className),
729
+ className: cn("a24-chat-attachment-rows", layout === "composer" && "a24-composer-attachment-row", className),
719
730
  dir,
720
731
  ...props
721
732
  },
722
- renderRow(imageAttachments, "images"),
723
- renderRow(fileAttachments, "files")
733
+ layout === "composer" ? renderRow(attachments, "mixed") : renderRow(imageAttachments, "images"),
734
+ layout === "message" ? renderRow(fileAttachments, "files") : null
724
735
  );
725
736
  }
726
737
 
@@ -728,7 +739,7 @@ function ChatAttachmentRows({
728
739
  import * as React4 from "react";
729
740
 
730
741
  // src/ui/composer-attachments.ts
731
- import { normalizeAttachmentMimeType as normalizeAttachmentMimeType2 } from "@agents24/client";
742
+ import { normalizeAttachmentMimeType } from "@agents24/client";
732
743
  var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
733
744
  function createAgentChatComposerFile(file) {
734
745
  return {
@@ -736,7 +747,7 @@ function createAgentChatComposerFile(file) {
736
747
  type: "file",
737
748
  url: URL.createObjectURL(file),
738
749
  filename: file.name || "attachment",
739
- mediaType: normalizeAttachmentMimeType2(file.type, file.name),
750
+ mediaType: normalizeAttachmentMimeType(file.type, file.name),
740
751
  source: file
741
752
  };
742
753
  }
@@ -791,32 +802,38 @@ function useChatComposerFocus({ textareaRef, disabled, focusKey, restoredDraft,
791
802
  // src/ui/use-audio-recorder.ts
792
803
  import * as React3 from "react";
793
804
  var RECORDING_CANDIDATES = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4"];
794
- function recordingExtension(mimeType) {
795
- return mimeType.startsWith("audio/mp4") ? "m4a" : "webm";
796
- }
797
805
  function useAudioRecorder(onRecorded, options = {}) {
798
- const mountedRef = React3.useRef(true);
799
- const startingRef = React3.useRef(false);
800
806
  const recorderRef = React3.useRef(null);
801
807
  const streamRef = React3.useRef(null);
808
+ const pendingRef = React3.useRef(false);
809
+ const generationRef = React3.useRef(0);
802
810
  const onRecordedRef = React3.useRef(onRecorded);
803
811
  const onStreamChangeRef = React3.useRef(options.onStreamChange);
804
- onRecordedRef.current = onRecorded;
805
- onStreamChangeRef.current = options.onStreamChange;
812
+ React3.useEffect(() => {
813
+ onRecordedRef.current = onRecorded;
814
+ onStreamChangeRef.current = options.onStreamChange;
815
+ });
806
816
  const [isRecording, setIsRecording] = React3.useState(false);
807
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
+ }, []);
808
824
  const stop = React3.useCallback(() => {
809
- if (recorderRef.current && recorderRef.current.state !== "inactive") recorderRef.current.stop();
825
+ const recorder = recorderRef.current;
826
+ if (recorder && recorder.state !== "inactive") recorder.stop();
810
827
  }, []);
811
828
  const start = React3.useCallback(async () => {
812
- if (startingRef.current || recorderRef.current) return;
813
- startingRef.current = true;
829
+ if (pendingRef.current || recorderRef.current) return;
814
830
  setError(null);
815
831
  if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === "undefined") {
816
832
  setError("Audio recording is not supported in this browser.");
817
- startingRef.current = false;
818
833
  return;
819
834
  }
835
+ pendingRef.current = true;
836
+ const generation = generationRef.current;
820
837
  try {
821
838
  const mimeType = RECORDING_CANDIDATES.find((candidate) => MediaRecorder.isTypeSupported(candidate));
822
839
  if (!mimeType) {
@@ -824,158 +841,169 @@ function useAudioRecorder(onRecorded, options = {}) {
824
841
  return;
825
842
  }
826
843
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
827
- if (!mountedRef.current) {
844
+ if (generation !== generationRef.current) {
828
845
  stream.getTracks().forEach((track) => track.stop());
829
846
  return;
830
847
  }
831
848
  streamRef.current = stream;
832
- onStreamChangeRef.current?.(stream);
833
849
  const recorder = new MediaRecorder(stream, { mimeType });
850
+ recorderRef.current = recorder;
834
851
  const chunks = [];
835
852
  recorder.ondataavailable = (event) => {
836
853
  if (event.data.size) chunks.push(event.data);
837
854
  };
838
855
  recorder.onstop = () => {
839
- const actualType = recorder.mimeType || "audio/webm";
840
- if (mountedRef.current) onRecordedRef.current(new File([new Blob(chunks, { type: actualType })], `recording-${Date.now()}.${recordingExtension(actualType)}`, { type: actualType }));
841
- stream.getTracks().forEach((track) => track.stop());
842
- onStreamChangeRef.current?.(null);
843
- recorderRef.current = null;
844
- streamRef.current = null;
845
- if (mountedRef.current) setIsRecording(false);
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.");
846
872
  };
847
- recorderRef.current = recorder;
848
- streamRef.current = stream;
849
873
  recorder.start();
850
874
  setIsRecording(true);
875
+ onStreamChangeRef.current?.(stream);
851
876
  } catch {
852
- streamRef.current?.getTracks().forEach((track) => track.stop());
853
- onStreamChangeRef.current?.(null);
854
- streamRef.current = null;
855
- recorderRef.current = null;
856
- if (mountedRef.current) setError("Microphone access is unavailable.");
877
+ if (generation === generationRef.current) {
878
+ release();
879
+ setIsRecording(false);
880
+ setError("Microphone access is unavailable.");
881
+ }
857
882
  } finally {
858
- startingRef.current = false;
859
- }
860
- }, []);
861
- React3.useEffect(() => {
862
- mountedRef.current = true;
863
- return () => {
864
- mountedRef.current = false;
865
- if (recorderRef.current && recorderRef.current.state !== "inactive") recorderRef.current.stop();
866
- streamRef.current?.getTracks().forEach((track) => track.stop());
867
- onStreamChangeRef.current?.(null);
868
- };
869
- }, []);
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]);
870
897
  return { error, isRecording, start, stop };
871
898
  }
872
899
 
873
900
  // src/ui/use-agent-chat-composer.ts
874
- function useAgentChatComposer({
875
- disabled = false,
876
- isRunning = false,
877
- isCancelling = false,
878
- inputContract,
879
- recordingEnabled,
880
- focusKey,
881
- restoredDraft,
882
- textareaRef,
883
- onSubmit
884
- }) {
885
- const [text, setDraftText] = React4.useState("");
886
- const [files, setDraftFiles] = React4.useState([]);
887
- const [isSubmitting, setIsSubmitting] = React4.useState(false);
901
+ function useAgentChatComposer(options) {
902
+ const [text, setTextState] = React4.useState("");
903
+ const [files, setFiles] = React4.useState([]);
888
904
  const [attachmentError, setAttachmentError] = React4.useState(null);
889
- const draftRef = React4.useRef({ text, files, revision: 0 });
890
- const inFlightRef = React4.useRef([]);
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);
891
911
  const submittingRef = React4.useRef(false);
892
- const mountedRef = React4.useRef(true);
893
- const updateDraft = (nextText, nextFiles) => {
894
- draftRef.current = { text: nextText, files: nextFiles, revision: draftRef.current.revision + 1 };
895
- setDraftText(nextText);
896
- setDraftFiles(nextFiles);
897
- };
898
912
  React4.useEffect(() => {
899
- mountedRef.current = true;
900
- return () => {
901
- mountedRef.current = false;
902
- revokeAgentChatComposerFiles(draftRef.current.files);
903
- revokeAgentChatComposerFiles(inFlightRef.current);
904
- };
913
+ filesRef.current = files;
914
+ }, [files]);
915
+ React4.useEffect(() => () => {
916
+ revokeAgentChatComposerFiles(filesRef.current);
917
+ revokeAgentChatComposerFiles(inFlightFilesRef.current);
905
918
  }, []);
906
- const focus = useChatComposerFocus({ textareaRef, disabled, focusKey, restoredDraft, onRestore: (draft) => {
907
- revokeAgentChatComposerFiles(draftRef.current.files.filter((file) => !draft.files.includes(file)));
908
- updateDraft(draft.text, draft.files);
909
- } });
910
- const accept = inputContract ? inputMimeTypes(inputContract) : null;
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
+ };
911
936
  const addFiles = (selected) => {
912
- const accepted = accept ? selected.filter((file) => accept.includes(normalizeAttachmentMimeType(file.type, file.name))) : 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]);
913
941
  setAttachmentError(accepted.length === selected.length ? null : "One or more selected file types are not supported by this Agent.");
914
- updateDraft(draftRef.current.text, [...draftRef.current.files, ...accepted.map(createAgentChatComposerFile)]);
915
942
  };
916
- const recordedRef = React4.useRef(addFiles);
917
- recordedRef.current = addFiles;
918
- const recorder = useAudioRecorder((file) => {
919
- if (mountedRef.current) recordedRef.current([file]);
920
- });
921
- const recordingAllowed = inputContract ? inputContract.modalities.audio.enabled && inputContract.modalities.audio.recording_enabled && recordingEnabled !== false : recordingEnabled === true;
922
- const validationError = inputContract ? validateAgentChatSubmission(
923
- inputContract,
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,
924
952
  text,
925
- files.map((file) => ({ data: file.source || new Uint8Array(), mediaType: file.mediaType, name: file.filename, byteSize: file.source ? void 0 : file.byteSize }))
953
+ files.map((file) => ({ data: file.source || new Uint8Array(), mediaType: file.mediaType, name: file.filename, byteSize: file.byteSize }))
926
954
  ) : null;
927
- const canSubmit = !disabled && !isCancelling && !isSubmitting && !recorder.isRecording && !validationError && Boolean(text.trim() || files.length);
955
+ const canSubmit = Boolean(text.trim() || files.length) && !options.disabled && !options.isCancelling && !isSubmitting && !recorder.isRecording && !validationError;
928
956
  const submit = async () => {
929
957
  if (!canSubmit || submittingRef.current) return;
930
- const submitted = draftRef.current;
958
+ const value = text;
959
+ const selectedFiles = files;
960
+ const clearedRevision = ++revisionRef.current;
931
961
  submittingRef.current = true;
932
- inFlightRef.current = submitted.files;
933
962
  setIsSubmitting(true);
934
- updateDraft("", []);
935
- const clearedRevision = draftRef.current.revision;
936
- focus();
963
+ inFlightFilesRef.current = selectedFiles;
964
+ setTextState("");
965
+ setFiles([]);
966
+ focusComposer();
937
967
  try {
938
- await onSubmit({
939
- text: submitted.text,
940
- files: submitted.files,
941
- attachmentIds: submitted.files.flatMap((file) => file.persistedAttachmentId ? [file.persistedAttachmentId] : [])
942
- });
943
- revokeAgentChatComposerFiles(submitted.files);
968
+ await options.onSubmit(value, selectedFiles);
969
+ revokeAgentChatComposerFiles(selectedFiles);
944
970
  } catch {
945
- if (mountedRef.current && draftRef.current.revision === clearedRevision) updateDraft(submitted.text, submitted.files);
946
- else revokeAgentChatComposerFiles(submitted.files);
971
+ if (revisionRef.current === clearedRevision) {
972
+ revisionRef.current += 1;
973
+ setTextState(value);
974
+ setFiles(selectedFiles);
975
+ } else revokeAgentChatComposerFiles(selectedFiles);
947
976
  } finally {
948
- inFlightRef.current = [];
977
+ inFlightFilesRef.current = [];
949
978
  submittingRef.current = false;
950
- if (mountedRef.current) setIsSubmitting(false);
979
+ setIsSubmitting(false);
951
980
  }
952
981
  };
953
982
  return {
954
983
  text,
984
+ setText,
955
985
  files,
956
- isSubmitting,
986
+ addFiles,
987
+ removeFile,
988
+ textareaRef,
989
+ submit,
957
990
  canSubmit,
958
- validationError,
991
+ isSubmitting,
959
992
  attachmentError,
960
- recordingAllowed,
993
+ attachmentMimeTypes,
994
+ validationError,
961
995
  recorder,
962
- accept,
963
- fileAccept: accept ? attachmentFileAccept(accept) : void 0,
964
- allowAttachments: accept ? accept.length > 0 : true,
965
- action: chatComposerAction({ hasDraft: Boolean(text.trim() || files.length), isRunning, isCancelling }),
966
- setText: (value) => updateDraft(value, draftRef.current.files),
967
- addFiles,
968
- removeFile: (id) => {
969
- revokeAgentChatComposerFiles(draftRef.current.files.filter((file) => file.id === id));
970
- updateDraft(draftRef.current.text, draftRef.current.files.filter((file) => file.id !== id));
971
- },
972
- submit,
996
+ recordingAllowed: options.recordingEnabled ?? options.inputContract?.modalities.audio.recording_enabled ?? false,
997
+ fileAccept: attachmentFileAccept(attachmentMimeTypes),
973
998
  onKeyDown: (event) => {
974
999
  if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
975
1000
  event.preventDefault();
976
1001
  void submit();
977
1002
  }
978
- }
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) })
979
1007
  };
980
1008
  }
981
1009
 
@@ -1094,7 +1122,6 @@ function AgentChatComposer({
1094
1122
  contextWindow,
1095
1123
  accept,
1096
1124
  allowAttachments = true,
1097
- attachmentLayout = "flow",
1098
1125
  className,
1099
1126
  disabled = false,
1100
1127
  forceExpanded = false,
@@ -1120,7 +1147,7 @@ function AgentChatComposer({
1120
1147
  focusKey,
1121
1148
  restoredDraft,
1122
1149
  textareaRef: localTextareaRef,
1123
- onSubmit: (input) => onSubmit({ ...input, text: input.text.trim() })
1150
+ onSubmit: (text2, files2) => onSubmit({ text: text2.trim(), files: files2, attachmentIds: files2.flatMap((file) => file.id ? [file.id] : []) })
1124
1151
  });
1125
1152
  const { text, files, setText, isSubmitting, canSubmit, action, submit } = composer;
1126
1153
  const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
@@ -1156,26 +1183,8 @@ function AgentChatComposer({
1156
1183
  event.currentTarget.value = "";
1157
1184
  };
1158
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: [
1159
- files.length > 0 ? /* @__PURE__ */ jsx4(
1160
- "div",
1161
- {
1162
- "data-testid": "agent-chat-composer-attachments",
1163
- "data-layout": attachmentLayout,
1164
- className: cn(
1165
- "a24-composer__attachments min-w-0 py-1",
1166
- 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"
1167
- ),
1168
- children: /* @__PURE__ */ jsx4(
1169
- ChatAttachmentRows,
1170
- {
1171
- attachments: files,
1172
- onRemove: (attachment) => attachment.id && composer.removeFile(attachment.id)
1173
- }
1174
- )
1175
- }
1176
- ) : null,
1177
1186
  composer.attachmentError || composer.recorder.error ? /* @__PURE__ */ jsx4("p", { role: "alert", children: composer.attachmentError || composer.recorder.error }) : null,
1178
- /* @__PURE__ */ jsx4(
1187
+ /* @__PURE__ */ jsxs2(
1179
1188
  "div",
1180
1189
  {
1181
1190
  "data-expanded": isExpanded ? "" : void 0,
@@ -1183,118 +1192,129 @@ function AgentChatComposer({
1183
1192
  "a24-composer__surface relative w-full border border-input bg-background shadow-sm",
1184
1193
  isExpanded ? "rounded-xl p-2 pb-1.5" : "rounded-xl px-2 py-[3px]"
1185
1194
  ),
1186
- children: /* @__PURE__ */ jsxs2(
1187
- "div",
1188
- {
1189
- className: "a24-composer__grid w-full",
1190
- style: {
1191
- alignItems: "center",
1192
- display: "grid",
1193
- gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
1194
- gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
1195
- gap: isExpanded ? "3px 10px" : "8px"
1196
- },
1197
- children: [
1198
- /* @__PURE__ */ jsxs2("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
1199
- /* @__PURE__ */ jsx4(
1200
- "input",
1201
- {
1202
- ref: fileInputRef,
1203
- accept: composer.fileAccept ?? accept,
1204
- "aria-label": "Attach files",
1205
- className: "hidden",
1206
- multiple: true,
1207
- name: "attachments",
1208
- onChange: handleFilesChange,
1209
- type: "file"
1210
- }
1211
- ),
1212
- /* @__PURE__ */ jsx4(
1213
- "button",
1214
- {
1215
- "aria-label": "Attach files",
1216
- className: cn("a24-composer__attach", pillButtonClass),
1217
- disabled: disabled || isSubmitting || !allowAttachments || !composer.allowAttachments,
1218
- onClick: () => fileInputRef.current?.click(),
1219
- title: "Attach files",
1220
- type: "button",
1221
- children: /* @__PURE__ */ jsx4(Plus, { className: "size-4" })
1222
- }
1223
- )
1224
- ] }),
1225
- /* @__PURE__ */ jsx4("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ jsx4(
1226
- "textarea",
1227
- {
1228
- ref: setTextareaRef,
1229
- "aria-label": "Message",
1230
- autoComplete: "off",
1231
- className: cn(
1232
- "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",
1233
- isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
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
+ }
1234
1230
  ),
1235
- disabled,
1236
- name: "message",
1237
- onChange: (event) => setText(event.target.value),
1238
- onKeyDown: composer.onKeyDown,
1239
- placeholder,
1240
- rows: isExpanded ? 2 : 1,
1241
- value: text
1242
- }
1243
- ) }),
1244
- isExpanded ? /* @__PURE__ */ jsxs2("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: [
1245
- inputToolbarContent,
1246
- /* @__PURE__ */ jsx4(AgentChatContextStatus, { contextWindow }),
1247
- composer.recordingAllowed ? /* @__PURE__ */ jsx4(
1248
- "button",
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",
1249
1246
  {
1250
- type: "button",
1251
- className: pillButtonClass,
1252
- "aria-label": composer.recorder.isRecording ? "Stop recording" : "Record audio",
1253
- disabled: disabled || isRunning || isSubmitting,
1254
- onClick: () => {
1255
- if (composer.recorder.isRecording) composer.recorder.stop();
1256
- else void composer.recorder.start();
1257
- },
1258
- children: composer.recorder.isRecording ? /* @__PURE__ */ jsx4(Square, {}) : /* @__PURE__ */ jsx4(Mic, {})
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
1259
1261
  }
1260
- ) : null
1261
- ] }) : null,
1262
- !isExpanded ? /* @__PURE__ */ jsx4("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
1263
- !isExpanded ? /* @__PURE__ */ jsxs2("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end", children: [
1264
- /* @__PURE__ */ jsx4(AgentChatContextStatus, { contextWindow }),
1265
- composer.recordingAllowed ? /* @__PURE__ */ jsx4(
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(
1266
1300
  "button",
1267
1301
  {
1268
- type: "button",
1269
- className: pillButtonClass,
1270
- "aria-label": composer.recorder.isRecording ? "Stop recording" : "Record audio",
1271
- disabled: disabled || isRunning || isSubmitting,
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),
1272
1305
  onClick: () => {
1273
- if (composer.recorder.isRecording) composer.recorder.stop();
1274
- else void composer.recorder.start();
1306
+ if (action === "stop") onStop?.();
1307
+ else void submit();
1275
1308
  },
1276
- children: composer.recorder.isRecording ? /* @__PURE__ */ jsx4(Square, {}) : /* @__PURE__ */ jsx4(Mic, {})
1309
+ title: composer.validationError || (action === "stop" ? "Stop" : isRunning ? "Queue message" : "Send"),
1310
+ type: "button",
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 })
1277
1312
  }
1278
- ) : null
1279
- ] }) : null,
1280
- /* @__PURE__ */ jsx4("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end gap-1", children: /* @__PURE__ */ jsx4(
1281
- "button",
1282
- {
1283
- "aria-label": isCancelling ? "Cancelling response" : action === "stop" ? "Stop generating" : files.length > 0 ? `Send ${formatFileCount(files)}` : isRunning ? "Queue message" : "Send message",
1284
- className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
1285
- disabled: isCancelling || (action === "stop" ? disabled : !canSubmit),
1286
- onClick: () => {
1287
- if (action === "stop") onStop?.();
1288
- else void submit();
1289
- },
1290
- title: composer.validationError || (action === "stop" ? "Stop" : isRunning ? "Queue message" : "Send"),
1291
- type: "button",
1292
- 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 })
1293
- }
1294
- ) })
1295
- ]
1296
- }
1297
- )
1313
+ ) })
1314
+ ]
1315
+ }
1316
+ )
1317
+ ]
1298
1318
  }
1299
1319
  ),
1300
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
@@ -1325,7 +1345,7 @@ function QueueRow({ message, onRemove, onEdit }) {
1325
1345
  /* @__PURE__ */ jsx5(CornerDownLeft, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }),
1326
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"}` }),
1327
1347
  message.status === "blocked" ? /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-amber-700", children: "Needs attention" }) : null,
1328
- ["steer_pending", "delivering"].includes(message.status) ? /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-muted-foreground", children: "Steering\u2026" }) : null,
1348
+ ["steer_pending", "delivering"].includes(message.status) ? /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-muted-foreground", children: "Sending\u2026" }) : null,
1329
1349
  editable ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1330
1350
  /* @__PURE__ */ jsx5(
1331
1351
  "button",
@@ -1439,6 +1459,21 @@ function AgentChatDefaultActions({
1439
1459
  React8.useEffect(() => () => {
1440
1460
  if (copyResetTimer.current !== null) window.clearTimeout(copyResetTimer.current);
1441
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
+ };
1442
1477
  const copied = handlers?.copied ?? optimisticCopied;
1443
1478
  const copyMessage = () => {
1444
1479
  handlers?.onCopy?.(message.content || "", message.id);
@@ -1482,7 +1517,8 @@ function AgentChatDefaultActions({
1482
1517
  ChatMessageAction,
1483
1518
  {
1484
1519
  label: "Like",
1485
- onClick: () => void handlers.onSetFeedback?.(message, {
1520
+ disabled: feedbackPending,
1521
+ onClick: () => void writeFeedback({
1486
1522
  rating: handlers.feedback?.rating === "like" ? null : "like"
1487
1523
  }),
1488
1524
  tooltip: "Like this response",
@@ -1493,10 +1529,11 @@ function AgentChatDefaultActions({
1493
1529
  /* @__PURE__ */ jsx6(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx6(
1494
1530
  ChatMessageAction,
1495
1531
  {
1532
+ disabled: feedbackPending,
1496
1533
  label: "Dislike",
1497
1534
  onClick: () => {
1498
1535
  if (handlers.feedback?.rating !== "dislike") {
1499
- void handlers.onSetFeedback?.(message, { rating: "dislike" });
1536
+ void writeFeedback({ rating: "dislike" });
1500
1537
  }
1501
1538
  setFeedbackOpen(true);
1502
1539
  },
@@ -1546,9 +1583,10 @@ function AgentChatDefaultActions({
1546
1583
  /* @__PURE__ */ jsx6(
1547
1584
  "button",
1548
1585
  {
1586
+ disabled: feedbackPending,
1549
1587
  className: "rounded-md px-3 py-2 text-sm font-medium hover:bg-muted",
1550
1588
  onClick: () => {
1551
- void Promise.resolve(handlers.onSetFeedback?.(message, { rating: null })).then(() => setFeedbackOpen(false)).catch(() => void 0);
1589
+ void writeFeedback({ rating: null }, true);
1552
1590
  },
1553
1591
  type: "button",
1554
1592
  children: "Remove feedback"
@@ -1557,9 +1595,10 @@ function AgentChatDefaultActions({
1557
1595
  /* @__PURE__ */ jsx6(
1558
1596
  "button",
1559
1597
  {
1598
+ disabled: feedbackPending,
1560
1599
  className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:opacity-90",
1561
1600
  onClick: () => {
1562
- void Promise.resolve(handlers.onSetFeedback?.(message, { rating: "dislike", reason, comment })).then(() => setFeedbackOpen(false)).catch(() => void 0);
1601
+ void writeFeedback({ rating: "dislike", reason, comment }, true);
1563
1602
  },
1564
1603
  type: "button",
1565
1604
  children: "Save"
@@ -2286,16 +2325,17 @@ function ToolActivityReel({
2286
2325
  /* @__PURE__ */ jsx10("span", { className: previous ? "a24-tool-reel__current" : void 0, children: displayed })
2287
2326
  ] });
2288
2327
  }
2289
- function ToolActivityOrb({ label }) {
2290
- return /* @__PURE__ */ jsx10("span", { className: "a24-tool-orb", children: /* @__PURE__ */ jsx10(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" }) });
2291
2330
  }
2292
2331
  function AgentChatActivityRow({
2293
2332
  label,
2294
2333
  modelTurnId,
2334
+ renderActivityIcon,
2295
2335
  onSelect
2296
2336
  }) {
2297
2337
  const content = /* @__PURE__ */ jsxs6(Fragment3, { children: [
2298
- /* @__PURE__ */ jsx10(ToolActivityOrb, { label }),
2338
+ /* @__PURE__ */ jsx10(ToolActivityOrb, { label, kind: "planning", renderActivityIcon }),
2299
2339
  /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label a24-tool-shimmer", children: /* @__PURE__ */ jsx10(ToolActivityReel, { label }) })
2300
2340
  ] });
2301
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 }) });
@@ -2304,6 +2344,7 @@ function AgentChatModelTurnRow({ part }) {
2304
2344
  return /* @__PURE__ */ jsx10(AgentChatActivityRow, { label: "Planning next moves", modelTurnId: part.modelTurnId });
2305
2345
  }
2306
2346
  function AgentChatNodeActivityRow({
2347
+ renderActivityIcon,
2307
2348
  onSelect,
2308
2349
  part
2309
2350
  }) {
@@ -2322,10 +2363,10 @@ function AgentChatNodeActivityRow({
2322
2363
  const label = running ? /* @__PURE__ */ jsx10("span", { className: "a24-tool-shimmer", children: /* @__PURE__ */ jsx10(ToolActivityReel, { label: part.label }) }) : part.label;
2323
2364
  if (!failed) {
2324
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: [
2325
- running ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: part.label }) : null,
2366
+ running ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: part.label, kind: "node", part, renderActivityIcon }) : null,
2326
2367
  /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label", children: label })
2327
2368
  ] }) : /* @__PURE__ */ jsxs6("div", { className: triggerClassName, children: [
2328
- running ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: part.label }) : null,
2369
+ running ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: part.label, kind: "node", part, renderActivityIcon }) : null,
2329
2370
  /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label", children: label })
2330
2371
  ] }) });
2331
2372
  }
@@ -2371,6 +2412,7 @@ function ToolDisclosure({
2371
2412
  root,
2372
2413
  showOrb,
2373
2414
  statusLabel,
2415
+ renderActivityIcon,
2374
2416
  accessory
2375
2417
  }) {
2376
2418
  const [open, setOpen] = React11.useState(false);
@@ -2380,10 +2422,10 @@ function ToolDisclosure({
2380
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");
2381
2423
  return /* @__PURE__ */ jsxs6("div", { className: cn("a24-tool-trigger-row", root && "a24-tool-trigger-row--root"), children: [
2382
2424
  onSelect && part ? /* @__PURE__ */ jsxs6("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
2383
- showOrb ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: statusLabel }) : null,
2425
+ showOrb ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: statusLabel || "Working", kind: grouped ? "group" : "tool", part, renderActivityIcon }) : null,
2384
2426
  /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label", children: label })
2385
2427
  ] }) : /* @__PURE__ */ jsxs6("div", { className: triggerClassName, children: [
2386
- showOrb ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: statusLabel }) : null,
2428
+ showOrb ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: statusLabel || "Working", kind: grouped ? "group" : "tool", part, renderActivityIcon }) : null,
2387
2429
  /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label", children: label })
2388
2430
  ] }),
2389
2431
  accessory
@@ -2405,7 +2447,7 @@ function ToolDisclosure({
2405
2447
  },
2406
2448
  type: "button",
2407
2449
  children: [
2408
- showOrb ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: statusLabel }) : null,
2450
+ showOrb ? /* @__PURE__ */ jsx10(ToolActivityOrb, { label: statusLabel || "Working", kind: grouped ? "group" : "tool", part, renderActivityIcon }) : null,
2409
2451
  /* @__PURE__ */ jsx10("span", { className: "a24-tool-trigger__label", children: label }),
2410
2452
  open ? /* @__PURE__ */ jsx10(ChevronDown2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ jsx10(ChevronRight2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
2411
2453
  ]
@@ -2466,6 +2508,7 @@ function AgentChatToolRow({
2466
2508
  return /* @__PURE__ */ jsx10("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: /* @__PURE__ */ jsxs6(
2467
2509
  ToolDisclosure,
2468
2510
  {
2511
+ renderActivityIcon: slots?.renderActivityIcon,
2469
2512
  error: failed,
2470
2513
  accessory: toolAccessory(context, slots),
2471
2514
  label: /* @__PURE__ */ jsx10(ToolLabel, { active, label: context.label, part, slots }),
@@ -2512,6 +2555,7 @@ function AgentChatToolSession({
2512
2555
  return /* @__PURE__ */ jsx10(
2513
2556
  ToolDisclosure,
2514
2557
  {
2558
+ renderActivityIcon: slots?.renderActivityIcon,
2515
2559
  error: failed,
2516
2560
  grouped: true,
2517
2561
  label: labelContent,
@@ -2556,6 +2600,7 @@ function AgentChatSubagentGroup({ isLatestActivity = true, isLive = false, part,
2556
2600
  return /* @__PURE__ */ jsx10(
2557
2601
  ToolDisclosure,
2558
2602
  {
2603
+ renderActivityIcon: slots?.renderActivityIcon,
2559
2604
  accessory: slots?.renderToolAccessory?.(context),
2560
2605
  error: failed,
2561
2606
  grouped: true,
@@ -2815,14 +2860,14 @@ function AgentResponseTimeline({
2815
2860
  if (part.kind === "model-turn") {
2816
2861
  const key = activeActivityClaimed ? part.id : `${message.id}-active-activity`;
2817
2862
  activeActivityClaimed = true;
2818
- rendered.push(/* @__PURE__ */ jsx11(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));
2819
2864
  continue;
2820
2865
  }
2821
2866
  if (part.kind === "node-activity") {
2822
2867
  const key = activeActivityClaimed ? part.id : `${message.id}-active-activity`;
2823
2868
  if (part.status === "running") activeActivityClaimed = true;
2824
2869
  rendered.push(
2825
- part.status === "running" ? /* @__PURE__ */ jsx11(AgentChatActivityRow, { label: part.label, onSelect: onNodeSelect ? () => onNodeSelect(part) : void 0 }, key) : /* @__PURE__ */ jsx11(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)
2826
2871
  );
2827
2872
  continue;
2828
2873
  }
@@ -2851,7 +2896,7 @@ function AgentResponseTimeline({
2851
2896
  rendered.push(/* @__PURE__ */ jsx11(MessageResponse, { children: message.content }, `${message.id}-fallback`));
2852
2897
  }
2853
2898
  if (rendered.length === 0 && isLoading) {
2854
- rendered.push(/* @__PURE__ */ jsx11(AgentChatActivityRow, { label: "Thinking" }, `${message.id}-active-activity`));
2899
+ rendered.push(/* @__PURE__ */ jsx11(AgentChatActivityRow, { renderActivityIcon: toolSlots?.renderActivityIcon, label: "Thinking" }, `${message.id}-active-activity`));
2855
2900
  }
2856
2901
  return /* @__PURE__ */ jsx11("div", { className: "space-y-3", children: rendered });
2857
2902
  }