@cntyclub/agent-react 0.16.0 → 0.18.0

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/dist/index.js CHANGED
@@ -1,11 +1,27 @@
1
- import { cn, Button, ThinkingOrb, Tooltip, TooltipTrigger, TooltipContent, ChatMessage, ChatMessageBubble, Markdown, AuroraText, Spinner, Checkbox, Collapsible, CollapsibleTrigger, ChatToolChip, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Group, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, Chat, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, useMediaQuery, TooltipProvider, DataTablePaged } from '@cntyclub/ui-react';
1
+ import { cn, Button, ThinkingOrb, Tooltip, TooltipTrigger, TooltipContent, ChatMessage, ChatMessageBubble, Markdown, AuroraText, Spinner, Checkbox, Collapsible, CollapsibleTrigger, ChatToolChip, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Group, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, ContextMeter, Chat, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, useMediaQuery, TooltipProvider, DataTablePaged } from '@cntyclub/ui-react';
2
2
  import { XIcon, Trash2Icon, SquareIcon, PauseIcon, PlayIcon, CheckIcon, FileTextIcon, PaperclipIcon, MicIcon, ArrowUpIcon, CopyIcon, Maximize2Icon, ListChecksIcon, ChevronDownIcon, ShieldAlertIcon, CheckCheckIcon, UploadCloudIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, ChevronsRightIcon, SparklesIcon, DownloadIcon, CodeIcon } from 'lucide-react';
3
- import * as React11 from 'react';
3
+ import * as React12 from 'react';
4
4
  import { AnimatePresence, motion } from 'motion/react';
5
5
  import { createPortal } from 'react-dom';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
 
8
8
  // src/api-client.ts
9
+ function collabBody(options) {
10
+ if (!options) return {};
11
+ const body = {};
12
+ if (options.idempotencyKey) body.idempotency_key = options.idempotencyKey;
13
+ if (options.replyToId) body.reply_to_id = options.replyToId;
14
+ if (options.threadRootId) body.thread_root_id = options.threadRootId;
15
+ if (options.mentionUserIds?.length) body.mention_user_ids = options.mentionUserIds;
16
+ return body;
17
+ }
18
+ function appendCollab(form, options) {
19
+ if (!options) return;
20
+ if (options.idempotencyKey) form.append("idempotency_key", options.idempotencyKey);
21
+ if (options.replyToId) form.append("reply_to_id", options.replyToId);
22
+ if (options.threadRootId) form.append("thread_root_id", options.threadRootId);
23
+ for (const id of options.mentionUserIds ?? []) form.append("mention_user_ids", id);
24
+ }
9
25
  var AgentApiError = class extends Error {
10
26
  status;
11
27
  constructor(message, status) {
@@ -65,20 +81,22 @@ var AgentApiClient = class {
65
81
  method: "POST"
66
82
  });
67
83
  }
