@agents24/chat-react 0.5.37 → 0.5.38

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `@agents24/chat-react`
2
2
 
3
- Last Updated: 2026-10-07
3
+ Last Updated: 2026-10-25
4
4
 
5
5
  Reusable React chat UI, runtime orchestration, and the canonical source-owned Agents24 chat scaffold.
6
6
 
@@ -30,6 +30,36 @@ Agent/model selector in the composer, preserves the active controller,
30
30
  transcript, thread, and draft while switching, and adopts the durable thread
31
31
  ID from the first accepted runtime event.
32
32
 
33
+ ## Fill the default composer with a suggestion
34
+
35
+ Keep the default composer and its attachment handling using a public ref:
36
+
37
+ ```tsx
38
+ import { useRef } from "react";
39
+ import { AgentChatComposer, type AgentChatComposerHandle, type AgentChatComposerSubmit } from "@agents24/chat-react/ui";
40
+
41
+ function SuggestedComposer({ scopeKey, onSubmit }: {
42
+ scopeKey: string;
43
+ onSubmit: (message: AgentChatComposerSubmit) => void | Promise<void>;
44
+ }) {
45
+ const composerRef = useRef<AgentChatComposerHandle>(null);
46
+ const prompt = "Find a patient with [prescription details].";
47
+ return <>
48
+ <button type="button" onClick={() => composerRef.current?.setText(prompt, {
49
+ focus: true,
50
+ selection: { start: prompt.indexOf("["), end: prompt.indexOf("]") + 1 },
51
+ })}>Find a patient</button>
52
+ <AgentChatComposer ref={composerRef} scopeKey={scopeKey} onSubmit={onSubmit} />
53
+ </>;
54
+ }
55
+ ```
56
+
57
+ `setText` preserves attached files and previews, updates validation/expansion, and never submits. Focus is optional and defaults to false; selection uses UTF-16 offsets into the new text and does not imply focus. Both apply after text commits. Disabled composers still accept text but skip focus and selection. Existing `textareaRef` remains available independently.
58
+
59
+ Every accepted update advances the draft revision, protecting suggestions and newer user edits from failed-send recovery. Use the current ref and change `scopeKey` with conversation/identity boundaries. Saved handles return false after a scope change or unmount; accepted updates return true. Scope changes invalidate operations; hosts continue to own conversation draft restoration through `restoredDraft`.
60
+
61
+ This API is pending the coordinated package release; it is not available in the already published `0.5.37`.
62
+
33
63
  ## Timeline customization
34
64
 
35
65
  `@agents24/react` projects backend response blocks into canonical ordered `message.parts`. The default scaffold renders that sequence through `AgentResponseTimeline`, preserving interleaved text, tools, HITL, and resumed text.
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractHash": "904701fe1c173854cd4154f2156020da471397fbc6bb644cea60f44c71a1a0d2",
2
+ "contractHash": "6d6ceb78b4285f987a5635b4d4d0b407732bf5e61abd1aca1369a97a7d6336e6",
3
3
  "contractVersion": "0.3.0",
4
4
  "corpus": "conformance/v1/corpus.json",
5
5
  "generatedBy": "@agents24/sdk-contract",
@@ -15,11 +15,11 @@
15
15
  "reactDom": ">=19 <20"
16
16
  },
17
17
  "@agents24/client": {
18
- "contract": "904701fe1c173854cd4154f2156020da471397fbc6bb644cea60f44c71a1a0d2",
18
+ "contract": "6d6ceb78b4285f987a5635b4d4d0b407732bf5e61abd1aca1369a97a7d6336e6",
19
19
  "runtime": "run-stream.v3"
20
20
  },
21
21
  "@agents24/node": {
22
- "contract": "904701fe1c173854cd4154f2156020da471397fbc6bb644cea60f44c71a1a0d2",
22
+ "contract": "6d6ceb78b4285f987a5635b4d4d0b407732bf5e61abd1aca1369a97a7d6336e6",
23
23
  "node": "^22.0.0 || ^24.0.0"
24
24
  },
25
25
  "@agents24/react": {
@@ -27,11 +27,11 @@
27
27
  "react": ">=19 <20"
28
28
  },
