@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.cjs CHANGED
@@ -590,6 +590,7 @@ function attachmentRenderKey(data, index, rowKind) {
590
590
  function ChatImageAttachment({
591
591
  className,
592
592
  data,
593
+ presentation = "message",
593
594
  onRemove,
594
595
  resolveContent,
595
596
  ...props
@@ -669,7 +670,7 @@ function ChatImageAttachment({
669
670
  Attachment,
670
671
  {
671
672
  ...props,
672
- className: cn("a24-chat-image-attachment", className),
673
+ className: cn("a24-chat-image-attachment", presentation === "composer" && "a24-composer-attachment-card", className),
673
674
  orientation: "vertical",
674
675
  ref: rootRef,
675
676
  state: status === "error" ? "error" : status === "done" ? "done" : "processing",
@@ -692,6 +693,7 @@ function ChatImageAttachment({
692
693
  status !== "done" && status !== "error" ? React.createElement("span", { className: "sr-only", role: "status" }, `Loading ${filename}`) : null,
693
694
  status === "error" ? React.createElement("span", { className: "sr-only" }, `${filename} preview unavailable`) : null
694
695
  ),
696
+ presentation === "composer" ? React.createElement("span", { className: "a24-composer-image-caption", title: filename }, filename) : null,
695
697
  onRemove ? React.createElement(
696
698
  "button",
697
699
  {
@@ -710,6 +712,7 @@ function ChatImageAttachment({
710
712
  function ChatAttachment({
711
713
  className,
712
714
  data,
715
+ presentation = "message",
713
716
  onRemove,
714
717
  orientation,
715
718
  resolveContent,
@@ -722,6 +725,7 @@ function ChatAttachment({
722
725
  ...props,
723
726
  className,
724
727
  data,
728
+ presentation,
725
729
  onRemove,
726
730
  resolveContent
727
731
  });
@@ -729,11 +733,16 @@ function ChatAttachment({
729
733
  return React.createElement(
730
734
  Attachment,
731
735
  {
732
- className: cn("a24-chat-attachment", className),
736
+ className: cn("a24-chat-attachment", presentation === "composer" && "a24-composer-attachment-card", className),
733
737
  orientation: orientation || "horizontal",
734
738
  size: "sm",
735
739
  ...props
736
740
  },
741
+ presentation === "composer" ? React.createElement(
742
+ "div",
743
+ { className: "a24-composer-file-preview", "aria-hidden": true },
744
+ React.createElement(import_file_icons.FileIcon, { size: 64, type: fileIconType, variant: "solid" })
745
+ ) : null,
737
746
  React.createElement(
738
747
  AttachmentMedia,
739
748
  Object.assign(
@@ -791,6 +800,7 @@ function ChatAttachments({
791
800
  }
792
801
  function ChatAttachmentRows({
793
802
  attachments,
803
+ layout = "message",
794
804
  className,
795
805
  dir,
796
806
  onRemove,
@@ -806,6 +816,7 @@ function ChatAttachmentRows({
806
816
  items.map(
807
817
  (attachment, index) => React.createElement(ChatAttachment, {
808
818
  data: attachment,
819
+ presentation: layout,
809
820
  dir: "ltr",
810
821
  key: attachmentRenderKey(attachment, index, kind),
811
822
  onRemove: onRemove ? () => onRemove(attachment) : void 0,
@@ -816,12 +827,12 @@ function ChatAttachmentRows({
816
827
  return React.createElement(
817
828
  "div",
818
829
  {
819
- className: cn("a24-chat-attachment-rows", className),
830
+ className: cn("a24-chat-attachment-rows", layout === "composer" && "a24-composer-attachment-row", className),
820
831
  dir,
821
832
  ...props
822
833
  },
823
- renderRow(imageAttachments, "images"),
824
- renderRow(fileAttachments, "files")
834
+ layout === "composer" ? renderRow(attachments, "mixed") : renderRow(imageAttachments, "images"),
835
+ layout === "message" ? renderRow(fileAttachments, "files") : null
825
836
  );
826
837
  }
827
838
 
@@ -831,6 +842,7 @@ var React4 = __toESM(require("react"), 1);
831
842
  // src/runtime/input-contract.ts
832
843
  var import_client = require("@agents24/client");
833
844
  var import_client2 = require("@agents24/client");
845
+ var canonicalInputMimeType = import_client.normalizeAttachmentMimeType;
834
846
  function uploadByteSize(data) {
835
847
  if (data instanceof Uint8Array || data instanceof ArrayBuffer) return data.byteLength;
836
848
  return data.size;
@@ -936,32 +948,38 @@ function useChatComposerFocus({ textareaRef, disabled, focusKey, restoredDraft,
936
948
  // src/ui/use-audio-recorder.ts
937
949
  var React3 = __toESM(require("react"), 1);
938
950
  var RECORDING_CANDIDATES = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4"];
939
- function recordingExtension(mimeType) {
940
- return mimeType.startsWith("audio/mp4") ? "m4a" : "webm";
941
- }
942
951
  function useAudioRecorder(onRecorded, options = {}) {
943
- const mountedRef = React3.useRef(true);
944
- const startingRef = React3.useRef(false);
945
952
  const recorderRef = React3.useRef(null);
946
953
  const streamRef = React3.useRef(null);
954
+ const pendingRef = React3.useRef(false);
955
+ const generationRef = React3.useRef(0);
947
956
  const onRecordedRef = React3.useRef(onRecorded);
948
957
  const onStreamChangeRef = React3.useRef(options.onStreamChange);
949
- onRecordedRef.current = onRecorded;
950
- onStreamChangeRef.current = options.onStreamChange;
958
+ React3.useEffect(() => {
959
+ onRecordedRef.current = onRecorded;
960
+ onStreamChangeRef.current = options.onStreamChange;
961
+ });
951
962
  const [isRecording, setIsRecording] = React3.useState(false);
952
963
  const [error, setError] = React3.useState(null);
964
+ const release = React3.useCallback(() => {
965
+ streamRef.current?.getTracks().forEach((track) => track.stop());
966
+ streamRef.current = null;
967
+ recorderRef.current = null;
968
+ onStreamChangeRef.current?.(null);
969
+ }, []);
953
970
  const stop = React3.useCallback(() => {
954
- if (recorderRef.current && recorderRef.current.state !== "inactive") recorderRef.current.stop();
971
+ const recorder = recorderRef.current;
972
+ if (recorder && recorder.state !== "inactive") recorder.stop();
955
973
  }, []);
956
974
  const start = React3.useCallback(async () => {
957
- if (startingRef.current || recorderRef.current) return;
958
- startingRef.current = true;
975
+ if (pendingRef.current || recorderRef.current) return;
959
976
  setError(null);
960
977
  if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === "undefined") {
961
978
  setError("Audio recording is not supported in this browser.");
962
- startingRef.current = false;
963
979
  return;
964
980
  }
981
+ pendingRef.current = true;
982
+ const generation = generationRef.current;
965
983
  try {
966
984
  const mimeType = RECORDING_CANDIDATES.find((candidate) => MediaRecorder.isTypeSupported(candidate));
967
985
  if (!mimeType) {
@@ -969,158 +987,169 @@ function useAudioRecorder(onRecorded, options = {}) {
969
987
  return;
970
988
  }
971
989
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
972
- if (!mountedRef.current) {
990
+ if (generation !== generationRef.current) {
973
991
  stream.getTracks().forEach((track) => track.stop());
974
992
  return;
975
993
  }
976
994
  streamRef.current = stream;
977
- onStreamChangeRef.current?.(stream);
978
995
  const recorder = new MediaRecorder(stream, { mimeType });
996
+ recorderRef.current = recorder;
979
997
  const chunks = [];
980
998
  recorder.ondataavailable = (event) => {
981
999
  if (event.data.size) chunks.push(event.data);
982
1000
  };
983
1001
  recorder.onstop = () => {
984
- const actualType = recorder.mimeType || "audio/webm";
985
- if (mountedRef.current) onRecordedRef.current(new File([new Blob(chunks, { type: actualType })], `recording-${Date.now()}.${recordingExtension(actualType)}`, { type: actualType }));
986
- stream.getTracks().forEach((track) => track.stop());
987
- onStreamChangeRef.current?.(null);
988
- recorderRef.current = null;
989
- streamRef.current = null;
990
- if (mountedRef.current) setIsRecording(false);
1002
+ if (generation !== generationRef.current) return;
1003
+ const actualType = recorder.mimeType || mimeType;
1004
+ const extension = actualType.startsWith("audio/mp4") ? "m4a" : "webm";
1005
+ try {
1006
+ onRecordedRef.current(new File([new Blob(chunks, { type: actualType })], `recording-${Date.now()}.${extension}`, { type: actualType }));
1007
+ } finally {
1008
+ release();
1009
+ setIsRecording(false);
1010
+ }
1011
+ };
1012
+ recorder.onerror = () => {
1013
+ recorder.onstop = null;
1014
+ if (recorder.state !== "inactive") recorder.stop();
1015
+ release();
1016
+ setIsRecording(false);
1017
+ setError("Audio recording failed. Try again.");
991
1018
  };
992
- recorderRef.current = recorder;
993
- streamRef.current = stream;
994
1019
  recorder.start();
995
1020
  setIsRecording(true);
1021
+ onStreamChangeRef.current?.(stream);
996
1022
  } catch {
997
- streamRef.current?.getTracks().forEach((track) => track.stop());
998
- onStreamChangeRef.current?.(null);
999
- streamRef.current = null;
1000
- recorderRef.current = null;
1001
- if (mountedRef.current) setError("Microphone access is unavailable.");
1023
+ if (generation === generationRef.current) {
1024
+ release();
1025
+ setIsRecording(false);
1026
+ setError("Microphone access is unavailable.");
1027
+ }
1002
1028
  } finally {
1003
- startingRef.current = false;
1004
- }
1005
- }, []);
1006
- React3.useEffect(() => {
1007
- mountedRef.current = true;
1008
- return () => {
1009
- mountedRef.current = false;
1010
- if (recorderRef.current && recorderRef.current.state !== "inactive") recorderRef.current.stop();
1011
- streamRef.current?.getTracks().forEach((track) => track.stop());
1012
- onStreamChangeRef.current?.(null);
1013
- };
1014
- }, []);
1029
+ if (generation === generationRef.current) pendingRef.current = false;
1030
+ }
1031
+ }, [release]);
1032
+ React3.useEffect(() => () => {
1033
+ generationRef.current += 1;
1034
+ pendingRef.current = false;
1035
+ const recorder = recorderRef.current;
1036
+ if (recorder) {
1037
+ recorder.onstop = null;
1038
+ recorder.onerror = null;
1039
+ if (recorder.state !== "inactive") recorder.stop();
1040
+ }
1041
+ release();
1042
+ }, [release]);
1015
1043
  return { error, isRecording, start, stop };
1016
1044
  }
1017
1045
 
1018
1046
  // src/ui/use-agent-chat-composer.ts
1019
- function useAgentChatComposer({
1020
- disabled = false,
1021
- isRunning = false,
1022
- isCancelling = false,
1023
- inputContract,
1024
- recordingEnabled,
1025
- focusKey,
1026
- restoredDraft,
1027
- textareaRef,
1028
- onSubmit
1029
- }) {
1030
- const [text, setDraftText] = React4.useState("");
1031
- const [files, setDraftFiles] = React4.useState([]);
1032
- const [isSubmitting, setIsSubmitting] = React4.useState(false);
1047
+ function useAgentChatComposer(options) {
1048
+ const [text, setTextState] = React4.useState("");
1049
+ const [files, setFiles] = React4.useState([]);
1033
1050
  const [attachmentError, setAttachmentError] = React4.useState(null);
1034
- const draftRef = React4.useRef({ text, files, revision: 0 });
1035
- const inFlightRef = React4.useRef([]);
1051
+ const [isSubmitting, setIsSubmitting] = React4.useState(false);
1052
+ const localTextareaRef = React4.useRef(null);
1053
+ const textareaRef = options.textareaRef ?? localTextareaRef;
1054
+ const filesRef = React4.useRef(files);
1055
+ const inFlightFilesRef = React4.useRef([]);
1056
+ const revisionRef = React4.useRef(0);
1036
1057
  const submittingRef = React4.useRef(false);
1037
- const mountedRef = React4.useRef(true);
1038
- const updateDraft = (nextText, nextFiles) => {
1039
- draftRef.current = { text: nextText, files: nextFiles, revision: draftRef.current.revision + 1 };
1040
- setDraftText(nextText);
1041
- setDraftFiles(nextFiles);
1042
- };
1043
1058
  React4.useEffect(() => {
1044
- mountedRef.current = true;
1045
- return () => {
1046
- mountedRef.current = false;
1047
- revokeAgentChatComposerFiles(draftRef.current.files);
1048
- revokeAgentChatComposerFiles(inFlightRef.current);
1049
- };
1059
+ filesRef.current = files;
1060
+ }, [files]);
1061
+ React4.useEffect(() => () => {
1062
+ revokeAgentChatComposerFiles(filesRef.current);
1063
+ revokeAgentChatComposerFiles(inFlightFilesRef.current);
1050
1064
  }, []);
1051
- const focus = useChatComposerFocus({ textareaRef, disabled, focusKey, restoredDraft, onRestore: (draft) => {
1052
- revokeAgentChatComposerFiles(draftRef.current.files.filter((file) => !draft.files.includes(file)));
1053
- updateDraft(draft.text, draft.files);
1054
- } });
1055
- const accept = inputContract ? inputMimeTypes(inputContract) : null;
1065
+ const focusComposer = useChatComposerFocus({
1066
+ textareaRef,
1067
+ disabled: Boolean(options.disabled),
1068
+ focusKey: options.focusKey,
1069
+ restoredDraft: options.restoredDraft,
1070
+ onRestore: (draft) => {
1071
+ revisionRef.current += 1;
1072
+ revokeAgentChatComposerFiles(filesRef.current);
1073
+ setTextState(draft.text);
1074
+ setFiles(draft.files);
1075
+ }
1076
+ });
1077
+ const attachmentMimeTypes = inputMimeTypes(options.inputContract);
1078
+ const setText = (value) => {
1079
+ revisionRef.current += 1;
1080
+ setTextState(value);
1081
+ };
1056
1082
  const addFiles = (selected) => {
1057
- const accepted = accept ? selected.filter((file) => accept.includes((0, import_client2.normalizeAttachmentMimeType)(file.type, file.name))) : selected;
1083
+ const accepted = selected.filter((file) => attachmentMimeTypes.includes(canonicalInputMimeType(file.type, file.name)));
1084
+ revisionRef.current += 1;
1085
+ const additions = accepted.map(createAgentChatComposerFile);
1086
+ setFiles((current) => [...current, ...additions]);
1058
1087
  setAttachmentError(accepted.length === selected.length ? null : "One or more selected file types are not supported by this Agent.");
1059
- updateDraft(draftRef.current.text, [...draftRef.current.files, ...accepted.map(createAgentChatComposerFile)]);
1060
1088
  };
1061
- const recordedRef = React4.useRef(addFiles);
1062
- recordedRef.current = addFiles;
1063
- const recorder = useAudioRecorder((file) => {
1064
- if (mountedRef.current) recordedRef.current([file]);
1065
- });
1066
- const recordingAllowed = inputContract ? inputContract.modalities.audio.enabled && inputContract.modalities.audio.recording_enabled && recordingEnabled !== false : recordingEnabled === true;
1067
- const validationError = inputContract ? validateAgentChatSubmission(
1068
- inputContract,
1089
+ const removeFile = (id) => {
1090
+ revisionRef.current += 1;
1091
+ const removed = filesRef.current.find((file) => file.id === id);
1092
+ if (removed) revokeAgentChatComposerFiles([removed]);
1093
+ setFiles((current) => current.filter((file) => file.id !== id));
1094
+ };
1095
+ const recorder = useAudioRecorder((file) => addFiles([file]), { onStreamChange: options.onRecordingStreamChange });
1096
+ const validationError = options.inputContract ? validateAgentChatSubmission(
1097
+ options.inputContract,
1069
1098
  text,
1070
- files.map((file) => ({ data: file.source || new Uint8Array(), mediaType: file.mediaType, name: file.filename, byteSize: file.source ? void 0 : file.byteSize }))
1099
+ files.map((file) => ({ data: file.source || new Uint8Array(), mediaType: file.mediaType, name: file.filename, byteSize: file.byteSize }))
1071
1100
  ) : null;
1072
- const canSubmit = !disabled && !isCancelling && !isSubmitting && !recorder.isRecording && !validationError && Boolean(text.trim() || files.length);
1101
+ const canSubmit = Boolean(text.trim() || files.length) && !options.disabled && !options.isCancelling && !isSubmitting && !recorder.isRecording && !validationError;
1073
1102
  const submit = async () => {
1074
1103
  if (!canSubmit || submittingRef.current) return;
1075
- const submitted = draftRef.current;
1104
+ const value = text;
1105
+ const selectedFiles = files;
1106
+ const clearedRevision = ++revisionRef.current;
1076
1107
  submittingRef.current = true;
1077
- inFlightRef.current = submitted.files;
1078
1108
  setIsSubmitting(true);
1079
- updateDraft("", []);
1080
- const clearedRevision = draftRef.current.revision;
1081
- focus();
1109
+ inFlightFilesRef.current = selectedFiles;
1110
+ setTextState("");
1111
+ setFiles([]);
1112
+ focusComposer();
1082
1113
  try {
1083
- await onSubmit({
1084
- text: submitted.text,
1085
- files: submitted.files,
1086
- attachmentIds: submitted.files.flatMap((file) => file.persistedAttachmentId ? [file.persistedAttachmentId] : [])
1087
- });
1088
- revokeAgentChatComposerFiles(submitted.files);
1114
+ await options.onSubmit(value, selectedFiles);
1115
+ revokeAgentChatComposerFiles(selectedFiles);
1089
1116
  } catch {
1090
- if (mountedRef.current && draftRef.current.revision === clearedRevision) updateDraft(submitted.text, submitted.files);
1091
- else revokeAgentChatComposerFiles(submitted.files);
1117
+ if (revisionRef.current === clearedRevision) {
1118
+ revisionRef.current += 1;
1119
+ setTextState(value);
1120
+ setFiles(selectedFiles);
1121
+ } else revokeAgentChatComposerFiles(selectedFiles);
1092
1122
  } finally {
1093
- inFlightRef.current = [];
1123
+ inFlightFilesRef.current = [];
1094
1124
  submittingRef.current = false;
1095
- if (mountedRef.current) setIsSubmitting(false);
1125
+ setIsSubmitting(false);
1096
1126
  }
1097
1127
  };
1098
1128
  return {
1099
1129
  text,
1130
+ setText,
1100
1131
  files,
1101
- isSubmitting,
1132
+ addFiles,
1133
+ removeFile,
1134
+ textareaRef,
1135
+ submit,
1102
1136
  canSubmit,
1103
- validationError,
1137
+ isSubmitting,
1104
1138
  attachmentError,
1105
- recordingAllowed,
1139
+ attachmentMimeTypes,
1140
+ validationError,
1106
1141
  recorder,
1107
- accept,
1108
- fileAccept: accept ? (0, import_client2.attachmentFileAccept)(accept) : void 0,
1109
- allowAttachments: accept ? accept.length > 0 : true,
1110
- action: chatComposerAction({ hasDraft: Boolean(text.trim() || files.length), isRunning, isCancelling }),
1111
- setText: (value) => updateDraft(value, draftRef.current.files),
1112
- addFiles,
1113
- removeFile: (id) => {
1114
- revokeAgentChatComposerFiles(draftRef.current.files.filter((file) => file.id === id));
1115
- updateDraft(draftRef.current.text, draftRef.current.files.filter((file) => file.id !== id));
1116
- },
1117
- submit,
1142
+ recordingAllowed: options.recordingEnabled ?? options.inputContract?.modalities.audio.recording_enabled ?? false,
1143
+ fileAccept: (0, import_client2.attachmentFileAccept)(attachmentMimeTypes),
1118
1144
  onKeyDown: (event) => {
1119
1145
  if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
1120
1146
  event.preventDefault();
1121
1147
  void submit();
1122
1148
  }
1123
- }
1149
+ },
1150
+ allowAttachments: attachmentMimeTypes.length > 0,
1151
+ isExpanded: Boolean(options.forceExpanded || text.includes("\n") || text.length > 62),
1152
+ action: chatComposerAction({ hasDraft: Boolean(text.trim() || files.length), isRunning: Boolean(options.isRunning), isCancelling: Boolean(options.isCancelling) })
1124
1153
  };
1125
1154
  }
1126
1155
 
@@ -1239,7 +1268,6 @@ function AgentChatComposer({
1239
1268
  contextWindow,
1240
1269
  accept,
1241
1270
  allowAttachments = true,
1242
- attachmentLayout = "flow",
1243
1271
  className,
1244
1272
  disabled = false,
1245
1273
  forceExpanded = false,
@@ -1265,7 +1293,7 @@ function AgentChatComposer({
1265
1293
  focusKey,
1266
1294
  restoredDraft,
1267
1295
  textareaRef: localTextareaRef,
1268
- onSubmit: (input) => onSubmit({ ...input, text: input.text.trim() })
1296
+ onSubmit: (text2, files2) => onSubmit({ text: text2.trim(), files: files2, attachmentIds: files2.flatMap((file) => file.id ? [file.id] : []) })
1269
1297
  });
1270
1298
  const { text, files, setText, isSubmitting, canSubmit, action, submit } = composer;
1271
1299
  const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
@@ -1301,26 +1329,8 @@ function AgentChatComposer({
1301
1329
  event.currentTarget.value = "";
1302
1330
  };
1303
1331
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), "data-agents24-chat-composer": "", children: [
1304
- files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1305
- "div",
1306
- {
1307
- "data-testid": "agent-chat-composer-attachments",
1308
- "data-layout": attachmentLayout,
1309
- className: cn(
1310
- "a24-composer__attachments min-w-0 py-1",
1311
- 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"
1312
- ),
1313
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1314
- ChatAttachmentRows,
1315
- {
1316
- attachments: files,
1317
- onRemove: (attachment) => attachment.id && composer.removeFile(attachment.id)
1318
- }
1319
- )
1320
- }
1321
- ) : null,
1322
1332
  composer.attachmentError || composer.recorder.error ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { role: "alert", children: composer.attachmentError || composer.recorder.error }) : null,
1323
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1333
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1324
1334
  "div",
1325
1335
  {
1326
1336
  "data-expanded": isExpanded ? "" : void 0,
@@ -1328,118 +1338,129 @@ function AgentChatComposer({
1328
1338
  "a24-composer__surface relative w-full border border-input bg-background shadow-sm",
1329
1339
  isExpanded ? "rounded-xl p-2 pb-1.5" : "rounded-xl px-2 py-[3px]"
1330
1340
  ),
1331
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1332
- "div",
1333
- {
1334
- className: "a24-composer__grid w-full",
1335
- style: {
1336
- alignItems: "center",
1337
- display: "grid",
1338
- gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
1339
- gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
1340
- gap: isExpanded ? "3px 10px" : "8px"
1341
- },
1342
- children: [
1343
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
1344
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1345
- "input",
1346
- {
1347
- ref: fileInputRef,
1348
- accept: composer.fileAccept ?? accept,
1349
- "aria-label": "Attach files",
1350
- className: "hidden",
1351
- multiple: true,
1352
- name: "attachments",
1353
- onChange: handleFilesChange,
1354
- type: "file"
1355
- }
1356
- ),
1357
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1358
- "button",
1359
- {
1360
- "aria-label": "Attach files",
1361
- className: cn("a24-composer__attach", pillButtonClass),
1362
- disabled: disabled || isSubmitting || !allowAttachments || !composer.allowAttachments,
1363
- onClick: () => fileInputRef.current?.click(),
1364
- title: "Attach files",
1365
- type: "button",
1366
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Plus, { className: "size-4" })
1367
- }
1368
- )
1369
- ] }),
1370
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1371
- "textarea",
1372
- {
1373
- ref: setTextareaRef,
1374
- "aria-label": "Message",
1375
- autoComplete: "off",
1376
- className: cn(
1377
- "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",
1378
- isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
1341
+ children: [
1342
+ files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1343
+ ChatAttachmentRows,
1344
+ {
1345
+ "data-testid": "agent-chat-composer-attachments",
1346
+ layout: "composer",
1347
+ attachments: files,
1348
+ onRemove: (attachment) => attachment.id && composer.removeFile(attachment.id)
1349
+ }
1350
+ ) : null,
1351
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1352
+ "div",
1353
+ {
1354
+ className: "a24-composer__grid w-full",
1355
+ style: {
1356
+ alignItems: "center",
1357
+ display: "grid",
1358
+ gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
1359
+ gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
1360
+ gap: isExpanded ? "3px 10px" : "8px"
1361
+ },
1362
+ children: [
1363
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
1364
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1365
+ "input",
1366
+ {
1367
+ ref: fileInputRef,
1368
+ accept: composer.fileAccept ?? accept,
1369
+ "aria-label": "Attach files",
1370
+ className: "hidden",
1371
+ multiple: true,
1372
+ name: "attachments",
1373
+ onChange: handleFilesChange,
1374
+ type: "file"
1375
+ }
1379
1376
  ),
1380
- disabled,
1381
- name: "message",
1382
- onChange: (event) => setText(event.target.value),
1383
- onKeyDown: composer.onKeyDown,
1384
- placeholder,
1385
- rows: isExpanded ? 2 : 1,
1386
- value: text
1387
- }
1388
- ) }),
1389
- isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: [
1390
- inputToolbarContent,
1391
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AgentChatContextStatus, { contextWindow }),
1392
- composer.recordingAllowed ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1393
- "button",
1377
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1378
+ "button",
1379
+ {
1380
+ "aria-label": "Attach files",
1381
+ className: cn("a24-composer__attach", pillButtonClass),
1382
+ disabled: disabled || isSubmitting || !allowAttachments || !composer.allowAttachments,
1383
+ onClick: () => fileInputRef.current?.click(),
1384
+ title: "Attach files",
1385
+ type: "button",
1386
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Plus, { className: "size-4" })
1387
+ }
1388
+ )
1389
+ ] }),
1390
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1391
+ "textarea",
1394
1392
  {
1395
- type: "button",
1396
- className: pillButtonClass,
1397
- "aria-label": composer.recorder.isRecording ? "Stop recording" : "Record audio",
1398
- disabled: disabled || isRunning || isSubmitting,
1399
- onClick: () => {
1400
- if (composer.recorder.isRecording) composer.recorder.stop();
1401
- else void composer.recorder.start();
1402
- },
1403
- children: composer.recorder.isRecording ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Square, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Mic, {})
1393
+ ref: setTextareaRef,
1394
+ "aria-label": "Message",
1395
+ autoComplete: "off",
1396
+ className: cn(
1397
+ "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",
1398
+ isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
1399
+ ),
1400
+ disabled,
1401
+ name: "message",
1402
+ onChange: (event) => setText(event.target.value),
1403
+ onKeyDown: composer.onKeyDown,
1404
+ placeholder,
1405
+ rows: isExpanded ? 2 : 1,
1406
+ value: text
1404
1407
  }
1405
- ) : null
1406
- ] }) : null,
1407
- !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
1408
- !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end", children: [
1409
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AgentChatContextStatus, { contextWindow }),
1410
- composer.recordingAllowed ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1408
+ ) }),
1409
+ isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: [
1410
+ inputToolbarContent,
1411
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AgentChatContextStatus, { contextWindow }),
1412
+ composer.recordingAllowed ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1413
+ "button",
1414
+ {
1415
+ type: "button",
1416
+ className: pillButtonClass,
1417
+ "aria-label": composer.recorder.isRecording ? "Stop recording" : "Record audio",
1418
+ disabled: disabled || isRunning || isSubmitting,
1419
+ onClick: () => {
1420
+ if (composer.recorder.isRecording) composer.recorder.stop();
1421
+ else void composer.recorder.start();
1422
+ },
1423
+ children: composer.recorder.isRecording ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Square, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Mic, {})
1424
+ }
1425
+ ) : null
1426
+ ] }) : null,
1427
+ !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
1428
+ !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end", children: [
1429
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AgentChatContextStatus, { contextWindow }),
1430
+ composer.recordingAllowed ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1431
+ "button",
1432
+ {
1433
+ type: "button",
1434
+ className: pillButtonClass,
1435
+ "aria-label": composer.recorder.isRecording ? "Stop recording" : "Record audio",
1436
+ disabled: disabled || isRunning || isSubmitting,
1437
+ onClick: () => {
1438
+ if (composer.recorder.isRecording) composer.recorder.stop();
1439
+ else void composer.recorder.start();
1440
+ },
1441
+ children: composer.recorder.isRecording ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Square, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Mic, {})
1442
+ }
1443
+ ) : null
1444
+ ] }) : null,
1445
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end gap-1", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1411
1446
  "button",