68
- sendMessage(message, conversationId, files) {
84
+ sendMessage(message, conversationId, files, options) {
69
85
  if (files && files.length > 0) {
70
86
  const form = new FormData();
71
87
  form.append("client_id", this.config.clientId);
72
88
  form.append("message", message);
73
89
  if (conversationId) form.append("conversation_id", conversationId);
74
90
  for (const file of files) form.append("files", file, file.name);
91
+ appendCollab(form, options);
75
92
  return this.request("/agent-mode/chat/", { body: form, method: "POST" });
76
93
  }
77
94
  return this.request("/agent-mode/chat/", {
78
95
  body: {
79
96
  client_id: this.config.clientId,
80
97
  conversation_id: conversationId ?? null,
81
- message
98
+ message,
99
+ ...collabBody(options)
82
100
  },
83
101
  method: "POST"
84
102
  });
@@ -91,7 +109,7 @@ var AgentApiClient = class {
91
109
  }
92
110
  // ------------------------------------------------------------- streaming
93
111
  /** POST a chat turn and yield parsed SSE events as they stream in. */
94
- async streamMessage(message, conversationId, files, onEvent, signal) {
112
+ async streamMessage(message, conversationId, files, onEvent, signal, options) {
95
113
  let body;
96
114
  const headers = await this.streamHeaders();
97
115
  if (files && files.length > 0) {
@@ -100,13 +118,15 @@ var AgentApiClient = class {
100
118
  form.append("message", message);
101
119
  if (conversationId) form.append("conversation_id", conversationId);
102
120
  for (const file of files) form.append("files", file, file.name);
121
+ appendCollab(form, options);
103
122
  body = form;
104
123
  } else {
105
124
  headers["Content-Type"] = "application/json";
106
125
  body = JSON.stringify({
107
126
  client_id: this.config.clientId,
108
127
  conversation_id: conversationId ?? null,
109
- message
128
+ message,
129
+ ...collabBody(options)
110
130
  });
111
131
  }
112
132
  await this.consumeStream("/agent-mode/chat/stream/", { body, headers, signal }, onEvent);
@@ -184,14 +204,219 @@ var AgentApiClient = class {
184
204
  listConversations() {
185
205
  return this.request("/agent-mode/conversations/");
186
206
  }
187
- getConversation(conversationId) {
188
- return this.request(`/agent-mode/conversations/${conversationId}/`);
207
+ getConversation(conversationId, options) {
208
+ const qs = new URLSearchParams();
209
+ if (options?.beforeSeq != null) qs.set("before_seq", String(options.beforeSeq));
210
+ if (options?.limit != null) qs.set("limit", String(options.limit));
211
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
212
+ return this.request(`/agent-mode/conversations/${conversationId}/${suffix}`);
189
213
  }
214
+ /** Soft-delete (creator/admin) or leave (member). Never a hard delete. */
190
215
  deleteConversation(conversationId) {
191
216
  return this.request(`/agent-mode/conversations/${conversationId}/`, {
192
217
  method: "DELETE"
193
218
  });
194
219
  }
220
+ // ------------------------------------------------------------- collaboration
221
+ setVisibility(conversationId, visibility, scope) {
222
+ return this.request(`/agent-mode/conversations/${conversationId}/visibility/`, {
223
+ method: "POST",
224
+ body: {
225
+ client_id: this.config.clientId,
226
+ visibility,
227
+ scope_type: scope?.scopeType ?? "none",
228
+ scope_event_id: scope?.scopeEventId
229
+ }
230
+ });
231
+ }
232
+ listParticipants(conversationId) {
233
+ return this.request(`/agent-mode/conversations/${conversationId}/participants/`);
234
+ }
235
+ markRead(conversationId, seq) {
236
+ return this.request(`/agent-mode/conversations/${conversationId}/read/`, {
237
+ method: "POST",
238
+ body: { client_id: this.config.clientId, seq }
239
+ });
240
+ }
241
+ saveDraft(conversationId, text) {
242
+ return this.request(`/agent-mode/conversations/${conversationId}/draft/`, {
243
+ method: "POST",
244
+ body: { client_id: this.config.clientId, text }
245
+ });
246
+ }
247
+ setFlags(conversationId, flags) {
248
+ return this.request(`/agent-mode/conversations/${conversationId}/flags/`, {
249
+ method: "POST",
250
+ body: { client_id: this.config.clientId, ...flags }
251
+ });
252
+ }
253
+ listFiles(conversationId) {
254
+ return this.request(`/agent-mode/conversations/${conversationId}/files/`);
255
+ }
256
+ summarize(conversationId) {
257
+ return this.request(`/agent-mode/conversations/${conversationId}/summarize/`, {
258
+ method: "POST",
259
+ body: { client_id: this.config.clientId }
260
+ });
261
+ }
262
+ listContext(conversationId) {
263
+ return this.request(`/agent-mode/conversations/${conversationId}/context/`);
264
+ }
265
+ pinContext(conversationId, item) {
266
+ return this.request(`/agent-mode/conversations/${conversationId}/context/`, {
267
+ method: "POST",
268
+ body: {
269
+ client_id: this.config.clientId,
270
+ kind: item.kind,
271
+ label: item.label,
272
+ ref_type: item.refType,
273
+ ref_id: item.refId
274
+ }
275
+ });
276
+ }
277
+ editMessage(conversationId, messageId, message) {
278
+ return this.request(`/agent-mode/conversations/${conversationId}/messages/${messageId}/`, {
279
+ method: "PATCH",
280
+ body: { client_id: this.config.clientId, message }
281
+ });
282
+ }
283
+ deleteMessage(conversationId, messageId) {
284
+ return this.request(`/agent-mode/conversations/${conversationId}/messages/${messageId}/`, {
285
+ method: "DELETE"
286
+ });
287
+ }
288
+ search(query, limit = 20) {
289
+ const qs = new URLSearchParams({ query, limit: String(limit) });
290
+ return this.request(`/agent-mode/search/?${qs.toString()}`);
291
+ }
292
+ getToolTrust() {
293
+ return this.request("/agent-mode/tool-trust/");
294
+ }
295
+ setToolTrust(toolName, trusted) {
296
+ return this.request("/agent-mode/tool-trust/", {
297
+ method: "POST",
298
+ body: { client_id: this.config.clientId, tool_name: toolName, trusted }
299
+ });
300
+ }
301
+ };
302
+
303
+ // src/realtime-client.ts
304
+ var AgentRealtimeClient = class {
305
+ config;
306
+ conversationId;
307
+ handlers;
308
+ socket = null;
309
+ lastSeq = 0;
310
+ closed = false;
311
+ retries = 0;
312
+ reconnectTimer = null;
313
+ constructor(config, conversationId, handlers, lastSeq = 0) {
314
+ this.config = config;
315
+ this.conversationId = conversationId;
316
+ this.handlers = handlers;
317
+ this.lastSeq = lastSeq;
318
+ }
319
+ async connect() {
320
+ if (this.closed || typeof WebSocket === "undefined") return;
321
+ const url = await this.buildUrl();
322
+ if (!url) return;
323
+ this.handlers.onStatus?.("connecting");
324
+ let socket;
325
+ try {
326
+ socket = new WebSocket(url);
327
+ } catch {
328
+ this.scheduleReconnect();
329
+ return;
330
+ }
331
+ this.socket = socket;
332
+ socket.onopen = () => {
333
+ this.retries = 0;
334
+ this.handlers.onStatus?.("open");
335
+ this.send({ action: "sync", last_seq: this.lastSeq });
336
+ };
337
+ socket.onmessage = (raw) => {
338
+ let event;
339
+ try {
340
+ event = JSON.parse(raw.data);
341
+ } catch {
342
+ return;
343
+ }
344
+ this.trackSeq(event);
345
+ this.handlers.onEvent(event);
346
+ };
347
+ socket.onclose = () => {
348
+ this.handlers.onStatus?.("closed");
349
+ if (!this.closed) this.scheduleReconnect();
350
+ };
351
+ socket.onerror = () => {
352
+ try {
353
+ socket.close();
354
+ } catch {
355
+ }
356
+ };
357
+ }
358
+ /** Advance the local high-water mark so a reconnect replays only the true gap. */
359
+ trackSeq(event) {
360
+ if (event.type === "sync") {
361
+ if (typeof event.last_seq === "number") this.lastSeq = Math.max(this.lastSeq, event.last_seq);
362
+ } else if (event.type === "message" || event.type === "message_edited") {
363
+ const seq = event.message?.seq;
364
+ if (typeof seq === "number") this.lastSeq = Math.max(this.lastSeq, seq);
365
+ }
366
+ }
367
+ setLastSeq(seq) {
368
+ if (seq > this.lastSeq) this.lastSeq = seq;
369
+ }
370
+ sendTyping(isTyping) {
371
+ this.send({ action: "typing", is_typing: isTyping });
372
+ }
373
+ markRead(seq) {
374
+ this.setLastSeq(seq);
375
+ this.send({ action: "read", seq });
376
+ }
377
+ requestSync() {
378
+ this.send({ action: "sync", last_seq: this.lastSeq });
379
+ }
380
+ close() {
381
+ this.closed = true;
382
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
383
+ this.reconnectTimer = null;
384
+ if (this.socket) {
385
+ try {
386
+ this.socket.close();
387
+ } catch {
388
+ }
389
+ this.socket = null;
390
+ }
391
+ }
392
+ send(payload) {
393
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
394
+ try {
395
+ this.socket.send(JSON.stringify(payload));
396
+ } catch {
397
+ }
398
+ }
399
+ }
400
+ scheduleReconnect() {
401
+ if (this.closed || this.reconnectTimer) return;
402
+ this.retries += 1;
403
+ const delay = Math.min(3e4, 1e3 * 2 ** Math.min(this.retries, 5));
404
+ this.reconnectTimer = setTimeout(() => {
405
+ this.reconnectTimer = null;
406
+ void this.connect();
407
+ }, delay);
408
+ }
409
+ async buildUrl() {
410
+ const token = await this.config.getAccessToken();
411
+ if (!token) return null;
412
+ const base = (this.config.realtime?.baseUrl ?? this.config.apiBaseUrl).replace(/\/$/, "").replace(/^http/, "ws");
413
+ const params = new URLSearchParams({
414
+ client_id: this.config.clientId,
415
+ token,
416
+ last_seq: String(this.lastSeq)
417
+ });
418
+ return `${base}/ws/agent-mode/conversations/${this.conversationId}/?${params.toString()}`;
419
+ }
195
420
  };
196
421
 
197
422
  // src/file-support.ts
@@ -259,7 +484,7 @@ function formatBytes(bytes) {
259
484
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
260
485
  }
261
486
  function AgentImageLightbox({ src, alt, onClose }) {
262
- React11.useEffect(() => {
487
+ React12.useEffect(() => {
263
488
  if (!src) return;
264
489
  const onKey = (event) => {
265
490
  if (event.key === "Escape") onClose();
@@ -329,24 +554,24 @@ function pickMimeType() {
329
554
  return "";
330
555
  }
331
556
  function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError }) {
332
- const [phase, setPhase] = React11.useState("recording");
333
- const [elapsed, setElapsed] = React11.useState(0);
334
- const [playing, setPlaying] = React11.useState(false);
335
- const recorderRef = React11.useRef(null);
336
- const streamRef = React11.useRef(null);
337
- const chunksRef = React11.useRef([]);
338
- const blobRef = React11.useRef(null);
339
- const audioUrlRef = React11.useRef(null);
340
- const audioRef = React11.useRef(null);
341
- const timerRef = React11.useRef(null);
342
- const canvasRef = React11.useRef(null);
343
- const audioContextRef = React11.useRef(null);
344
- const analyserRef = React11.useRef(null);
345
- const rafRef = React11.useRef(null);
346
- const levelsRef = React11.useRef(new Array(MAX_BARS).fill(0));
347
- const onErrorRef = React11.useRef(onError);
557
+ const [phase, setPhase] = React12.useState("recording");
558
+ const [elapsed, setElapsed] = React12.useState(0);
559
+ const [playing, setPlaying] = React12.useState(false);
560
+ const recorderRef = React12.useRef(null);
561
+ const streamRef = React12.useRef(null);
562
+ const chunksRef = React12.useRef([]);
563
+ const blobRef = React12.useRef(null);
564
+ const audioUrlRef = React12.useRef(null);
565
+ const audioRef = React12.useRef(null);
566
+ const timerRef = React12.useRef(null);
567
+ const canvasRef = React12.useRef(null);
568
+ const audioContextRef = React12.useRef(null);
569
+ const analyserRef = React12.useRef(null);
570
+ const rafRef = React12.useRef(null);
571
+ const levelsRef = React12.useRef(new Array(MAX_BARS).fill(0));
572
+ const onErrorRef = React12.useRef(onError);
348
573
  onErrorRef.current = onError;
349
- const stopMeter = React11.useCallback(() => {
574
+ const stopMeter = React12.useCallback(() => {
350
575
  if (rafRef.current != null) {
351
576
  cancelAnimationFrame(rafRef.current);
352
577
  rafRef.current = null;
@@ -357,7 +582,7 @@ function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError
357
582
  if (ctx && ctx.state !== "closed") void ctx.close().catch(() => {
358
583
  });
359
584
  }, []);
360
- const stopTracks = React11.useCallback(() => {
585
+ const stopTracks = React12.useCallback(() => {
361
586
  streamRef.current?.getTracks().forEach((track) => track.stop());
362
587
  streamRef.current = null;
363
588
  if (timerRef.current) {
@@ -366,7 +591,7 @@ function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError
366
591
  }
367
592
  stopMeter();
368
593
  }, [stopMeter]);
369
- const startMeter = React11.useCallback((stream) => {
594
+ const startMeter = React12.useCallback((stream) => {
370
595
  const Ctor = typeof window === "undefined" ? void 0 : window.AudioContext ?? window.webkitAudioContext;
371
596
  if (!Ctor) return;
372
597
  let context;
@@ -436,7 +661,7 @@ function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError
436
661
  };
437
662
  rafRef.current = requestAnimationFrame(draw);
438
663
  }, []);
439
- React11.useEffect(() => {
664
+ React12.useEffect(() => {
440
665
  let cancelled = false;
441
666
  void (async () => {
442
667
  if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
@@ -589,40 +814,40 @@ function AgentComposer({
589
814
  voiceEnabled,
590
815
  voiceMaxSeconds = 600
591
816
  }) {
592
- const textareaRef = React11.useRef(null);
593
- const inputRef = React11.useRef(null);
594
- const [recording, setRecording] = React11.useState(false);
595
- const [lightboxSrc, setLightboxSrc] = React11.useState(null);
817
+ const textareaRef = React12.useRef(null);
818
+ const inputRef = React12.useRef(null);
819
+ const [recording, setRecording] = React12.useState(false);
820
+ const [lightboxSrc, setLightboxSrc] = React12.useState(null);
596
821
  const canSubmit = !disabled && !busy && (value.trim().length > 0 || files.length > 0);
597
822
  const voiceAvailable = Boolean(voiceEnabled && onTranscribe);
598
- const submit = React11.useCallback(() => {
823
+ const submit = React12.useCallback(() => {
599
824
  if (!canSubmit) return;
600
825
  onSubmit(value.trim());
601
826
  textareaRef.current?.focus();
602
827
  }, [canSubmit, onSubmit, value]);
603
- React11.useEffect(() => {
828
+ React12.useEffect(() => {
604
829
  if (!disabled) textareaRef.current?.focus();
605
830
  }, [disabled]);
606
- React11.useEffect(() => {
831
+ React12.useEffect(() => {
607
832
  const textarea = textareaRef.current;
608
833
  if (!textarea) return;
609
834
  textarea.style.height = "auto";
610
835
  textarea.style.height = `${Math.min(textarea.scrollHeight, 144)}px`;
611
836
  }, [value]);
612
- const previews = React11.useMemo(
837
+ const previews = React12.useMemo(
613
838
  () => files.map((file, originalIndex) => {
614
839
  const image = isImageFile(file);
615
840
  return { file, image, originalIndex, url: image ? URL.createObjectURL(file) : null };
616
841
  }),
617
842
  [files]
618
843
  );
619
- React11.useEffect(
844
+ React12.useEffect(
620
845
  () => () => {
621
846
  previews.forEach((preview) => preview.url && URL.revokeObjectURL(preview.url));
622
847
  },
623
848
  [previews]
624
849
  );
625
- const ordered = React11.useMemo(
850
+ const ordered = React12.useMemo(
626
851
  () => [...previews].sort((a, b) => Number(b.image) - Number(a.image)),
627
852
  [previews]
628
853
  );
@@ -867,15 +1092,15 @@ function groupMessages(messages) {
867
1092
  return items;
868
1093
  }
869
1094
  function ChatScroller({ children, className, ...props }) {
870
- const viewportRef = React11.useRef(null);
871
- const pinnedRef = React11.useRef(true);
872
- const handleScroll = React11.useCallback(() => {
1095
+ const viewportRef = React12.useRef(null);
1096
+ const pinnedRef = React12.useRef(true);
1097
+ const handleScroll = React12.useCallback(() => {
873
1098
  const viewport = viewportRef.current;
874
1099
  if (!viewport) return;
875
1100
  const distanceFromBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
876
1101
  pinnedRef.current = distanceFromBottom < 64;
877
1102
  }, []);
878
- React11.useEffect(() => {
1103
+ React12.useEffect(() => {
879
1104
  const viewport = viewportRef.current;
880
1105
  if (!viewport || !pinnedRef.current) return;
881
1106
  viewport.scrollTop = viewport.scrollHeight;
@@ -892,15 +1117,66 @@ function ChatScroller({ children, className, ...props }) {
892
1117
  }
893
1118
  );
894
1119
  }
1120
+ var MIN_CHARS_PER_FRAME = 2;
1121
+ var CATCH_UP = 0.22;
1122
+ function usePrefersReducedMotion() {
1123
+ const [reduced, setReduced] = React12.useState(
1124
+ () => typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches
1125
+ );
1126
+ React12.useEffect(() => {
1127
+ if (typeof matchMedia === "undefined") return;
1128
+ const mq = matchMedia("(prefers-reduced-motion: reduce)");
1129
+ const onChange = (event) => setReduced(event.matches);
1130
+ mq.addEventListener("change", onChange);
1131
+ return () => mq.removeEventListener("change", onChange);
1132
+ }, []);
1133
+ return reduced;
1134
+ }
1135
+ function useSmoothText(target, animate) {
1136
+ const prefersReduced = usePrefersReducedMotion();
1137
+ const enabled = animate && !prefersReduced;
1138
+ const [count, setCount] = React12.useState(() => enabled ? 0 : target.length);
1139
+ const countRef = React12.useRef(count);
1140
+ countRef.current = count;
1141
+ const everEnabled = React12.useRef(enabled);
1142
+ if (enabled) everEnabled.current = true;
1143
+ React12.useEffect(() => {
1144
+ if (!everEnabled.current) {
1145
+ if (countRef.current !== target.length) setCount(target.length);
1146
+ return;
1147
+ }
1148
+ if (countRef.current > target.length) {
1149
+ setCount(target.length);
1150
+ return;
1151
+ }
1152
+ if (countRef.current >= target.length) return;
1153
+ let raf = 0;
1154
+ let last = performance.now();
1155
+ const tick = (now) => {
1156
+ const dt = Math.min(64, now - last);
1157
+ last = now;
1158
+ const frames = dt / (1e3 / 60);
1159
+ const gap = target.length - countRef.current;
1160
+ const step = Math.max(MIN_CHARS_PER_FRAME * frames, gap * CATCH_UP * frames);
1161
+ const next = Math.min(target.length, countRef.current + step);
1162
+ countRef.current = next;
1163
+ setCount(next);
1164
+ if (next < target.length) raf = requestAnimationFrame(tick);
1165
+ };
1166
+ raf = requestAnimationFrame(tick);
1167
+ return () => cancelAnimationFrame(raf);
1168
+ }, [target, enabled]);
1169
+ return everEnabled.current ? target.slice(0, Math.floor(count)) : target;
1170
+ }
895
1171
  function CopyButton({ className, text }) {
896
- const [copied, setCopied] = React11.useState(false);
897
- const timerRef = React11.useRef(null);
898
- React11.useEffect(() => {
1172
+ const [copied, setCopied] = React12.useState(false);
1173
+ const timerRef = React12.useRef(null);
1174
+ React12.useEffect(() => {
899
1175
  return () => {
900
1176
  if (timerRef.current) clearTimeout(timerRef.current);
901
1177
  };
902
1178
  }, []);
903
- const copy = React11.useCallback(async () => {
1179
+ const copy = React12.useCallback(async () => {
904
1180
  try {
905
1181
  await navigator.clipboard.writeText(text);
906
1182
  } catch {
@@ -932,8 +1208,9 @@ function CopyButton({ className, text }) {
932
1208
  ] });
933
1209
  }
934
1210
  var isImageAttachment = (format) => IMAGE_ACCEPT.includes(`.${format.toLowerCase()}`);
935
- function MessageItem({ message }) {
936
- const [lightboxSrc, setLightboxSrc] = React11.useState(null);
1211
+ function MessageItem({ animate = false, message }) {
1212
+ const [lightboxSrc, setLightboxSrc] = React12.useState(null);
1213
+ const displayed = useSmoothText(message.content ?? "", animate && message.role === "assistant");
937
1214
  if (message.role === "user") {
938
1215
  const attachments = message.attachments ?? [];
939
1216
  const images = attachments.filter((a) => isImageAttachment(a.format) && a.url);
@@ -989,7 +1266,7 @@ function MessageItem({ message }) {
989
1266
  if (src) setLightboxSrc(src);
990
1267
  },
991
1268
  role: "presentation",
992
- children: /* @__PURE__ */ jsx(Markdown, { children: message.content })
1269
+ children: /* @__PURE__ */ jsx(Markdown, { children: displayed })
993
1270
  }
994
1271
  ) }),
995
1272
  /* @__PURE__ */ jsx(
@@ -1017,8 +1294,8 @@ var DEFAULT_PHRASES = [
1017
1294
  ];
1018
1295
  function ThinkingIndicator({ className, intervalMs = 1800, phrases }) {
1019
1296
  const words = phrases && phrases.length ? phrases : DEFAULT_PHRASES;
1020
- const [index, setIndex] = React11.useState(0);
1021
- React11.useEffect(() => {
1297
+ const [index, setIndex] = React12.useState(0);
1298
+ React12.useEffect(() => {
1022
1299
  setIndex(0);
1023
1300
  const timer = setInterval(() => {
1024
1301
  setIndex((current) => (current + 1) % words.length);
@@ -1037,14 +1314,7 @@ function ThinkingIndicator({ className, intervalMs = 1800, phrases }) {
1037
1314
  role: "status",
1038
1315
  children: [
1039
1316
  /* @__PURE__ */ jsx(ThinkingOrb, { className: "shrink-0", size: 20, speed: 0.7, state: "solving" }),
1040
- /* @__PURE__ */ jsx(
1041
- "span",
1042
- {
1043
- className: "fade-in-0 animate-in text-xs font-medium duration-300",
1044
- children: /* @__PURE__ */ jsx(AuroraText, { children: words[index] })
1045
- },
1046
- index
1047
- )
1317
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: /* @__PURE__ */ jsx(AuroraText, { speed: 2.2, children: /* @__PURE__ */ jsx("span", { className: "fade-in-0 inline-block animate-in duration-300", children: words[index] }, index) }) })
1048
1318
  ]
1049
1319
  }
1050
1320
  );
@@ -1273,7 +1543,7 @@ function DownloadButton({ block, size = "sm" }) {
1273
1543
  );
1274
1544
  }
1275
1545
  function DocumentViewer({ block, onClose }) {
1276
- React11.useEffect(() => {
1546
+ React12.useEffect(() => {
1277
1547
  const onKey = (event) => event.key === "Escape" && onClose();
1278
1548
  window.addEventListener("keydown", onKey);
1279
1549
  return () => window.removeEventListener("keydown", onKey);
@@ -1298,7 +1568,7 @@ function DocumentViewer({ block, onClose }) {
1298
1568
  ] });
1299
1569
  }
1300
1570
  function DocumentCard({ block }) {
1301
- const [open, setOpen] = React11.useState(false);
1571
+ const [open, setOpen] = React12.useState(false);
1302
1572
  const previewLines = (block.preview || "").split("\n").slice(0, 6).join("\n");
1303
1573
  return /* @__PURE__ */ jsxs("div", { className: "w-full max-w-full overflow-hidden rounded-xl border border-border bg-card shadow-xs", children: [
1304
1574
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-border/72 border-b px-3 py-2", children: [
@@ -1420,7 +1690,7 @@ function StepDetail({
1420
1690
  ] });
1421
1691
  }
1422
1692
  function ToolActivityGroup({ describeToolCall, steps }) {
1423
- const [open, setOpen] = React11.useState(false);
1693
+ const [open, setOpen] = React12.useState(false);
1424
1694
  if (steps.length === 0) return null;
1425
1695
  const last = steps[steps.length - 1];
1426
1696
  const anyRunning = steps.some((step) => step.status === "running");
@@ -1513,6 +1783,12 @@ function ToolApprovalCard({
1513
1783
  ] })
1514
1784
  ] });
1515
1785
  }
1786
+ function contextMeterLabel(meter) {
1787
+ const pct = Math.round(meter.pct * 100);
1788
+ if (meter.state === "full") return `Context full (${pct}%) \u2014 start a new chat to continue`;
1789
+ if (meter.state === "summarizing") return `Context ${pct}% \u2014 summarizing older messages`;
1790
+ return `Context ${pct}% used`;
1791
+ }
1516
1792
  function AgentPanel({
1517
1793
  agentMode,
1518
1794
  canToggleAgentMode,
@@ -1525,12 +1801,12 @@ function AgentPanel({
1525
1801
  onClose,
1526
1802
  onToggleAgentMode
1527
1803
  }) {
1528
- const [draft, setDraft] = React11.useState("");
1529
- const [view, setView] = React11.useState("chat");
1530
- const [files, setFiles] = React11.useState([]);
1531
- const [uploadError, setUploadError] = React11.useState(null);
1532
- const [dragging, setDragging] = React11.useState(false);
1533
- const dragDepth = React11.useRef(0);
1804
+ const [draft, setDraft] = React12.useState("");
1805
+ const [view, setView] = React12.useState("chat");
1806
+ const [files, setFiles] = React12.useState([]);
1807
+ const [uploadError, setUploadError] = React12.useState(null);
1808
+ const [dragging, setDragging] = React12.useState(false);
1809
+ const dragDepth = React12.useRef(0);
1534
1810
  const agentName = config.agentName ?? chat.agentInfo?.name ?? "Assistant";
1535
1811
  const caps = {
1536
1812
  visionEnabled: chat.agentInfo?.vision_enabled,
@@ -1541,7 +1817,7 @@ function AgentPanel({
1541
1817
  "What can you help me with?",
1542
1818
  "Give me a quick summary of my data."
1543
1819
  ];
1544
- const addFiles = React11.useCallback(
1820
+ const addFiles = React12.useCallback(
1545
1821
  (incoming) => {
1546
1822
  const accepted = [];
1547
1823
  let rejection = null;
@@ -1558,10 +1834,10 @@ function AgentPanel({
1558
1834
  },
1559
1835
  [config, caps.visionEnabled, caps.visionMaxMb]
1560
1836
  );
1561
- const removeFile = React11.useCallback((index) => {
1837
+ const removeFile = React12.useCallback((index) => {
1562
1838
  setFiles((current) => current.filter((_, i) => i !== index));
1563
1839
  }, []);
1564
- const handleSubmit = React11.useCallback(
1840
+ const handleSubmit = React12.useCallback(
1565
1841
  (text) => {
1566
1842
  const staged = files;
1567
1843
  setDraft("");
@@ -1571,7 +1847,7 @@ function AgentPanel({
1571
1847
  },
1572
1848
  [chat, files]
1573
1849
  );
1574
- const onDragEnter = React11.useCallback(
1850
+ const onDragEnter = React12.useCallback(
1575
1851
  (event) => {
1576
1852
  if (!uploadEnabled || !event.dataTransfer?.types?.includes("Files")) return;
1577
1853
  dragDepth.current += 1;
@@ -1579,12 +1855,12 @@ function AgentPanel({
1579
1855
  },
1580
1856
  [uploadEnabled]
1581
1857
  );
1582
- const onDragLeave = React11.useCallback((event) => {
1858
+ const onDragLeave = React12.useCallback((event) => {
1583
1859
  if (!event.dataTransfer?.types?.includes("Files")) return;
1584
1860
  dragDepth.current = Math.max(0, dragDepth.current - 1);
1585
1861
  if (dragDepth.current === 0) setDragging(false);
1586
1862
  }, []);
1587
- const onDrop = React11.useCallback(
1863
+ const onDrop = React12.useCallback(
1588
1864
  (event) => {
1589
1865
  if (!uploadEnabled) return;
1590
1866
  event.preventDefault();
@@ -1595,7 +1871,7 @@ function AgentPanel({
1595
1871
  },
1596
1872
  [addFiles, uploadEnabled]
1597
1873
  );
1598
- const openHistory = React11.useCallback(() => {
1874
+ const openHistory = React12.useCallback(() => {
1599
1875
  setView("history");
1600
1876
  void chat.refreshConversations();
1601
1877
  }, [chat]);
@@ -1630,6 +1906,33 @@ function AgentPanel({
1630
1906
  ] })
1631
1907
  ] }),
1632
1908
  /* @__PURE__ */ jsxs(ChatHeaderActions, { children: [
1909
+ chat.contextMeter && chat.contextMeter.pct > 0 ? /* @__PURE__ */ jsxs(Tooltip, { children: [
1910
+ /* @__PURE__ */ jsx(
1911
+ TooltipTrigger,
1912
+ {
1913
+ render: /* @__PURE__ */ jsx(
1914
+ "button",
1915
+ {
1916
+ "aria-label": contextMeterLabel(chat.contextMeter),
1917
+ className: "mr-0.5 inline-flex cursor-default items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring",
1918
+ type: "button",
1919
+ children: /* @__PURE__ */ jsx(
1920
+ ContextMeter,
1921
+ {
1922
+ lockAt: chat.contextMeter.lock_at,
1923
+ pct: chat.contextMeter.pct,
1924
+ size: 18,
1925
+ state: chat.contextMeter.state,
1926
+ summarizeAt: chat.contextMeter.summarize_at,
1927
+ warnAt: chat.contextMeter.warn_at
1928
+ }
1929
+ )
1930
+ }
1931
+ )
1932
+ }
1933
+ ),
1934
+ /* @__PURE__ */ jsx(TooltipContent, { children: contextMeterLabel(chat.contextMeter) })
1935
+ ] }) : null,
1633
1936
  /* @__PURE__ */ jsxs(Tooltip, { children: [
1634
1937
  /* @__PURE__ */ jsx(
1635
1938
  TooltipTrigger,
@@ -1759,7 +2062,16 @@ function AgentPanel({
1759
2062
  const anchorKey = newMessageAnchorId ? grouped.find(
1760
2063
  (it) => it.kind !== "tool-activity" && it.message.id === newMessageAnchorId
1761
2064
  )?.key : void 0;
1762
- return grouped.map((item) => /* @__PURE__ */ jsxs(React11.Fragment, { children: [
2065
+ let liveAssistantKey = null;
2066
+ if (chat.sending) {
2067
+ for (let i = grouped.length - 1; i >= 0; i -= 1) {
2068
+ if (grouped[i].kind === "assistant-text") {
2069
+ liveAssistantKey = grouped[i].key;
2070
+ break;
2071
+ }
2072
+ }
2073
+ }
2074
+ return grouped.map((item) => /* @__PURE__ */ jsxs(React12.Fragment, { children: [
1763
2075
  item.key === anchorKey ? /* @__PURE__ */ jsx(NewMessagesDivider, {}) : null,
1764
2076
  /* @__PURE__ */ jsx(
1765
2077
  motion.div,
@@ -1773,7 +2085,7 @@ function AgentPanel({
1773
2085
  describeToolCall: config.describeToolCall,
1774
2086
  steps: item.steps
1775
2087
  }
1776
- ) }) : /* @__PURE__ */ jsx(MessageItem, { message: item.message })
2088
+ ) }) : /* @__PURE__ */ jsx(MessageItem, { animate: item.key === liveAssistantKey, message: item.message })
1777
2089
  }
1778
2090
  )
1779
2091
  ] }, item.key));
@@ -1842,6 +2154,43 @@ var OPTIMISTIC_ID_PREFIX = "optimistic-";
1842
2154
  var STREAMING_ID_PREFIX = "streaming-";
1843
2155
  var optimisticCounter = 0;
1844
2156
  var streamingCounter = 0;
2157
+ function newIdempotencyKey() {
2158
+ try {
2159
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
2160
+ } catch {
2161
+ }
2162
+ return `idem-${Date.now()}-${Math.floor(Math.random() * 1e9)}`;
2163
+ }
2164
+ function mergeMessages(current, incoming) {
2165
+ if (!incoming?.length) return current;
2166
+ const settledIds = new Set(
2167
+ current.filter((m) => !m.id.startsWith(OPTIMISTIC_ID_PREFIX)).map((m) => m.id)
2168
+ );
2169
+ const additions = incoming.filter((m) => m.id && !settledIds.has(m.id));
2170
+ if (!additions.length) return current;
2171
+ let base = current;
2172
+ const optimistic = current.filter((m) => m.id.startsWith(OPTIMISTIC_ID_PREFIX));
2173
+ const placeholder = optimistic[optimistic.length - 1];
2174
+ if (placeholder) {
2175
+ const echoIndex = additions.findIndex(
2176
+ (m) => m.role === "user" && (m.content ?? "") === (placeholder.content ?? "")
2177
+ );
2178
+ if (echoIndex !== -1) {
2179
+ base = current.filter((m) => m.id !== placeholder.id);
2180
+ additions[echoIndex] = {
2181
+ ...additions[echoIndex],
2182
+ client_key: placeholder.client_key ?? placeholder.id
2183
+ };
2184
+ }
2185
+ }
2186
+ const merged = [...base, ...additions];
2187
+ merged.sort((a, b) => {
2188
+ const sa = typeof a.seq === "number" ? a.seq : Number.MAX_SAFE_INTEGER;
2189
+ const sb = typeof b.seq === "number" ? b.seq : Number.MAX_SAFE_INTEGER;
2190
+ return sa - sb;
2191
+ });
2192
+ return merged;
2193
+ }
1845
2194
  function buildOptimisticUserMessage(text, files) {
1846
2195
  optimisticCounter += 1;
1847
2196
  const attachments = (files ?? []).map((file, index) => ({
@@ -1859,17 +2208,25 @@ function buildOptimisticUserMessage(text, files) {
1859
2208
  };
1860
2209
  }
1861
2210
  function useAgentChat(config) {
1862
- const client = React11.useMemo(() => new AgentApiClient(config), [config]);
1863
- const [agentInfo, setAgentInfo] = React11.useState(null);
1864
- const [conversationId, setConversationId] = React11.useState(null);
1865
- const [conversationEpoch, setConversationEpoch] = React11.useState(0);
1866
- const [conversations, setConversations] = React11.useState([]);
1867
- const [messages, setMessages] = React11.useState([]);
1868
- const [pendingApprovals, setPendingApprovals] = React11.useState([]);
1869
- const [sending, setSending] = React11.useState(false);
1870
- const [resolvingApprovalId, setResolvingApprovalId] = React11.useState(null);
1871
- const [error, setError] = React11.useState(null);
1872
- React11.useEffect(() => {
2211
+ const client = React12.useMemo(() => new AgentApiClient(config), [config]);
2212
+ const configRef = React12.useRef(config);
2213
+ configRef.current = config;
2214
+ const [agentInfo, setAgentInfo] = React12.useState(null);
2215
+ const [conversationId, setConversationId] = React12.useState(null);
2216
+ const [conversationEpoch, setConversationEpoch] = React12.useState(0);
2217
+ const [conversations, setConversations] = React12.useState([]);
2218
+ const [messages, setMessages] = React12.useState([]);
2219
+ const [pendingApprovals, setPendingApprovals] = React12.useState([]);
2220
+ const [sending, setSending] = React12.useState(false);
2221
+ const [resolvingApprovalId, setResolvingApprovalId] = React12.useState(null);
2222
+ const [error, setError] = React12.useState(null);
2223
+ const [currentConversation, setCurrentConversation] = React12.useState(null);
2224
+ const [contextMeter, setContextMeter] = React12.useState(null);
2225
+ const [participants, setParticipants] = React12.useState([]);
2226
+ const [myState, setMyState] = React12.useState(null);
2227
+ const [typingUsers, setTypingUsers] = React12.useState({});
2228
+ const [realtimeStatus, setRealtimeStatus] = React12.useState("idle");
2229
+ React12.useEffect(() => {
1873
2230
  let cancelled = false;
1874
2231
  client.getConfig().then((info) => {
1875
2232
  if (!cancelled) setAgentInfo(info);
@@ -1879,11 +2236,11 @@ function useAgentChat(config) {
1879
2236
  cancelled = true;
1880
2237
  };
1881
2238
  }, [client]);
1882
- const handleFailure = React11.useCallback((err) => {
2239
+ const handleFailure = React12.useCallback((err) => {
1883
2240
  const message = err instanceof AgentApiError ? err.message : "Something went wrong. Please try again.";
1884
2241
  setError(message);
1885
2242
  }, []);
1886
- const applyResponse = React11.useCallback(
2243
+ const applyResponse = React12.useCallback(
1887
2244
  (response) => {
1888
2245
  if (response.status === "error" && !response.messages?.length) {
1889
2246
  setError(response.message ?? "The assistant could not answer.");
@@ -1913,7 +2270,7 @@ function useAgentChat(config) {
1913
2270
  },
1914
2271
  []
1915
2272
  );
1916
- const handleStreamEvent = React11.useCallback(
2273
+ const handleStreamEvent = React12.useCallback(
1917
2274
  (event, ctx) => {
1918
2275
  switch (event.type) {
1919
2276
  case "conversation":
@@ -1991,29 +2348,93 @@ function useAgentChat(config) {
1991
2348
  },
1992
2349
  []
1993
2350
  );
2351
+ const applyRealtime = React12.useCallback((event) => {
2352
+ switch (event.type) {
2353
+ case "sync":
2354
+ setMessages((current) => mergeMessages(current, event.messages ?? []));
2355
+ break;
2356
+ case "message":
2357
+ setMessages((current) => mergeMessages(current, [event.message]));
2358
+ break;
2359
+ case "message_edited":
2360
+ setMessages(
2361
+ (current) => current.map(
2362
+ (message) => message.id === event.message.id ? { ...event.message, client_key: message.client_key ?? message.id } : message
2363
+ )
2364
+ );
2365
+ break;
2366
+ case "message_deleted":
2367
+ setMessages(
2368
+ (current) => current.map(
2369
+ (message) => message.id === event.message_id ? { ...message, deleted: true, content: "" } : message
2370
+ )
2371
+ );
2372
+ break;
2373
+ case "typing":
2374
+ setTypingUsers((current) => {
2375
+ const next = { ...current };
2376
+ if (event.is_typing) next[event.user_id] = event.name;
2377
+ else delete next[event.user_id];
2378
+ return next;
2379
+ });
2380
+ break;
2381
+ case "visibility_changed":
2382
+ setCurrentConversation(
2383
+ (current) => current ? { ...current, visibility: event.visibility } : current
2384
+ );
2385
+ break;
2386
+ }
2387
+ }, []);
2388
+ const realtimeEnabled = config.realtime?.enabled !== false;
2389
+ const isPublic = currentConversation?.visibility === "public";
2390
+ const baselineSeq = currentConversation?.last_seq ?? 0;
2391
+ React12.useEffect(() => {
2392
+ if (!realtimeEnabled || !conversationId || !isPublic) {
2393
+ setRealtimeStatus("idle");
2394
+ return;
2395
+ }
2396
+ const rt = new AgentRealtimeClient(
2397
+ configRef.current,
2398
+ conversationId,
2399
+ { onEvent: applyRealtime, onStatus: setRealtimeStatus },
2400
+ baselineSeq
2401
+ );
2402
+ void rt.connect();
2403
+ return () => {
2404
+ rt.close();
2405
+ };
2406
+ }, [applyRealtime, baselineSeq, conversationId, isPublic, realtimeEnabled]);
1994
2407
  const streamingEnabled = config.streaming !== false && typeof ReadableStream !== "undefined";
1995
- const send = React11.useCallback(
2408
+ const send = React12.useCallback(
1996
2409
  async (text, files) => {
1997
2410
  if (sending) return;
1998
2411
  if (!text.trim() && !(files && files.length)) return;
1999
2412
  setSending(true);
2000
2413
  setError(null);
2001
2414
  setMessages((current) => [...current, buildOptimisticUserMessage(text, files)]);
2415
+ const idempotencyKey = newIdempotencyKey();
2002
2416
  try {
2003
2417
  if (streamingEnabled) {
2004
2418
  const ctx = { streamingId: null };
2005
2419
  let started = false;
2006
2420
  try {
2007
- await client.streamMessage(text, conversationId, files, (event) => {
2008
- started = true;
2009
- handleStreamEvent(event, ctx);
2010
- });
2421
+ await client.streamMessage(
2422
+ text,
2423
+ conversationId,
2424
+ files,
2425
+ (event) => {
2426
+ started = true;
2427
+ handleStreamEvent(event, ctx);
2428
+ },
2429
+ void 0,
2430
+ { idempotencyKey }
2431
+ );
2011
2432
  return;
2012
2433
  } catch (err) {
2013
2434
  if (started) throw err;
2014
2435
  }
2015
2436
  }
2016
- const response = await client.sendMessage(text, conversationId, files);
2437
+ const response = await client.sendMessage(text, conversationId, files, { idempotencyKey });
2017
2438
  applyResponse(response);
2018
2439
  } catch (err) {
2019
2440
  handleFailure(err);
@@ -2023,14 +2444,14 @@ function useAgentChat(config) {
2023
2444
  },
2024
2445
  [applyResponse, client, conversationId, handleFailure, handleStreamEvent, sending, streamingEnabled]
2025
2446
  );
2026
- const transcribe = React11.useCallback(
2447
+ const transcribe = React12.useCallback(
2027
2448
  async (audio) => {
2028
2449
  const { text } = await client.transcribe(audio);
2029
2450
  return text;
2030
2451
  },
2031
2452
  [client]
2032
2453
  );
2033
- const resolveApproval = React11.useCallback(
2454
+ const resolveApproval = React12.useCallback(
2034
2455
  async (callId, approve) => {
2035
2456
  if (resolvingApprovalId) return;
2036
2457
  setResolvingApprovalId(callId);
@@ -2059,14 +2480,19 @@ function useAgentChat(config) {
2059
2480
  },
2060
2481
  [applyResponse, client, handleFailure, handleStreamEvent, resolvingApprovalId, streamingEnabled]
2061
2482
  );
2062
- const newChat = React11.useCallback(() => {
2483
+ const newChat = React12.useCallback(() => {
2063
2484
  setConversationId(null);
2064
2485
  setMessages([]);
2065
2486
  setPendingApprovals([]);
2066
2487
  setError(null);
2488
+ setCurrentConversation(null);
2489
+ setContextMeter(null);
2490
+ setParticipants([]);
2491
+ setMyState(null);
2492
+ setTypingUsers({});
2067
2493
  setConversationEpoch((e) => e + 1);
2068
2494
  }, []);
2069
- const refreshConversations = React11.useCallback(async () => {
2495
+ const refreshConversations = React12.useCallback(async () => {
2070
2496
  try {
2071
2497
  const { conversations: list } = await client.listConversations();
2072
2498
  setConversations(list);
@@ -2076,7 +2502,7 @@ function useAgentChat(config) {
2076
2502
  return [];
2077
2503
  }
2078
2504
  }, [client, handleFailure]);
2079
- const openConversation = React11.useCallback(
2505
+ const openConversation = React12.useCallback(
2080
2506
  async (id) => {
2081
2507
  setError(null);
2082
2508
  try {
@@ -2084,6 +2510,23 @@ function useAgentChat(config) {
2084
2510
  setConversationId(id);
2085
2511
  setMessages(payload.messages ?? []);
2086
2512
  setPendingApprovals(payload.pending_approvals ?? []);
2513
+ const convo = payload.conversation;
2514
+ setCurrentConversation(
2515
+ convo ? {
2516
+ id: convo.id,
2517
+ title: convo.title,
2518
+ visibility: convo.visibility,
2519
+ scope_type: convo.scope_type,
2520
+ last_seq: convo.last_seq,
2521
+ is_creator: convo.is_creator,
2522
+ can_toggle_visibility: convo.can_toggle_visibility,
2523
+ can_go_private: convo.can_go_private
2524
+ } : null
2525
+ );
2526
+ setContextMeter(payload.context_meter ?? null);
2527
+ setParticipants(payload.participants ?? []);
2528
+ setMyState(payload.my_state ?? null);
2529
+ setTypingUsers({});
2087
2530
  setConversationEpoch((e) => e + 1);
2088
2531
  } catch (err) {
2089
2532
  handleFailure(err);
@@ -2091,7 +2534,7 @@ function useAgentChat(config) {
2091
2534
  },
2092
2535
  [client, handleFailure]
2093
2536
  );
2094
- const deleteConversation = React11.useCallback(
2537
+ const deleteConversation = React12.useCallback(
2095
2538
  async (id) => {
2096
2539
  try {
2097
2540
  await client.deleteConversation(id);
@@ -2103,25 +2546,31 @@ function useAgentChat(config) {
2103
2546
  },
2104
2547
  [client, conversationId, handleFailure, newChat]
2105
2548
  );
2106
- const clearError = React11.useCallback(() => setError(null), []);
2549
+ const clearError = React12.useCallback(() => setError(null), []);
2107
2550
  return {
2108
2551
  agentInfo,
2109
2552
  clearError,
2553
+ contextMeter,
2110
2554
  conversationId,
2111
2555
  conversationEpoch,
2112
2556
  conversations,
2557
+ currentConversation,
2113
2558
  deleteConversation,
2114
2559
  error,
2115
2560
  messages,
2561
+ myState,
2116
2562
  newChat,
2117
2563
  openConversation,
2564
+ participants,
2118
2565
  pendingApprovals,
2566
+ realtimeStatus,
2119
2567
  refreshConversations,
2120
2568
  resolveApproval,
2121
2569
  resolvingApprovalId,
2122
2570
  send,
2123
2571
  sending,
2124
- transcribe
2572
+ transcribe,
2573
+ typingUsers
2125
2574
  };
2126
2575
  }
2127
2576
  var STORAGE_PREFIX = "cc-agent-always-approve:";
@@ -2140,11 +2589,11 @@ function readStored(clientId) {
2140
2589
  }
2141
2590
  }
2142
2591
  function useAlwaysApprove(clientId) {
2143
- const [approved, setApproved] = React11.useState(() => /* @__PURE__ */ new Set());
2144
- React11.useEffect(() => {
2592
+ const [approved, setApproved] = React12.useState(() => /* @__PURE__ */ new Set());
2593
+ React12.useEffect(() => {
2145
2594
  setApproved(readStored(clientId));
2146
2595
  }, [clientId]);
2147
- const persist = React11.useCallback(
2596
+ const persist = React12.useCallback(
2148
2597
  (next) => {
2149
2598
  setApproved(next);
2150
2599
  if (typeof window === "undefined") return;
@@ -2155,8 +2604,8 @@ function useAlwaysApprove(clientId) {
2155
2604
  },
2156
2605
  [clientId]
2157
2606
  );
2158
- const isAlwaysApproved = React11.useCallback((toolName) => approved.has(toolName), [approved]);
2159
- const setAlwaysApprove = React11.useCallback(
2607
+ const isAlwaysApproved = React12.useCallback((toolName) => approved.has(toolName), [approved]);
2608
+ const setAlwaysApprove = React12.useCallback(
2160
2609
  (toolName) => {
2161
2610
  if (approved.has(toolName)) return;
2162
2611
  const next = new Set(approved);
@@ -2165,7 +2614,7 @@ function useAlwaysApprove(clientId) {
2165
2614
  },
2166
2615
  [approved, persist]
2167
2616
  );
2168
- const clearAlwaysApprove = React11.useCallback(
2617
+ const clearAlwaysApprove = React12.useCallback(
2169
2618
  (toolName) => {
2170
2619
  if (!approved.has(toolName)) return;
2171
2620
  const next = new Set(approved);
@@ -2191,10 +2640,10 @@ function AgentWidget({
2191
2640
  const isControlled = controlledOpen !== void 0;
2192
2641
  const isDocked = presentation === "docked";
2193
2642
  const storageKey2 = `cc-agent-open:${config.clientId}`;
2194
- const [uncontrolledOpen, setUncontrolledOpen] = React11.useState(false);
2643
+ const [uncontrolledOpen, setUncontrolledOpen] = React12.useState(false);
2195
2644
  const open = controlledOpen ?? uncontrolledOpen;
2196
- const [bootstrapDone, setBootstrapDone] = React11.useState(isControlled);
2197
- const persist = React11.useCallback(
2645
+ const [bootstrapDone, setBootstrapDone] = React12.useState(isControlled);
2646
+ const persist = React12.useCallback(
2198
2647
  (next) => {
2199
2648
  if (isControlled || !behavior.persistOpenState) return;
2200
2649
  try {
@@ -2204,7 +2653,7 @@ function AgentWidget({
2204
2653
  },
2205
2654
  [behavior.persistOpenState, isControlled, storageKey2]
2206
2655
  );
2207
- const setOpen = React11.useCallback(
2656
+ const setOpen = React12.useCallback(
2208
2657
  (next) => {
2209
2658
  setUncontrolledOpen(next);
2210
2659
  persist(next);
@@ -2212,14 +2661,14 @@ function AgentWidget({
2212
2661
  },
2213
2662
  [onOpenChange, persist]
2214
2663
  );
2215
- const [agentMode, setAgentMode] = React11.useState(false);
2664
+ const [agentMode, setAgentMode] = React12.useState(false);
2216
2665
  const isDesktop = useMediaQuery("(min-width: 640px)", { defaultSSRValue: true, ssr: true });
2217
2666
  const fullscreen = !isDesktop || agentMode;
2218
2667
  const chat = useAgentChat(config);
2219
2668
  const alwaysApprove = useAlwaysApprove(config.clientId);
2220
2669
  const { messages, pendingApprovals, resolveApproval, resolvingApprovalId, sending } = chat;
2221
- const bootRef = React11.useRef(false);
2222
- React11.useEffect(() => {
2670
+ const bootRef = React12.useRef(false);
2671
+ React12.useEffect(() => {
2223
2672
  if (bootRef.current) return;
2224
2673
  if (isControlled && !isDocked) return;
2225
2674
  bootRef.current = true;
@@ -2251,15 +2700,15 @@ function AgentWidget({
2251
2700
  cancelled = true;
2252
2701
  };
2253
2702
  }, []);
2254
- const seenCountRef = React11.useRef(0);
2255
- const [baselined, setBaselined] = React11.useState(false);
2256
- const [anchorId, setAnchorId] = React11.useState(null);
2257
- React11.useEffect(() => {
2703
+ const seenCountRef = React12.useRef(0);
2704
+ const [baselined, setBaselined] = React12.useState(false);
2705
+ const [anchorId, setAnchorId] = React12.useState(null);
2706
+ React12.useEffect(() => {
2258
2707
  if (baselined || !bootstrapDone) return;
2259
2708
  seenCountRef.current = messages.length;
2260
2709
  setBaselined(true);
2261
2710
  }, [baselined, bootstrapDone, messages.length]);
2262
- React11.useEffect(() => {
2711
+ React12.useEffect(() => {
2263
2712
  if (open) {
2264
2713
  if (messages.length > seenCountRef.current) {
2265
2714
  const firstUnread = messages[seenCountRef.current];
@@ -2269,7 +2718,7 @@ function AgentWidget({
2269
2718
  }
2270
2719
  }, [open, messages]);
2271
2720
  const unreadCount = open || !baselined ? 0 : messages.slice(seenCountRef.current).filter(isUnreadable).length;
2272
- const workLabel = React11.useMemo(() => {
2721
+ const workLabel = React12.useMemo(() => {
2273
2722
  if (!sending) return null;
2274
2723
  const last = messages[messages.length - 1];
2275
2724
  if (last?.role === "tool") return "Calling tools\u2026";
@@ -2278,14 +2727,14 @@ function AgentWidget({
2278
2727
  }, [sending, messages]);
2279
2728
  const approvalWaiting = pendingApprovals.length > 0;
2280
2729
  const { isAlwaysApproved } = alwaysApprove;
2281
- React11.useEffect(() => {
2730
+ React12.useEffect(() => {
2282
2731
  if (resolvingApprovalId) return;
2283
2732
  const target = pendingApprovals.find((approval) => isAlwaysApproved(approval.tool_name));
2284
2733
  if (target) void resolveApproval(target.id, true);
2285
2734
  }, [pendingApprovals, resolvingApprovalId, isAlwaysApproved, resolveApproval]);
2286
- const firedWriteIds = React11.useRef(/* @__PURE__ */ new Set());
2735
+ const firedWriteIds = React12.useRef(/* @__PURE__ */ new Set());
2287
2736
  const { onWriteSuccess } = config;
2288
- React11.useEffect(() => {
2737
+ React12.useEffect(() => {
2289
2738
  if (!onWriteSuccess) return;
2290
2739
  for (const message of messages) {
2291
2740
  if (message.role !== "tool" || !message.is_write || message.tool_status !== "success") continue;
@@ -2295,9 +2744,9 @@ function AgentWidget({
2295
2744
  onWriteSuccess({ arguments: args, toolName: message.tool_name ?? "" });
2296
2745
  }
2297
2746
  }, [messages, onWriteSuccess]);
2298
- const lastNavigatedMessageId = React11.useRef(null);
2299
- const navEpochRef = React11.useRef(-1);
2300
- React11.useEffect(() => {
2747
+ const lastNavigatedMessageId = React12.useRef(null);
2748
+ const navEpochRef = React12.useRef(-1);
2749
+ React12.useEffect(() => {
2301
2750
  if (!open || fullscreen || !config.navigate || !config.pages?.length) return;
2302
2751
  const toolMessages = messages.filter((message) => message.role === "tool");
2303
2752
  const latest = toolMessages[toolMessages.length - 1];
@@ -2316,7 +2765,7 @@ function AgentWidget({
2316
2765
  const scrollTo = typeof mapping.scrollTo === "function" ? mapping.scrollTo(args) : mapping.scrollTo;
2317
2766
  config.navigate(path, { scrollTo: scrollTo ?? null });
2318
2767
  }, [chat.conversationEpoch, config, fullscreen, messages, open]);
2319
- const closePanel = React11.useCallback(() => {
2768
+ const closePanel = React12.useCallback(() => {
2320
2769
  setOpen(false);
2321
2770
  setAgentMode(false);
2322
2771
  }, [setOpen]);
@@ -2489,6 +2938,6 @@ function defineAgentConfig(config) {
2489
2938
  return config;
2490
2939
  }
2491
2940
 
2492
- export { AgentApiClient, AgentApiError, AgentComposer, AgentImageLightbox, AgentPanel, AgentVoiceRecorder, AgentWidget, ChatScroller, CopyButton, DEFAULT_ACCEPT, DocumentCard, IMAGE_ACCEPT, MessageItem, TaskChecklist, ThinkingIndicator, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, attachmentsEnabled, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, isImageFile, resolveToolPresentation, textExtensions, useAgentChat, useAlwaysApprove, validateFile };
2941
+ export { AgentApiClient, AgentApiError, AgentComposer, AgentImageLightbox, AgentPanel, AgentRealtimeClient, AgentVoiceRecorder, AgentWidget, ChatScroller, CopyButton, DEFAULT_ACCEPT, DocumentCard, IMAGE_ACCEPT, MessageItem, TaskChecklist, ThinkingIndicator, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, attachmentsEnabled, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, isImageFile, resolveToolPresentation, textExtensions, useAgentChat, useAlwaysApprove, useSmoothText, validateFile };
2493
2942
  //# sourceMappingURL=index.js.map
2494
2943
  //# sourceMappingURL=index.js.map