29
29
  "@talmudpedia/runtime-sdk": {
30
- "contract": "904701fe1c173854cd4154f2156020da471397fbc6bb644cea60f44c71a1a0d2",
30
+ "contract": "6d6ceb78b4285f987a5635b4d4d0b407732bf5e61abd1aca1369a97a7d6336e6",
31
31
  "protocolOwner": "@agents24/client"
32
32
  },
33
33
  "agents24": {
34
- "contract": "904701fe1c173854cd4154f2156020da471397fbc6bb644cea60f44c71a1a0d2",
34
+ "contract": "6d6ceb78b4285f987a5635b4d4d0b407732bf5e61abd1aca1369a97a7d6336e6",
35
35
  "python": ">=3.10"
36
36
  }
37
37
  },
@@ -5,9 +5,9 @@
5
5
  "title": "Agents24 Chat",
6
6
  "description": "Complete editable Agents24 chat, inheriting your application's theme.",
7
7
  "dependencies": [
8
- "@agents24/chat-react@0.5.37",
9
- "@agents24/client@0.2.19",
10
- "@agents24/react@0.2.24",
8
+ "@agents24/chat-react@0.5.38",
9
+ "@agents24/client@0.2.20",
10
+ "@agents24/react@0.2.25",
11
11
  "@shadcn/react@^0.2.1",
12
12
  "class-variance-authority@^0.7.1",
13
13
  "clsx@^2.1.1",
@@ -254,13 +254,13 @@
254
254
  }
255
255
  ],
256
256
  "meta": {
257
- "version": "0.5.37",
257
+ "version": "0.5.38",
258
258
  "source": "packages/agents24-chat-react/scaffold",
259
259
  "source_sha256": "411a13a78466b84a923ae81a16d1b032deb758e7f44c1df5915cfdff71df6d62",
260
260
  "agents24": {
261
- "@agents24/client": "0.2.19",
262
- "@agents24/react": "0.2.24",
263
- "@agents24/chat-react": "0.5.37"
261
+ "@agents24/client": "0.2.20",
262
+ "@agents24/react": "0.2.25",
263
+ "@agents24/chat-react": "0.5.38"
264
264
  }
265
265
  },
266
266
  "docs": "Import the installed styles.css from your application stylesheet. Pass your configured AgentClient to Agents24ChatScaffold. Authentication and URLs remain host-owned. Installed UI is editable; review changes before reinstalling and do not use --overwrite on customized files."
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import type { AgentChatComposerFile } from "./composer-attachments";
3
+ import { type AgentChatComposerTextOptions } from "./use-agent-chat-composer";
3
4
  import type { AgentChatInputContract } from "../runtime/input-contract";
4
5
  import type { ContextWindow } from "@agents24/client/protocol";
5
6
  export type { AgentChatComposerFile } from "./composer-attachments";
@@ -8,7 +9,13 @@ export type AgentChatComposerSubmit = {
8
9
  files: AgentChatComposerFile[];
9
10
  attachmentIds?: string[];
10
11
  };
12
+ export type AgentChatComposerHandle = {
13
+ /** Replace only text, preserving attachments. Returns false after scope change or unmount. */
14
+ setText(text: string, options?: AgentChatComposerTextOptions): boolean;
15
+ };
11
16
  export type AgentChatComposerProps = {
17
+ /** Public draft controls for the default composer. */
18
+ ref?: React.Ref<AgentChatComposerHandle>;
12
19
  inputContract?: AgentChatInputContract | null;
13
20
  recordingEnabled?: boolean;
14
21
  contextWindow?: ContextWindow | null;
@@ -35,4 +42,4 @@ export type AgentChatComposerProps = {
35
42
  } | null;
36
43
  textareaRef?: React.Ref<HTMLTextAreaElement>;
37
44
  };