1412
1447
  {
1413
- type: "button",
1414
- className: pillButtonClass,
1415
- "aria-label": composer.recorder.isRecording ? "Stop recording" : "Record audio",
1416
- disabled: disabled || isRunning || isSubmitting,
1448
+ "aria-label": isCancelling ? "Cancelling response" : action === "stop" ? "Stop generating" : files.length > 0 ? `Send ${formatFileCount(files)}` : isRunning ? "Queue message" : "Send message",
1449
+ className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
1450
+ disabled: isCancelling || (action === "stop" ? disabled : !canSubmit),
1417
1451
  onClick: () => {
1418
- if (composer.recorder.isRecording) composer.recorder.stop();
1419
- else void composer.recorder.start();
1452
+ if (action === "stop") onStop?.();
1453
+ else void submit();
1420
1454
  },
1421
- children: composer.recorder.isRecording ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Square, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Mic, {})
1455
+ title: composer.validationError || (action === "stop" ? "Stop" : isRunning ? "Queue message" : "Send"),
1456
+ type: "button",
1457
+ children: isCancelling ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Loader2, { className: "size-3.5 animate-spin" }) : action === "stop" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Square, { className: "size-3.5 fill-current" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.ArrowUp, { className: "size-4", strokeWidth: 2.5 })
1422
1458
  }
