@meshagent/meshagent-tailwind 0.38.4 → 0.39.1

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.
@@ -22,16 +22,52 @@ export interface UseChatThreadResult {
22
22
  localParticipantName: string;
23
23
  cancelRequest?: () => void;
24
24
  }
25
- export interface ThreadStatus {
26
- text: string | null;
27
- mode: string | null;
28
- startedAt: Date | null;
29
- supportsAgentMessages: boolean;
25
+ export declare class PendingAgentMessage {
26
+ readonly messageId: string;
27
+ readonly messageType: string;
28
+ readonly threadPath: string;
29
+ readonly text: string;
30
+ readonly attachments: string[];
31
+ readonly senderName?: string;
32
+ readonly awaitingAcceptance: boolean;
33
+ readonly awaitingOnline: boolean;
34
+ constructor({ messageId, messageType, threadPath, text, attachments, senderName, awaitingAcceptance, awaitingOnline, }: {
35
+ messageId: string;
36
+ messageType: string;
37
+ threadPath: string;
38
+ text: string;
39
+ attachments: string[];
40
+ senderName?: string;
41
+ awaitingAcceptance?: boolean;
42
+ awaitingOnline?: boolean;
43
+ });
44
+ static fromQueueJson(json: Record<string, unknown>): PendingAgentMessage;
30
45
  }
46
+ export declare class ChatThreadStatusState {
47
+ readonly text?: string;
48
+ readonly startedAt?: Date;
49
+ readonly mode?: string;
50
+ readonly turnId?: string;
51
+ readonly pendingMessages: PendingAgentMessage[];
52
+ readonly pendingItemId?: string;
53
+ readonly supportsAgentMessages: boolean;
54
+ constructor({ text, startedAt, mode, turnId, pendingMessages, pendingItemId, supportsAgentMessages, }: {
55
+ text?: string;
56
+ startedAt?: Date;
57
+ mode?: string;
58
+ turnId?: string;
59
+ pendingMessages?: PendingAgentMessage[];
60
+ pendingItemId?: string;
61
+ supportsAgentMessages?: boolean;
62
+ });
63
+ get hasStatus(): boolean;
64
+ }
65
+ export type ThreadStatus = ChatThreadStatusState;
31
66
  export interface UseThreadStatusProps {
32
67
  room: RoomClient;
33
68
  path: string;
34
69
  agentName?: string;
70
+ previous?: ChatThreadStatusState;
35
71
  }
36
72
  export declare function formatThreadStatusText(text: string, startedAt?: Date | null): string;
37
73
  export declare function useChatThread({ room, path, participants, participantNames, initialMessage, includeLocalParticipant, agentName, }: UseChatThreadProps): UseChatThreadResult;
@@ -4,6 +4,103 @@ import {
4
4
  } from "@meshagent/meshagent";
5
5
  import { subscribe, useDocumentChanged, useRoomParticipants } from "@meshagent/meshagent-react";
6
6
  import { MeshagentFileUpload, fileToAsyncIterable } from "./file-attachment";
7
+ const agentTurnSteerType = "meshagent.agent.turn.steer";
8
+ class PendingAgentMessage {
9
+ messageId;
10
+ messageType;
11
+ threadPath;
12
+ text;
13
+ attachments;
14
+ senderName;
15
+ awaitingAcceptance;
16
+ awaitingOnline;
17
+ constructor({
18
+ messageId,
19
+ messageType,
20
+ threadPath,
21
+ text,
22
+ attachments,
23
+ senderName,
24
+ awaitingAcceptance = false,
25
+ awaitingOnline = false
26
+ }) {
27
+ this.messageId = messageId;
28
+ this.messageType = messageType;
29
+ this.threadPath = threadPath;
30
+ this.text = text;
31
+ this.attachments = attachments;
32
+ this.senderName = senderName;
33
+ this.awaitingAcceptance = awaitingAcceptance;
34
+ this.awaitingOnline = awaitingOnline;
35
+ }
36
+ static fromQueueJson(json) {
37
+ const content = json["content"];
38
+ const textParts = [];
39
+ const attachments = [];
40
+ if (Array.isArray(content)) {
41
+ for (const item of content) {
42
+ if (item == null || typeof item !== "object") {
43
+ continue;
44
+ }
45
+ const obj = item;
46
+ const type = obj["type"];
47
+ if (type === "text") {
48
+ const text = obj["text"];
49
+ if (typeof text === "string" && text.trim().length > 0) {
50
+ textParts.push(text);
51
+ }
52
+ } else if (type === "file") {
53
+ const url = obj["url"];
54
+ if (typeof url === "string" && url.trim().length > 0) {
55
+ attachments.push(url);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ const senderName = json["sender_name"];
61
+ const messageType = json["message_type"];
62
+ const messageId = json["message_id"];
63
+ const threadPath = json["thread_id"];
64
+ return new PendingAgentMessage({
65
+ messageId: typeof messageId === "string" ? messageId : crypto.randomUUID(),
66
+ messageType: typeof messageType === "string" ? messageType : agentTurnSteerType,
67
+ threadPath: typeof threadPath === "string" ? threadPath : "",
68
+ text: textParts.join("\n\n"),
69
+ attachments,
70
+ senderName: typeof senderName === "string" && senderName.trim().length > 0 ? senderName.trim() : void 0,
71
+ awaitingOnline: false
72
+ });
73
+ }
74
+ }
75
+ class ChatThreadStatusState {
76
+ text;
77
+ startedAt;
78
+ mode;
79
+ turnId;
80
+ pendingMessages;
81
+ pendingItemId;
82
+ supportsAgentMessages;
83
+ constructor({
84
+ text,
85
+ startedAt,
86
+ mode,
87
+ turnId,
88
+ pendingMessages = [],
89
+ pendingItemId,
90
+ supportsAgentMessages: supportsAgentMessages2 = false
91
+ }) {
92
+ this.text = text;
93
+ this.startedAt = startedAt;
94
+ this.mode = mode;
95
+ this.turnId = turnId;
96
+ this.pendingMessages = pendingMessages;
97
+ this.pendingItemId = pendingItemId;
98
+ this.supportsAgentMessages = supportsAgentMessages2;
99
+ }
100
+ get hasStatus() {
101
+ return this.text != null && this.text.trim().length > 0;
102
+ }
103
+ }
7
104
  function getParticipantName(participant) {
8
105
  const name = participant.getAttribute("name");
9
106
  return typeof name === "string" ? name.trim() : "";
@@ -86,73 +183,161 @@ function threadStatusAttributeCandidates(path, prefix) {
86
183
  }
87
184
  return [`${prefix}.${path}`, `${prefix}./${path}`];
88
185
  }
89
- function resolveThreadStatus({ room, path, agentName }) {
90
- const normalizedAgentName = agentName?.trim();
91
- const remoteParticipants = room.messaging.remoteParticipants;
92
- const candidates = normalizedAgentName && normalizedAgentName !== "" ? remoteParticipants.filter((participant) => getParticipantName(participant) === normalizedAgentName) : remoteParticipants.filter((participant) => participant.role === "agent" || supportsAgentMessages(participant));
93
- const textKeys = threadStatusAttributeCandidates(path, "thread.status.text");
94
- const legacyKeys = threadStatusAttributeCandidates(path, "thread.status");
95
- const modeKeys = threadStatusAttributeCandidates(path, "thread.status.mode");
96
- const startedAtKeys = threadStatusAttributeCandidates(path, "thread.status.started_at");
97
- let text = null;
98
- let mode = null;
99
- let startedAt = null;
100
- let hasAgentMessageSupport = false;
186
+ function parsePendingMessagesStatus(participant, path) {
187
+ for (const key of threadStatusAttributeCandidates(
188
+ path,
189
+ "thread.status.pending_messages"
190
+ )) {
191
+ const value = participant.getAttribute(key);
192
+ if (typeof value !== "string" || value.trim().length === 0) {
193
+ continue;
194
+ }
195
+ try {
196
+ const decoded = JSON.parse(value.trim());
197
+ if (decoded != null && typeof decoded === "object" && !Array.isArray(decoded)) {
198
+ return decoded;
199
+ }
200
+ } catch (_) {
201
+ }
202
+ }
203
+ return void 0;
204
+ }
205
+ function resolveThreadStatus({
206
+ room,
207
+ path,
208
+ agentName,
209
+ previous
210
+ }) {
211
+ const keyCandidates = threadStatusAttributeCandidates(path, "thread.status");
212
+ const textKeyCandidates = threadStatusAttributeCandidates(path, "thread.status.text");
213
+ const modeKeyCandidates = threadStatusAttributeCandidates(path, "thread.status.mode");
214
+ const startedAtKeyCandidates = threadStatusAttributeCandidates(path, "thread.status.started_at");
215
+ const pendingItemIdKeyCandidates = threadStatusAttributeCandidates(path, "thread.status.pending_item_id");
216
+ const candidates = agentName != null ? room.messaging.remoteParticipants.filter(
217
+ (participant) => participant.getAttribute("name") === agentName
218
+ ) : room.messaging.remoteParticipants.filter(
219
+ (participant) => participant.role === "agent" || supportsAgentMessages(participant)
220
+ );
221
+ let nextStatus;
222
+ let nextMode;
223
+ let nextStartedAt;
224
+ let nextTurnId;
225
+ let nextPendingMessages = [];
226
+ let nextPendingItemId;
227
+ let nextSupportsAgentMessages = false;
101
228
  for (const participant of candidates) {
102
- hasAgentMessageSupport = hasAgentMessageSupport || supportsAgentMessages(participant);
103
- if (text === null) {
104
- for (const key of [...textKeys, ...legacyKeys]) {
229
+ if (supportsAgentMessages(participant)) {
230
+ nextSupportsAgentMessages = true;
231
+ }
232
+ if (nextStatus == null) {
233
+ for (const key of textKeyCandidates) {
105
234
  const value = participant.getAttribute(key);
106
- if (typeof value === "string" && value.trim() !== "") {
107
- text = value.trim();
235
+ if (typeof value === "string" && value.trim().length > 0) {
236
+ nextStatus = value.trim();
108
237
  break;
109
238
  }
110
239
  }
111
240
  }
112
- if (mode === null) {
113
- for (const key of modeKeys) {
241
+ if (nextStatus == null) {
242
+ for (const key of keyCandidates) {
114
243
  const value = participant.getAttribute(key);
115
- if (typeof value !== "string") {
116
- continue;
117
- }
118
- const normalized = value.trim().toLowerCase();
119
- if (normalized === "busy" || normalized === "steerable") {
120
- mode = normalized;
244
+ if (typeof value === "string" && value.trim().length > 0) {
245
+ nextStatus = value.trim();
121
246
  break;
122
247
  }
123
248
  }
124
249
  }
125
- if (startedAt === null) {
126
- for (const key of startedAtKeys) {
250
+ const pendingStatus = parsePendingMessagesStatus(participant, path);
251
+ if (pendingStatus != null && typeof pendingStatus === "object") {
252
+ if (nextTurnId == null) {
253
+ const turnId = pendingStatus["turn_id"];
254
+ if (typeof turnId === "string" && turnId.trim().length > 0) {
255
+ nextTurnId = turnId.trim();
256
+ }
257
+ }
258
+ if (nextPendingMessages.length === 0) {
259
+ const messages = pendingStatus["messages"];
260
+ if (Array.isArray(messages)) {
261
+ nextPendingMessages = messages.flatMap((item) => {
262
+ if (item != null && typeof item === "object") {
263
+ return [
264
+ PendingAgentMessage.fromQueueJson(
265
+ item
266
+ )
267
+ ];
268
+ }
269
+ return [];
270
+ });
271
+ }
272
+ }
273
+ }
274
+ if (nextMode == null) {
275
+ for (const key of modeKeyCandidates) {
276
+ const value = participant.getAttribute(key);
277
+ if (typeof value === "string") {
278
+ const normalized = value.trim().toLowerCase();
279
+ if (normalized === "busy" || normalized === "steerable") {
280
+ nextMode = normalized;
281
+ break;
282
+ }
283
+ }
284
+ }
285
+ }
286
+ if (nextStartedAt == null) {
287
+ for (const key of startedAtKeyCandidates) {
127
288
  const value = participant.getAttribute(key);
128
- if (typeof value !== "string" || value.trim() === "") {
289
+ if (typeof value !== "string") {
129
290
  continue;
130
291
  }
131
- const parsed = new Date(value);
292
+ const normalized = value.trim();
293
+ if (normalized.length === 0) {
294
+ continue;
295
+ }
296
+ const parsed = new Date(normalized);
132
297
  if (!Number.isNaN(parsed.getTime())) {
133
- startedAt = parsed;
298
+ nextStartedAt = parsed;
299
+ break;
300
+ }
301
+ }
302
+ }
303
+ if (nextPendingItemId == null) {
304
+ for (const key of pendingItemIdKeyCandidates) {
305
+ const value = participant.getAttribute(key);
306
+ if (typeof value === "string" && value.trim().length > 0) {
307
+ nextPendingItemId = value.trim();
134
308
  break;
135
309
  }
136
310
  }
137
311
  }
312
+ if (nextStatus != null && nextMode != null && nextStartedAt != null && nextTurnId != null && nextPendingItemId != null) {
313
+ break;
314
+ }
138
315
  }
139
- if (text === null) {
140
- return {
141
- text: null,
142
- mode: null,
143
- startedAt: null,
144
- supportsAgentMessages: hasAgentMessageSupport
145
- };
316
+ if (nextStatus == null) {
317
+ return new ChatThreadStatusState({
318
+ supportsAgentMessages: nextSupportsAgentMessages
319
+ });
146
320
  }
147
- return {
148
- text,
149
- mode: mode ?? "busy",
150
- startedAt,
151
- supportsAgentMessages: hasAgentMessageSupport
152
- };
321
+ nextMode ??= "busy";
322
+ nextStartedAt ??= previous?.hasStatus === true ? previous.startedAt : /* @__PURE__ */ new Date();
323
+ return new ChatThreadStatusState({
324
+ text: nextStatus,
325
+ startedAt: nextStartedAt,
326
+ mode: nextMode,
327
+ turnId: nextTurnId,
328
+ pendingMessages: nextPendingMessages,
329
+ pendingItemId: nextPendingItemId,
330
+ supportsAgentMessages: nextSupportsAgentMessages
331
+ });
332
+ }
333
+ function pendingMessagesEqual(left, right) {
334
+ return left.length === right.length && left.every((message, index) => {
335
+ const other = right[index];
336
+ return message.messageId === other.messageId && message.messageType === other.messageType && message.text === other.text && stringArraysEqual(message.attachments, other.attachments) && message.senderName === other.senderName;
337
+ });
153
338
  }
154
339
  function threadStatusEquals(left, right) {
155
- return left.text === right.text && left.mode === right.mode && left.startedAt?.getTime() === right.startedAt?.getTime() && left.supportsAgentMessages === right.supportsAgentMessages;
340
+ return left.text === right.text && left.mode === right.mode && left.startedAt?.getTime() === right.startedAt?.getTime() && left.turnId === right.turnId && left.pendingItemId === right.pendingItemId && pendingMessagesEqual(left.pendingMessages, right.pendingMessages) && left.supportsAgentMessages === right.supportsAgentMessages;
156
341
  }
157
342
  function formatThreadStatusText(text, startedAt) {
158
343
  if (!(startedAt instanceof Date) || Number.isNaN(startedAt.getTime())) {
@@ -337,8 +522,15 @@ function useThreadStatus({ room, path, agentName }) {
337
522
  const [status, setStatus] = useState(() => resolveThreadStatus({ room, path, agentName }));
338
523
  useEffect(() => {
339
524
  const updateStatus = () => {
340
- const nextStatus = resolveThreadStatus({ room, path, agentName });
341
- setStatus((currentStatus) => threadStatusEquals(currentStatus, nextStatus) ? currentStatus : nextStatus);
525
+ setStatus((currentStatus) => {
526
+ const nextStatus = resolveThreadStatus({
527
+ room,
528
+ path,
529
+ agentName,
530
+ previous: currentStatus
531
+ });
532
+ return threadStatusEquals(currentStatus, nextStatus) ? currentStatus : nextStatus;
533
+ });
342
534
  };
343
535
  const roomSubscription = subscribe(room.listen(), {
344
536
  next: (event) => {
@@ -349,23 +541,22 @@ function useThreadStatus({ room, path, agentName }) {
349
541
  updateStatus();
350
542
  }
351
543
  });
352
- const handleParticipantsChanged = () => {
353
- updateStatus();
354
- };
355
- room.messaging.on("participant_added", handleParticipantsChanged);
356
- room.messaging.on("participant_removed", handleParticipantsChanged);
357
- room.messaging.on("messaging_enabled", handleParticipantsChanged);
544
+ room.messaging.on("participant_added", updateStatus);
545
+ room.messaging.on("participant_removed", updateStatus);
546
+ room.messaging.on("messaging_enabled", updateStatus);
358
547
  updateStatus();
359
548
  return () => {
360
549
  roomSubscription.unsubscribe();
361
- room.messaging.off("participant_added", handleParticipantsChanged);
362
- room.messaging.off("participant_removed", handleParticipantsChanged);
363
- room.messaging.off("messaging_enabled", handleParticipantsChanged);
550
+ room.messaging.off("participant_added", updateStatus);
551
+ room.messaging.off("participant_removed", updateStatus);
552
+ room.messaging.off("messaging_enabled", updateStatus);
364
553
  };
365
554
  }, [agentName, path, room]);
366
555
  return status;
367
556
  }
368
557
  export {
558
+ ChatThreadStatusState,
559
+ PendingAgentMessage,
369
560
  formatThreadStatusText,
370
561
  useChatThread,
371
562
  useThreadStatus