38
- export declare function AgentChatComposer({ inputContract, recordingEnabled, contextWindow, accept, allowAttachments, className, disabled, forceExpanded, focusKey, scopeKey, inputToolbarContent, isRunning, isCancelling, onAttachmentCountChange, onStop, onSubmit, placeholder, restoredDraft, textareaRef, }: AgentChatComposerProps): import("react/jsx-runtime").JSX.Element;
45
+ export declare function AgentChatComposer({ ref, inputContract, recordingEnabled, contextWindow, accept, allowAttachments, className, disabled, forceExpanded, focusKey, scopeKey, inputToolbarContent, isRunning, isCancelling, onAttachmentCountChange, onStop, onSubmit, placeholder, restoredDraft, textareaRef, }: AgentChatComposerProps): import("react/jsx-runtime").JSX.Element;
package/dist/ui/index.cjs CHANGED
@@ -1099,15 +1099,33 @@ function useAgentChatComposer(options) {
1099
1099
  const generationRef = React4.useRef(0);
1100
1100
  const mountedRef = React4.useRef(true);
1101
1101
  const textRef = React4.useRef(text);
1102
+ const scope = React4.useMemo(() => ({ active: false }), [options.scopeKey]);
1103
+ const [textInteraction, setTextInteraction] = React4.useState(null);
1104
+ const handledTextInteractionRef = React4.useRef(textInteraction);
1102
1105
  React4.useLayoutEffect(() => {
1106
+ scope.active = true;
1103
1107
  mountedRef.current = true;
1104
1108
  submittingRef.current = false;
1105
1109
  setIsSubmitting(false);
1106
1110
  return () => {
1111
+ scope.active = false;
1107
1112
  mountedRef.current = false;
1108
1113
  generationRef.current += 1;
1109
1114
  };
1110
- }, [options.scopeKey]);
1115
+ }, [scope]);
1116
+ React4.useLayoutEffect(() => {
1117
+ if (handledTextInteractionRef.current === textInteraction) return;
1118
+ handledTextInteractionRef.current = textInteraction;
1119
+ if (!textInteraction || textInteraction.scope !== scope || !scope.active || textInteraction.revision !== revisionRef.current || options.disabled) return;
1120
+ const textarea = textareaRef.current;
1121
+ if (!textarea) return;
1122
+ if (textInteraction.options.focus) textarea.focus({ preventScroll: true });
1123
+ const selection = textInteraction.options.selection;
1124
+ if (selection) {
1125
+ const clamp = (offset) => Math.max(0, Math.min(textRef.current.length, offset));
1126
+ textarea.setSelectionRange(clamp(selection.start), clamp(selection.end), selection.direction);
1127
+ }
1128
+ }, [textInteraction, scope, textareaRef, options.disabled]);
1111
1129
  React4.useEffect(() => () => {
1112
1130
  revokeAgentChatComposerFiles(filesRef.current);
1113
1131
  revokeAgentChatComposerFiles([...inFlightFilesRef.current.values()].flat());
@@ -1132,11 +1150,14 @@ function useAgentChatComposer(options) {
1132
1150
  }
1133
1151
  });
1134
1152
  const attachmentMimeTypes = inputMimeTypes(options.inputContract);
1135
- const setText = (value) => {
1136
- revisionRef.current += 1;
1153
+ const setText = React4.useCallback((value, interaction = {}) => {
1154
+ if (!scope.active) return false;
1155
+ const revision = ++revisionRef.current;
1137
1156
  textRef.current = value;
1138
1157
  setTextState(value);
1139
- };
1158
+ setTextInteraction({ scope, revision, options: interaction });
1159
+ return true;
1160
+ }, [scope]);
1140
1161
  const addFiles = (selected) => {
1141
1162
  const accepted = selected.filter((file) => attachmentMimeTypes.includes(canonicalInputMimeType(file.type, file.name)));
1142
1163
  revisionRef.current += 1;
@@ -1349,6 +1370,7 @@ function formatFileCount(files) {
1349
1370
  var pillButtonClass = "inline-flex size-8 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-muted text-sm text-muted-foreground shadow-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
1350
1371
  var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-primary text-sm text-primary-foreground shadow-none transition-[background-color,color,opacity,transform] duration-200 hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
1351
1372
  function AgentChatComposer({
1373
+ ref,
1352
1374
  inputContract,
1353
1375
  recordingEnabled,
1354
1376
  contextWindow,
@@ -1384,6 +1406,7 @@ function AgentChatComposer({
1384
1406
  onSubmit: (text2, files2) => onSubmit({ text: text2.trim(), files: files2, attachmentIds: files2.flatMap((file) => file.id ? [file.id] : []) })
1385
1407
  });
1386
1408
  const { text, files, setText, isSubmitting, canSubmit, action, submit } = composer;
1409
+ React6.useImperativeHandle(ref, () => ({ setText }), [setText]);
1387
1410
  const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
1388
1411
  const setTextareaRef = React6.useCallback(
1389
1412
  (node) => {