1423
- ) : null
1424
- ] }) : null,
1425
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end gap-1", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1426
- "button",
1427
- {
1428
- "aria-label": isCancelling ? "Cancelling response" : action === "stop" ? "Stop generating" : files.length > 0 ? `Send ${formatFileCount(files)}` : isRunning ? "Queue message" : "Send message",
1429
- className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
1430
- disabled: isCancelling || (action === "stop" ? disabled : !canSubmit),
1431
- onClick: () => {
1432
- if (action === "stop") onStop?.();
1433
- else void submit();
1434
- },
1435
- title: composer.validationError || (action === "stop" ? "Stop" : isRunning ? "Queue message" : "Send"),
1436
- type: "button",
1437
- children: isCancelling ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Loader2, { className: "size-3.5 animate-spin" }) : action === "stop" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Square, { className: "size-3.5 fill-current" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.ArrowUp, { className: "size-4", strokeWidth: 2.5 })
1438
- }
1439
- ) })
1440
- ]
1441
- }
1442
- )
1459
+ ) })
1460
+ ]
1461
+ }
1462
+ )
1463
+ ]
1443
1464
  }
1444
1465
  ),
1445
1466
  !isExpanded && inputToolbarContent ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
@@ -1470,7 +1491,7 @@ function QueueRow({ message, onRemove, onEdit }) {
1470
1491
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.CornerDownLeft, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }),
1471
1492
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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"}` }),
1472
1493
  message.status === "blocked" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "shrink-0 text-xs text-amber-700", children: "Needs attention" }) : null,
