@cntyclub/agent-react 0.17.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,4 +1,4 @@
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
3
  import * as React12 from 'react';
4
4
  import { AnimatePresence, motion } from 'motion/react';
@@ -6,6 +6,22 @@ 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
@@ -1558,6 +1783,12 @@ function ToolApprovalCard({
1558
1783
  ] })
1559
1784
  ] });
1560
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
+ }
1561
1792
  function AgentPanel({
1562
1793
  agentMode,
1563
1794
  canToggleAgentMode,
@@ -1675,6 +1906,33 @@ function AgentPanel({
1675
1906
  ] })
1676
1907
  ] }),
1677
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,
1678
1936
  /* @__PURE__ */ jsxs(Tooltip, { children: [
1679
1937
  /* @__PURE__ */ jsx(
1680
1938
  TooltipTrigger,
@@ -1896,6 +2154,43 @@ var OPTIMISTIC_ID_PREFIX = "optimistic-";
1896
2154
  var STREAMING_ID_PREFIX = "streaming-";
1897
2155
  var optimisticCounter = 0;
1898
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
+ }
1899
2194
  function buildOptimisticUserMessage(text, files) {
1900
2195
  optimisticCounter += 1;
1901
2196
  const attachments = (files ?? []).map((file, index) => ({
@@ -1914,6 +2209,8 @@ function buildOptimisticUserMessage(text, files) {
1914
2209
  }
1915
2210
  function useAgentChat(config) {
1916
2211
  const client = React12.useMemo(() => new AgentApiClient(config), [config]);
2212
+ const configRef = React12.useRef(config);
2213
+ configRef.current = config;
1917
2214
  const [agentInfo, setAgentInfo] = React12.useState(null);
1918
2215
  const [conversationId, setConversationId] = React12.useState(null);
1919
2216
  const [conversationEpoch, setConversationEpoch] = React12.useState(0);
@@ -1923,6 +2220,12 @@ function useAgentChat(config) {
1923
2220
  const [sending, setSending] = React12.useState(false);
1924
2221
  const [resolvingApprovalId, setResolvingApprovalId] = React12.useState(null);
1925
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");
1926
2229
  React12.useEffect(() => {
1927
2230
  let cancelled = false;
1928
2231
  client.getConfig().then((info) => {
@@ -2045,6 +2348,62 @@ function useAgentChat(config) {
2045
2348
  },
2046
2349
  []
2047
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]);
2048
2407
  const streamingEnabled = config.streaming !== false && typeof ReadableStream !== "undefined";
2049
2408
  const send = React12.useCallback(
2050
2409
  async (text, files) => {
@@ -2053,21 +2412,29 @@ function useAgentChat(config) {
2053
2412
  setSending(true);
2054
2413
  setError(null);
2055
2414
  setMessages((current) => [...current, buildOptimisticUserMessage(text, files)]);
2415
+ const idempotencyKey = newIdempotencyKey();
2056
2416
  try {
2057
2417
  if (streamingEnabled) {
2058
2418
  const ctx = { streamingId: null };
2059
2419
  let started = false;
2060
2420
  try {
2061
- await client.streamMessage(text, conversationId, files, (event) => {
2062
- started = true;
2063
- handleStreamEvent(event, ctx);
2064
- });
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
+ );
2065
2432
  return;
2066
2433
  } catch (err) {
2067
2434
  if (started) throw err;
2068
2435
  }
2069
2436
  }
2070
- const response = await client.sendMessage(text, conversationId, files);
2437
+ const response = await client.sendMessage(text, conversationId, files, { idempotencyKey });
2071
2438
  applyResponse(response);
2072
2439
  } catch (err) {
2073
2440
  handleFailure(err);
@@ -2118,6 +2485,11 @@ function useAgentChat(config) {
2118
2485
  setMessages([]);
2119
2486
  setPendingApprovals([]);
2120
2487
  setError(null);
2488
+ setCurrentConversation(null);
2489
+ setContextMeter(null);
2490
+ setParticipants([]);
2491
+ setMyState(null);
2492
+ setTypingUsers({});
2121
2493
  setConversationEpoch((e) => e + 1);
2122
2494
  }, []);
2123
2495
  const refreshConversations = React12.useCallback(async () => {
@@ -2138,6 +2510,23 @@ function useAgentChat(config) {
2138
2510
  setConversationId(id);
2139
2511
  setMessages(payload.messages ?? []);
2140
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({});
2141
2530
  setConversationEpoch((e) => e + 1);
2142
2531
  } catch (err) {
2143
2532
  handleFailure(err);
@@ -2161,21 +2550,27 @@ function useAgentChat(config) {
2161
2550
  return {
2162
2551
  agentInfo,
2163
2552
  clearError,
2553
+ contextMeter,
2164
2554
  conversationId,
2165
2555
  conversationEpoch,
2166
2556
  conversations,
2557
+ currentConversation,
2167
2558
  deleteConversation,
2168
2559
  error,
2169
2560
  messages,
2561
+ myState,
2170
2562
  newChat,
2171
2563
  openConversation,
2564
+ participants,
2172
2565
  pendingApprovals,
2566
+ realtimeStatus,
2173
2567
  refreshConversations,
2174
2568
  resolveApproval,
2175
2569
  resolvingApprovalId,
2176
2570
  send,
2177
2571
  sending,
2178
- transcribe
2572
+ transcribe,
2573
+ typingUsers
2179
2574
  };
2180
2575
  }
2181
2576
  var STORAGE_PREFIX = "cc-agent-always-approve:";
@@ -2543,6 +2938,6 @@ function defineAgentConfig(config) {
2543
2938
  return config;
2544
2939
  }
2545
2940
 
2546
- 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, useSmoothText, 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 };
2547
2942
  //# sourceMappingURL=index.js.map
2548
2943
  //# sourceMappingURL=index.js.map