1473
- ["steer_pending", "delivering"].includes(message.status) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "shrink-0 text-xs text-muted-foreground", children: "Steering\u2026" }) : null,
1494
+ ["steer_pending", "delivering"].includes(message.status) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "shrink-0 text-xs text-muted-foreground", children: "Sending\u2026" }) : null,
1474
1495
  editable ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1475
1496
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1476
1497
  "button",
@@ -1584,6 +1605,21 @@ function AgentChatDefaultActions({
1584
1605
  React8.useEffect(() => () => {
1585
1606
  if (copyResetTimer.current !== null) window.clearTimeout(copyResetTimer.current);
1586
1607
  }, []);
1608
+ const [feedbackPending, setFeedbackPending] = React8.useState(false);
1609
+ const feedbackInFlight = React8.useRef(false);
1610
+ const writeFeedback = async (input, close = false) => {
1611
+ if (feedbackInFlight.current) return;
1612
+ feedbackInFlight.current = true;
1613
+ setFeedbackPending(true);
1614
+ try {
1615
+ await handlers?.onSetFeedback?.(message, input);
1616
+ if (close) setFeedbackOpen(false);
1617
+ } catch {
1618
+ } finally {
1619
+ feedbackInFlight.current = false;
1620
+ setFeedbackPending(false);
1621
+ }
1622
+ };
1587
1623
  const copied = handlers?.copied ?? optimisticCopied;
1588
1624
  const copyMessage = () => {
1589
1625
  handlers?.onCopy?.(message.content || "", message.id);
@@ -1627,7 +1663,8 @@ function AgentChatDefaultActions({
1627
1663
  ChatMessageAction,
1628
1664
  {
1629
1665
  label: "Like",
1630
- onClick: () => void handlers.onSetFeedback?.(message, {
1666
+ disabled: feedbackPending,
1667
+ onClick: () => void writeFeedback({
1631
1668
  rating: handlers.feedback?.rating === "like" ? null : "like"
1632
1669
  }),
1633
1670
  tooltip: "Like this response",
@@ -1638,10 +1675,11 @@ function AgentChatDefaultActions({
1638
1675
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui.Popover.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1639
1676
  ChatMessageAction,
1640
1677
  {
1678
+ disabled: feedbackPending,
1641
1679
  label: "Dislike",
1642
1680
  onClick: () => {
1643
1681
  if (handlers.feedback?.rating !== "dislike") {
1644
- void handlers.onSetFeedback?.(message, { rating: "dislike" });
1682
+ void writeFeedback({ rating: "dislike" });
1645
1683
  }
1646
1684
  setFeedbackOpen(true);
1647
1685
  },
@@ -1691,9 +1729,10 @@ function AgentChatDefaultActions({
1691
1729
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1692
1730
  "button",
1693
1731
  {
1732
+ disabled: feedbackPending,
1694
1733
  className: "rounded-md px-3 py-2 text-sm font-medium hover:bg-muted",
1695
1734
  onClick: () => {
1696
- void Promise.resolve(handlers.onSetFeedback?.(message, { rating: null })).then(() => setFeedbackOpen(false)).catch(() => void 0);
1735
+ void writeFeedback({ rating: null }, true);
1697
1736
  },
1698
1737
  type: "button",
1699
1738
  children: "Remove feedback"
@@ -1702,9 +1741,10 @@ function AgentChatDefaultActions({
1702
1741
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1703
1742
  "button",
1704
1743
  {
1744
+ disabled: feedbackPending,
1705
1745
  className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:opacity-90",
1706
1746
  onClick: () => {
1707
- void Promise.resolve(handlers.onSetFeedback?.(message, { rating: "dislike", reason, comment })).then(() => setFeedbackOpen(false)).catch(() => void 0);
1747
+ void writeFeedback({ rating: "dislike", reason, comment }, true);
1708
1748
  },
1709
1749
  type: "button",
1710
1750
  children: "Save"
@@ -2608,16 +2648,17 @@ function ToolActivityReel({
2608
2648
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: previous ? "a24-tool-reel__current" : void 0, children: displayed })
2609
2649
  ] });
2610
2650
  }
2611
- function ToolActivityOrb({ label }) {
2612
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-tool-orb", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_thinking_orbs.ThinkingOrb, { "aria-label": label || "Working", size: 20, speed: 0.8, state: "searching" }) });
2651
+ function ToolActivityOrb({ label = "Working", kind = "tool", part, renderActivityIcon }) {
2652
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-tool-orb", children: renderActivityIcon ? renderActivityIcon({ label, kind, part }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_thinking_orbs.ThinkingOrb, { "aria-label": label, size: 20, speed: 0.8, state: "searching" }) });
2613
2653
  }
2614
2654
  function AgentChatActivityRow({
2615
2655
  label,
2616
2656
  modelTurnId,
2657
+ renderActivityIcon,
2617
2658
  onSelect
2618
2659
  }) {
2619
2660
  const content = /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2620
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label }),
2661
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label, kind: "planning", renderActivityIcon }),
2621
2662
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-tool-trigger__label a24-tool-shimmer", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityReel, { label }) })
2622
2663
  ] });
2623
2664
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "a24-tool-trigger-row a24-tool-trigger-row--root", "data-agents24-model-turn": modelTurnId, role: "status", children: onSelect ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { className: "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--active", onClick: onSelect, type: "button", children: content }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--active", children: content }) });
@@ -2626,6 +2667,7 @@ function AgentChatModelTurnRow({ part }) {
2626
2667
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AgentChatActivityRow, { label: "Planning next moves", modelTurnId: part.modelTurnId });
2627
2668
  }
2628
2669
  function AgentChatNodeActivityRow({
2670
+ renderActivityIcon,
2629
2671
  onSelect,
2630
2672
  part
2631
2673
  }) {
@@ -2644,10 +2686,10 @@ function AgentChatNodeActivityRow({
2644
2686
  const label = running ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-tool-shimmer", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityReel, { label: part.label }) }) : part.label;
2645
2687
  if (!failed) {
2646
2688
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "a24-tool-trigger-row", role: "status", children: onSelect ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
2647
- running ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label: part.label }) : null,
2689
+ running ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label: part.label, kind: "node", part, renderActivityIcon }) : null,
2648
2690
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-tool-trigger__label", children: label })
2649
2691
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: triggerClassName, children: [
2650
- running ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label: part.label }) : null,
2692
+ running ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label: part.label, kind: "node", part, renderActivityIcon }) : null,
2651
2693
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-tool-trigger__label", children: label })
2652
2694
  ] }) });
2653
2695
  }
@@ -2693,6 +2735,7 @@ function ToolDisclosure({
2693
2735
  root,
2694
2736
  showOrb,
2695
2737
  statusLabel,
2738
+ renderActivityIcon,
2696
2739
  accessory
2697
2740
  }) {
2698
2741
  const [open, setOpen] = React11.useState(false);
@@ -2702,10 +2745,10 @@ function ToolDisclosure({
2702
2745
  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");
2703
2746
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: cn("a24-tool-trigger-row", root && "a24-tool-trigger-row--root"), children: [
2704
2747
  onSelect && part ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
2705
- showOrb ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label: statusLabel }) : null,
2748
+ showOrb ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label: statusLabel || "Working", kind: grouped ? "group" : "tool", part, renderActivityIcon }) : null,
2706
2749
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-tool-trigger__label", children: label })
2707
2750
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: triggerClassName, children: [
2708
- showOrb ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label: statusLabel }) : null,
2751
+ showOrb ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label: statusLabel || "Working", kind: grouped ? "group" : "tool", part, renderActivityIcon }) : null,
2709
2752
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-tool-trigger__label", children: label })
2710
2753
  ] }),
2711
2754
  accessory
@@ -2727,7 +2770,7 @@ function ToolDisclosure({
2727
2770
  },
2728
2771
  type: "button",
2729
2772
  children: [
2730
- showOrb ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label: statusLabel }) : null,
2773
+ showOrb ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolActivityOrb, { label: statusLabel || "Working", kind: grouped ? "group" : "tool", part, renderActivityIcon }) : null,
2731
2774
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-tool-trigger__label", children: label }),
2732
2775
  open ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react6.ChevronDown, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react6.ChevronRight, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
2733
2776
  ]
@@ -2788,6 +2831,7 @@ function AgentChatToolRow({
2788
2831
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2789
2832
  ToolDisclosure,
2790
2833
  {
2834
+ renderActivityIcon: slots?.renderActivityIcon,
2791
2835
  error: failed,
2792
2836
  accessory: toolAccessory(context, slots),
2793
2837
  label: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolLabel, { active, label: context.label, part, slots }),
@@ -2834,6 +2878,7 @@ function AgentChatToolSession({
2834
2878
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2835
2879
  ToolDisclosure,
2836
2880
  {
2881
+ renderActivityIcon: slots?.renderActivityIcon,
2837
2882
  error: failed,
2838
2883
  grouped: true,
2839
2884
  label: labelContent,
@@ -2878,6 +2923,7 @@ function AgentChatSubagentGroup({ isLatestActivity = true, isLive = false, part,
2878
2923
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2879
2924
  ToolDisclosure,
2880
2925
  {
2926
+ renderActivityIcon: slots?.renderActivityIcon,
2881
2927
  accessory: slots?.renderToolAccessory?.(context),
2882
2928
  error: failed,
2883
2929
  grouped: true,
@@ -3137,14 +3183,14 @@ function AgentResponseTimeline({
3137
3183
  if (part.kind === "model-turn") {
3138
3184
  const key = activeActivityClaimed ? part.id : `${message.id}-active-activity`;
3139
3185
  activeActivityClaimed = true;
3140
- rendered.push(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AgentChatActivityRow, { label: "Planning next moves", modelTurnId: part.modelTurnId }, key));
3186
+ rendered.push(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AgentChatActivityRow, { renderActivityIcon: toolSlots?.renderActivityIcon, label: "Planning next moves", modelTurnId: part.modelTurnId }, key));
3141
3187
  continue;
3142
3188
  }
3143
3189
  if (part.kind === "node-activity") {
3144
3190
  const key = activeActivityClaimed ? part.id : `${message.id}-active-activity`;
3145
3191
  if (part.status === "running") activeActivityClaimed = true;
3146
3192
  rendered.push(
3147
- part.status === "running" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AgentChatActivityRow, { label: part.label, onSelect: onNodeSelect ? () => onNodeSelect(part) : void 0 }, key) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AgentChatNodeActivityRow, { onSelect: onNodeSelect, part }, part.id)
3193
+ part.status === "running" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AgentChatActivityRow, { renderActivityIcon: toolSlots?.renderActivityIcon, label: part.label, onSelect: onNodeSelect ? () => onNodeSelect(part) : void 0 }, key) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AgentChatNodeActivityRow, { renderActivityIcon: toolSlots?.renderActivityIcon, onSelect: onNodeSelect, part }, part.id)
3148
3194
  );
3149
3195
  continue;
3150
3196
  }
@@ -3173,7 +3219,7 @@ function AgentResponseTimeline({
3173
3219
  rendered.push(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageResponse, { children: message.content }, `${message.id}-fallback`));
3174
3220
  }
3175
3221
  if (rendered.length === 0 && isLoading) {
3176
- rendered.push(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AgentChatActivityRow, { label: "Thinking" }, `${message.id}-active-activity`));
3222
+ rendered.push(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AgentChatActivityRow, { renderActivityIcon: toolSlots?.renderActivityIcon, label: "Thinking" }, `${message.id}-active-activity`));
3177
3223
  }
3178
3224
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "space-y-3", children: rendered });
3179
3225
  }