@meshagent/meshagent-tailwind 0.41.4 → 0.41.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +33 -0
  3. package/dist/cjs/chat/chat-thread.js +15 -10
  4. package/dist/cjs/chat/dataset-chat-thread.d.ts +4 -1
  5. package/dist/cjs/chat/dataset-chat-thread.js +130 -157
  6. package/dist/cjs/chat/multi-thread-view.d.ts +4 -1
  7. package/dist/cjs/chat/multi-thread-view.js +4 -0
  8. package/dist/cjs/chat/new-chat-thread.d.ts +4 -1
  9. package/dist/cjs/chat/new-chat-thread.js +43 -87
  10. package/dist/cjs/file-preview/file-preview.d.ts +6 -0
  11. package/dist/cjs/file-preview/file-preview.js +220 -0
  12. package/dist/cjs/meetings/camera-grid.d.ts +46 -0
  13. package/dist/cjs/meetings/camera-grid.js +435 -0
  14. package/dist/cjs/meetings/controls.d.ts +4 -2
  15. package/dist/cjs/meetings/controls.js +9 -3
  16. package/dist/cjs/meetings/lobby.d.ts +17 -0
  17. package/dist/cjs/meetings/lobby.js +595 -0
  18. package/dist/cjs/meetings/meeting-scope.d.ts +7 -6
  19. package/dist/cjs/meetings/meeting-scope.js +64 -15
  20. package/dist/cjs/meetings/meeting-view.d.ts +6 -0
  21. package/dist/cjs/meetings/meeting-view.js +635 -0
  22. package/dist/cjs/meetings/meetings.d.ts +3 -0
  23. package/dist/cjs/meetings/meetings.js +3 -0
  24. package/dist/cjs/meetings/wake-lock.js +2 -2
  25. package/dist/esm/chat/chat-thread.js +15 -10
  26. package/dist/esm/chat/dataset-chat-thread.d.ts +4 -1
  27. package/dist/esm/chat/dataset-chat-thread.js +129 -133
  28. package/dist/esm/chat/multi-thread-view.d.ts +4 -1
  29. package/dist/esm/chat/multi-thread-view.js +4 -0
  30. package/dist/esm/chat/new-chat-thread.d.ts +4 -1
  31. package/dist/esm/chat/new-chat-thread.js +43 -87
  32. package/dist/esm/file-preview/file-preview.d.ts +6 -0
  33. package/dist/esm/file-preview/file-preview.js +220 -0
  34. package/dist/esm/meetings/camera-grid.d.ts +46 -0
  35. package/dist/esm/meetings/camera-grid.js +405 -0
  36. package/dist/esm/meetings/controls.d.ts +4 -2
  37. package/dist/esm/meetings/controls.js +9 -3
  38. package/dist/esm/meetings/lobby.d.ts +17 -0
  39. package/dist/esm/meetings/lobby.js +595 -0
  40. package/dist/esm/meetings/meeting-scope.d.ts +7 -6
  41. package/dist/esm/meetings/meeting-scope.js +71 -16
  42. package/dist/esm/meetings/meeting-view.d.ts +6 -0
  43. package/dist/esm/meetings/meeting-view.js +630 -0
  44. package/dist/esm/meetings/meetings.d.ts +3 -0
  45. package/dist/esm/meetings/meetings.js +3 -0
  46. package/dist/esm/meetings/wake-lock.js +2 -2
  47. package/dist/index.css +1 -1
  48. package/package.json +9 -5
package/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## [0.41.5]
2
+ - Stability
3
+
1
4
  ## [0.41.4]
2
5
  - Stability
3
6
 
package/README.md CHANGED
@@ -38,6 +38,39 @@ MeshAgent removes the infrastructure headaches of building and shipping AI Agent
38
38
 
39
39
  Follow our [Getting Started Documentation](https://docs.meshagent.com/introduction/get_started) to setup your MeshAgent account, create your first project, and start building agents!
40
40
 
41
+ ## Meetings
42
+
43
+ The meeting components use a connected `RoomClient` to request LiveKit credentials before joining a meeting. Wrap `MeetingView` in `MeetingScope` and pass the same room client used by the rest of your app:
44
+
45
+ ```tsx
46
+ import { MeetingScope, MeetingView } from "@meshagent/meshagent-tailwind";
47
+ import type { RoomClient } from "@meshagent/meshagent";
48
+
49
+ export function MeetingPanel({ roomClient }: { roomClient: RoomClient }) {
50
+ return (
51
+ <MeetingScope client={roomClient}>
52
+ <MeetingView />
53
+ </MeetingScope>
54
+ );
55
+ }
56
+ ```
57
+
58
+ `MeetingScope` configures its controller with `roomClient.livekit.getConnectionInfo({ breakoutRoom: "" })` and `MeetingView` uses that connection info when the user starts the main meeting. If you are building a custom meeting UI, request the connection info before calling LiveKit's `connect`:
59
+
60
+ ```ts
61
+ import { Room } from "livekit-client";
62
+ import type { RoomClient } from "@meshagent/meshagent";
63
+
64
+ const livekitRoom = new Room();
65
+ const connectionInfo = await roomClient.livekit.getConnectionInfo({
66
+ breakoutRoom: "",
67
+ });
68
+
69
+ await livekitRoom.connect(connectionInfo.url, connectionInfo.token);
70
+ ```
71
+
72
+ Pass a non-empty `breakoutRoom` to `MeetingScope` or `roomClient.livekit.getConnectionInfo({ breakoutRoom })` when the meeting should join a specific breakout room.
73
+
41
74
  ## Next Steps and Examples
42
75
 
43
76
  To see examples of agents in action and to start building your own agents check out the MeshAgent docs at [docs.meshagent.com](https://docs.meshagent.com/)
@@ -259,7 +259,7 @@ function MarkdownBlock({ text }) {
259
259
  children
260
260
  }
261
261
  ),
262
- p: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { ...props, className: "mb-2 last:mb-0", children }),
262
+ p: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { ...props, className: "mb-2 last:mb-0 overflow-hidden max-w-full", children }),
263
263
  table: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "my-4 w-full overflow-x-auto", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
264
264
  "table",
265
265
  {
@@ -299,6 +299,17 @@ function MarkdownBlock({ text }) {
299
299
  className: "mb-2 ml-6 list-decimal last:mb-0",
300
300
  children
301
301
  }
302
+ ),
303
+ a: ({ children, href, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
304
+ "a",
305
+ {
306
+ ...props,
307
+ href,
308
+ target: "_blank",
309
+ rel: "noopener noreferrer",
310
+ className: "text-primary underline overflow-ellipsis inline-block max-w-full overflow-x-hidden",
311
+ children
312
+ }
302
313
  )
303
314
  },
304
315
  children: text
@@ -394,10 +405,7 @@ function ThreadAttachment({ room, attachment, onImageSettled }) {
394
405
  }
395
406
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileAttachment, { room, path });
396
407
  }
397
- function ChatBubble({
398
- text,
399
- mine
400
- }) {
408
+ function ChatBubble({ text, mine }) {
401
409
  if (text.trim() === "") {
402
410
  return null;
403
411
  }
@@ -405,7 +413,7 @@ function ChatBubble({
405
413
  "div",
406
414
  {
407
415
  className: (0, import_utils.cn)(
408
- "w-fit max-w-[85%] rounded-md px-4 py-3 text-sm leading-6 shadow-xs sm:max-w-2xl",
416
+ "w-fit max-w-[85%] rounded-md px-4 py-3 text-sm leading-6 shadow-xs sm:w-2xl",
409
417
  mine ? "bg-secondary/85 text-foreground" : "bg-muted/70 text-foreground"
410
418
  ),
411
419
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownBlock, { text })
@@ -474,10 +482,7 @@ function defaultEventHeadline(kind, state, eventName) {
474
482
  }
475
483
  return eventName.replace(/[._]/gu, " ");
476
484
  }
477
- function EventRow({
478
- room,
479
- message
480
- }) {
485
+ function EventRow({ room, message }) {
481
486
  const method = getTrimmedStringAttribute(message, "method") || "agent/event";
482
487
  const eventName = getTrimmedStringAttribute(message, "name") || getTrimmedStringAttribute(message, "event_type") || method.replace(/\//gu, ".");
483
488
  const kind = getTrimmedStringAttribute(message, "kind").toLowerCase();
@@ -1,8 +1,11 @@
1
1
  import type { ReactElement } from "react";
2
2
  import type { RoomClient } from "@meshagent/meshagent";
3
+ import type { BaseChatClient } from "@meshagent/meshagent-agents";
3
4
  export interface DatasetChatThreadProps {
4
5
  room: RoomClient;
5
6
  path: string;
7
+ chatClient?: BaseChatClient;
8
+ disposeChatClient?: boolean;
6
9
  agentName?: string;
7
10
  emptyStateTitle?: string;
8
11
  emptyStateDescription?: string;
@@ -10,4 +13,4 @@ export interface DatasetChatThreadProps {
10
13
  initialShowCompletedToolCalls?: boolean;
11
14
  openFile?: (path: string) => void | Promise<void>;
12
15
  }
13
- export declare function DatasetChatThread({ room, path, agentName, emptyStateTitle, emptyStateDescription, inputPlaceholder, initialShowCompletedToolCalls, openFile, }: DatasetChatThreadProps): ReactElement;
16
+ export declare function DatasetChatThread({ room, path, chatClient, disposeChatClient, agentName, emptyStateTitle, emptyStateDescription, inputPlaceholder, initialShowCompletedToolCalls, openFile, }: DatasetChatThreadProps): ReactElement;
@@ -34,7 +34,7 @@ module.exports = __toCommonJS(dataset_chat_thread_exports);
34
34
  var import_jsx_runtime = require("react/jsx-runtime");
35
35
  var import_react = require("react");
36
36
  var import_meshagent = require("@meshagent/meshagent");
37
- var import_meshagent_react = require("@meshagent/meshagent-react");
37
+ var import_meshagent_agents = require("@meshagent/meshagent-agents");
38
38
  var import_lucide_react = require("lucide-react");
39
39
  var import_react_markdown = __toESM(require("react-markdown"));
40
40
  var import_rehype_highlight = __toESM(require("rehype-highlight"));
@@ -48,34 +48,6 @@ var import_file_attachment = require("./file-attachment");
48
48
  var import_chat_hooks = require("./chat-hooks");
49
49
  var import_utils = require("../lib/utils");
50
50
  var import_chat_thread = require("./chat-thread");
51
- const agentRoomMessageType = "agent-message";
52
- const agentTurnStartType = "meshagent.agent.turn.start";
53
- const agentTurnSteerType = "meshagent.agent.turn.steer";
54
- const agentTurnInterruptType = "meshagent.agent.turn.interrupt";
55
- const agentThreadOpenType = "meshagent.agent.thread.open";
56
- const agentThreadCloseType = "meshagent.agent.thread.close";
57
- const agentTurnStartAcceptedType = "meshagent.agent.turn.start.accepted";
58
- const agentTurnStartRejectedType = "meshagent.agent.turn.start.rejected";
59
- const agentTurnSteerAcceptedType = "meshagent.agent.turn.steer.accepted";
60
- const agentTurnSteerRejectedType = "meshagent.agent.turn.steer.rejected";
61
- const agentTurnStartedType = "meshagent.agent.turn.started";
62
- const agentTurnSteeredType = "meshagent.agent.turn.steered";
63
- const agentTextContentDeltaType = "meshagent.agent.text_content.delta";
64
- const agentTextContentEndedType = "meshagent.agent.text_content.ended";
65
- const agentReasoningContentStartedType = "meshagent.agent.reasoning_content.started";
66
- const agentReasoningContentDeltaType = "meshagent.agent.reasoning_content.delta";
67
- const agentReasoningContentEndedType = "meshagent.agent.reasoning_content.ended";
68
- const agentFileContentStartedType = "meshagent.agent.file_content.started";
69
- const agentFileContentDeltaType = "meshagent.agent.file_content.delta";
70
- const agentFileContentEndedType = "meshagent.agent.file_content.ended";
71
- const agentToolCallPendingType = "meshagent.agent.tool_call.pending";
72
- const agentToolCallInProgressType = "meshagent.agent.tool_call.in_progress";
73
- const agentToolCallStartedType = "meshagent.agent.tool_call.started";
74
- const agentToolCallEndedType = "meshagent.agent.tool_call.ended";
75
- const agentImageGenerationStartedType = "meshagent.agent.image_generation.started";
76
- const agentImageGenerationPartialType = "meshagent.agent.image_generation.partial";
77
- const agentImageGenerationCompletedType = "meshagent.agent.image_generation.completed";
78
- const agentImageGenerationFailedType = "meshagent.agent.image_generation.failed";
79
51
  const stickyBottomThresholdPx = 24;
80
52
  const maxPreviewEdgePx = 312.5;
81
53
  function createDatasetThreadModel() {
@@ -496,11 +468,11 @@ function timestampFromPayload(payload) {
496
468
  }
497
469
  function imageGenerationStatusFromType(type) {
498
470
  switch (type) {
499
- case agentImageGenerationCompletedType:
471
+ case import_meshagent_agents.agentImageGenerationCompletedType:
500
472
  return "completed";
501
- case agentImageGenerationFailedType:
473
+ case import_meshagent_agents.agentImageGenerationFailedType:
502
474
  return "failed";
503
- case agentImageGenerationPartialType:
475
+ case import_meshagent_agents.agentImageGenerationPartialType:
504
476
  return "in_progress";
505
477
  default:
506
478
  return "pending";
@@ -668,7 +640,7 @@ function applyAgentMessagePayload(model, payload, path) {
668
640
  if (!type) {
669
641
  return false;
670
642
  }
671
- if (type === agentTurnStartRejectedType || type === agentTurnSteerRejectedType) {
643
+ if (type === import_meshagent_agents.agentTurnStartRejectedType || type === import_meshagent_agents.agentTurnSteerRejectedType) {
672
644
  const sourceMessageId = stringValue(payload.source_message_id);
673
645
  return sourceMessageId ? model.agentRowsByItemId.delete(sourceMessageId) : false;
674
646
  }
@@ -676,14 +648,14 @@ function applyAgentMessagePayload(model, payload, path) {
676
648
  const turnId = payloadTurnId(payload);
677
649
  let changed = false;
678
650
  switch (type) {
679
- case agentTurnStartAcceptedType:
680
- case agentTurnSteerAcceptedType:
651
+ case import_meshagent_agents.agentTurnStartAcceptedType:
652
+ case import_meshagent_agents.agentTurnSteerAcceptedType:
681
653
  break;
682
- case agentTurnStartedType:
683
- case agentTurnSteeredType:
654
+ case import_meshagent_agents.agentTurnStartedType:
655
+ case import_meshagent_agents.agentTurnSteeredType:
684
656
  changed = materializeTurnInputPayload(model, payload) || changed;
685
657
  break;
686
- case agentTextContentDeltaType:
658
+ case import_meshagent_agents.agentTextContentDeltaType:
687
659
  changed = appendAgentRowText({
688
660
  model,
689
661
  itemId,
@@ -693,7 +665,7 @@ function applyAgentMessagePayload(model, payload, path) {
693
665
  delta: String(payload.text ?? "")
694
666
  }) || changed;
695
667
  break;
696
- case agentTextContentEndedType:
668
+ case import_meshagent_agents.agentTextContentEndedType:
697
669
  changed = upsertAgentRow({
698
670
  model,
699
671
  itemId,
@@ -705,7 +677,7 @@ function applyAgentMessagePayload(model, payload, path) {
705
677
  }
706
678
  }) || changed;
707
679
  break;
708
- case agentReasoningContentStartedType:
680
+ case import_meshagent_agents.agentReasoningContentStartedType:
709
681
  changed = upsertAgentRow({
710
682
  model,
711
683
  itemId,
@@ -713,7 +685,7 @@ function applyAgentMessagePayload(model, payload, path) {
713
685
  data: { kind: "reasoning", role: "assistant", text: "" }
714
686
  }) || changed;
715
687
  break;
716
- case agentReasoningContentDeltaType:
688
+ case import_meshagent_agents.agentReasoningContentDeltaType:
717
689
  changed = appendAgentRowText({
718
690
  model,
719
691
  itemId,
@@ -723,7 +695,7 @@ function applyAgentMessagePayload(model, payload, path) {
723
695
  delta: String(payload.text ?? "")
724
696
  }) || changed;
725
697
  break;
726
- case agentReasoningContentEndedType:
698
+ case import_meshagent_agents.agentReasoningContentEndedType:
727
699
  changed = upsertAgentRow({
728
700
  model,
729
701
  itemId,
@@ -735,7 +707,7 @@ function applyAgentMessagePayload(model, payload, path) {
735
707
  }
736
708
  }) || changed;
737
709
  break;
738
- case agentFileContentStartedType:
710
+ case import_meshagent_agents.agentFileContentStartedType:
739
711
  changed = upsertAgentRow({
740
712
  model,
741
713
  itemId,
@@ -743,17 +715,17 @@ function applyAgentMessagePayload(model, payload, path) {
743
715
  data: { kind: "file", role: "assistant", urls: [] }
744
716
  }) || changed;
745
717
  break;
746
- case agentFileContentDeltaType:
747
- case agentFileContentEndedType:
718
+ case import_meshagent_agents.agentFileContentDeltaType:
719
+ case import_meshagent_agents.agentFileContentEndedType:
748
720
  changed = appendAgentRowUrl({ model, itemId, turnId, url: payload.url }) || changed;
749
721
  break;
750
- case agentToolCallPendingType:
751
- case agentToolCallInProgressType:
752
- case agentToolCallStartedType:
753
- case agentToolCallEndedType: {
722
+ case import_meshagent_agents.agentToolCallPendingType:
723
+ case import_meshagent_agents.agentToolCallInProgressType:
724
+ case import_meshagent_agents.agentToolCallStartedType:
725
+ case import_meshagent_agents.agentToolCallEndedType: {
754
726
  const tool = String(payload.tool ?? payload.tool_name ?? payload.name ?? "");
755
727
  const isImageGeneration = tool.trim().toLowerCase() === "image_generation";
756
- if (isImageGeneration && type === agentToolCallEndedType && payload.error == null) {
728
+ if (isImageGeneration && type === import_meshagent_agents.agentToolCallEndedType && payload.error == null) {
757
729
  break;
758
730
  }
759
731
  changed = upsertAgentRow({
@@ -772,15 +744,15 @@ function applyAgentMessagePayload(model, payload, path) {
772
744
  role: "assistant",
773
745
  toolkit: String(payload.toolkit ?? payload.toolkit_name ?? ""),
774
746
  tool,
775
- status: type === agentToolCallEndedType ? "completed" : "running"
747
+ status: type === import_meshagent_agents.agentToolCallEndedType ? "completed" : "running"
776
748
  }
777
749
  }) || changed;
778
750
  break;
779
751
  }
780
- case agentImageGenerationStartedType:
781
- case agentImageGenerationPartialType:
782
- case agentImageGenerationCompletedType:
783
- case agentImageGenerationFailedType:
752
+ case import_meshagent_agents.agentImageGenerationStartedType:
753
+ case import_meshagent_agents.agentImageGenerationPartialType:
754
+ case import_meshagent_agents.agentImageGenerationCompletedType:
755
+ case import_meshagent_agents.agentImageGenerationFailedType:
784
756
  changed = upsertAgentRow({
785
757
  model,
786
758
  itemId,
@@ -1019,6 +991,26 @@ function describeError(error) {
1019
991
  }
1020
992
  return String(error);
1021
993
  }
994
+ function pendingAgentMessageFromInput(pending) {
995
+ const payload = pending.payload.toJson();
996
+ const parsed = import_chat_hooks.PendingAgentMessage.fromQueueJson({
997
+ ...payload,
998
+ message_type: pending.messageType,
999
+ created_at: pending.createdAt.toISOString()
1000
+ });
1001
+ return new import_chat_hooks.PendingAgentMessage({
1002
+ messageId: parsed.messageId,
1003
+ messageType: parsed.messageType,
1004
+ threadPath: parsed.threadPath,
1005
+ text: parsed.text,
1006
+ attachments: parsed.attachments,
1007
+ senderName: parsed.senderName,
1008
+ createdAt: parsed.createdAt,
1009
+ matchByContentOnly: parsed.matchByContentOnly,
1010
+ awaitingAcceptance: pending.awaitingAcceptance,
1011
+ awaitingOnline: pending.awaitingOnline
1012
+ });
1013
+ }
1022
1014
  function MarkdownBlock({ text }) {
1023
1015
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1024
1016
  import_react_markdown.default,
@@ -1435,24 +1427,11 @@ function EmptyState({
1435
1427
  function ErrorBanner({ message }) {
1436
1428
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mx-auto w-full max-w-[912px] whitespace-pre-wrap rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive", children: message });
1437
1429
  }
1438
- function sendThreadSubscriptionMessage({
1439
- room,
1440
- agent,
1441
- messageType,
1442
- path
1443
- }) {
1444
- void room.messaging.sendMessage({
1445
- to: agent,
1446
- type: agentRoomMessageType,
1447
- ignoreOffline: true,
1448
- message: {
1449
- payload: { type: messageType, thread_id: path }
1450
- }
1451
- }).catch(() => void 0);
1452
- }
1453
1430
  function DatasetChatThread({
1454
1431
  room,
1455
1432
  path,
1433
+ chatClient,
1434
+ disposeChatClient = false,
1456
1435
  agentName,
1457
1436
  emptyStateTitle,
1458
1437
  emptyStateDescription,
@@ -1466,14 +1445,35 @@ function DatasetChatThread({
1466
1445
  const [sendError, setSendError] = (0, import_react.useState)(null);
1467
1446
  const [showCompletedToolCalls, setShowCompletedToolCalls] = (0, import_react.useState)(initialShowCompletedToolCalls);
1468
1447
  const status = (0, import_chat_hooks.useThreadStatus)({ room, path, agentName });
1469
- const agentParticipant = findAgentParticipant(room, agentName);
1470
1448
  const localParticipantName = getParticipantName(room.localParticipant);
1471
1449
  const scrollContainerRef = (0, import_react.useRef)(null);
1472
1450
  const contentRef = (0, import_react.useRef)(null);
1473
1451
  const stickToBottomRef = (0, import_react.useRef)(true);
1452
+ const threadSessionRef = (0, import_react.useRef)(null);
1453
+ const threadSessionCursorRef = (0, import_react.useRef)(0);
1454
+ const [threadSessionVersion, setThreadSessionVersion] = (0, import_react.useState)(0);
1455
+ const ownsChatClient = chatClient == null;
1456
+ const activeChatClient = (0, import_react.useMemo)(
1457
+ () => chatClient ?? new import_meshagent_agents.MessagingChatClient({ room, agentName }),
1458
+ [agentName, chatClient, room]
1459
+ );
1460
+ const agentParticipant = activeChatClient.agentParticipant() ?? findAgentParticipant(room, agentName);
1474
1461
  const bumpModelVersion = (0, import_react.useCallback)(() => {
1475
1462
  setModelVersion((current) => current + 1);
1476
1463
  }, []);
1464
+ (0, import_react.useEffect)(() => {
1465
+ void activeChatClient.start();
1466
+ const handleChange = () => {
1467
+ setThreadSessionVersion((current) => current + 1);
1468
+ };
1469
+ activeChatClient.addListener(handleChange);
1470
+ return () => {
1471
+ activeChatClient.removeListener(handleChange);
1472
+ if (ownsChatClient || disposeChatClient) {
1473
+ void activeChatClient.stop();
1474
+ }
1475
+ };
1476
+ }, [activeChatClient, disposeChatClient, ownsChatClient]);
1477
1477
  (0, import_react.useEffect)(() => {
1478
1478
  const model2 = createDatasetThreadModel();
1479
1479
  modelRef.current = model2;
@@ -1551,46 +1551,41 @@ function DatasetChatThread({
1551
1551
  };
1552
1552
  }, [bumpModelVersion, path, room]);
1553
1553
  (0, import_react.useEffect)(() => {
1554
- const subscription = (0, import_meshagent_react.subscribe)(room.listen(), {
1555
- next: (event) => {
1556
- if (!(event instanceof import_meshagent.RoomMessageEvent)) {
1557
- return;
1558
- }
1559
- if (event.message.type !== agentRoomMessageType) {
1560
- return;
1561
- }
1562
- const payload = event.message.message.payload;
1563
- if (!isRecord(payload)) {
1564
- return;
1565
- }
1566
- if (applyAgentMessagePayload(modelRef.current, payload, path)) {
1567
- bumpModelVersion();
1554
+ if (isTmpThreadPath(path)) {
1555
+ threadSessionRef.current = null;
1556
+ threadSessionCursorRef.current = 0;
1557
+ setThreadSessionVersion((current) => current + 1);
1558
+ return;
1559
+ }
1560
+ const session = activeChatClient.openThread(path);
1561
+ threadSessionRef.current = session;
1562
+ threadSessionCursorRef.current = 0;
1563
+ const drainSessionMessages = () => {
1564
+ let changed = false;
1565
+ const messages = session.messages;
1566
+ while (threadSessionCursorRef.current < messages.length) {
1567
+ const event = messages[threadSessionCursorRef.current];
1568
+ threadSessionCursorRef.current += 1;
1569
+ if (applyAgentMessagePayload(modelRef.current, event.payload, path)) {
1570
+ changed = true;
1568
1571
  }
1569
1572
  }
1570
- });
1571
- return () => {
1572
- subscription.unsubscribe();
1573
+ if (changed) {
1574
+ bumpModelVersion();
1575
+ }
1576
+ setThreadSessionVersion((current) => current + 1);
1573
1577
  };
1574
- }, [bumpModelVersion, path, room]);
1575
- (0, import_react.useEffect)(() => {
1576
- if (!agentParticipant) {
1577
- return;
1578
- }
1579
- sendThreadSubscriptionMessage({
1580
- room,
1581
- agent: agentParticipant,
1582
- messageType: agentThreadOpenType,
1583
- path
1584
- });
1578
+ session.addListener(drainSessionMessages);
1579
+ drainSessionMessages();
1585
1580
  return () => {
1586
- sendThreadSubscriptionMessage({
1587
- room,
1588
- agent: agentParticipant,
1589
- messageType: agentThreadCloseType,
1590
- path
1591
- });
1581
+ session.removeListener(drainSessionMessages);
1582
+ if (threadSessionRef.current === session) {
1583
+ threadSessionRef.current = null;
1584
+ threadSessionCursorRef.current = 0;
1585
+ }
1586
+ void session.close().catch(() => void 0);
1592
1587
  };
1593
- }, [agentParticipant, path, room]);
1588
+ }, [activeChatClient, bumpModelVersion, path]);
1594
1589
  const allMessages = (0, import_react.useMemo)(() => {
1595
1590
  const model2 = modelRef.current;
1596
1591
  const mergedRowsByItemId = /* @__PURE__ */ new Map();
@@ -1615,29 +1610,24 @@ function DatasetChatThread({
1615
1610
  for (const pending of status.pendingMessages) {
1616
1611
  combined.set(pending.messageId, pending);
1617
1612
  }
1613
+ for (const pending of threadSessionRef.current?.pendingInputs ?? []) {
1614
+ combined.set(pending.messageId, pendingAgentMessageFromInput(pending));
1615
+ }
1618
1616
  const values = Array.from(combined.values()).filter((pending) => !allMessages.some((message) => datasetThreadMessageMatchesPendingAgentMessage(message, pending)));
1619
1617
  return [
1620
1618
  ...values.filter((message) => !message.awaitingAcceptance),
1621
1619
  ...values.filter((message) => message.awaitingAcceptance)
1622
1620
  ];
1623
- }, [allMessages, status.pendingMessages]);
1624
- const canInterruptActiveTurn = status.supportsAgentMessages && status.turnId != null;
1621
+ }, [allMessages, status.pendingMessages, threadSessionVersion]);
1622
+ const canInterruptActiveTurn = status.turnId != null && (status.supportsAgentMessages || agentParticipant != null || chatClient != null);
1625
1623
  const cancelTurn = (0, import_react.useCallback)(async () => {
1626
- if (!status.turnId?.trim() || !agentParticipant) {
1624
+ const turnId = status.turnId?.trim();
1625
+ const session = threadSessionRef.current;
1626
+ if (!turnId || session == null) {
1627
1627
  return;
1628
1628
  }
1629
- await room.messaging.sendMessage({
1630
- to: agentParticipant,
1631
- type: agentRoomMessageType,
1632
- message: {
1633
- payload: {
1634
- type: agentTurnInterruptType,
1635
- thread_id: path,
1636
- turn_id: status.turnId
1637
- }
1638
- }
1639
- });
1640
- }, [agentParticipant, path, room, status.turnId]);
1629
+ await session.interruptTurn(turnId);
1630
+ }, [status.turnId, threadSessionVersion]);
1641
1631
  const selectAttachments = (0, import_react.useCallback)((files) => {
1642
1632
  const nextAttachments = files.map((file) => new import_file_attachment.MeshagentFileUpload(
1643
1633
  room,
@@ -1651,56 +1641,39 @@ function DatasetChatThread({
1651
1641
  if (message.text.trim() === "" && message.attachments.length === 0) {
1652
1642
  return;
1653
1643
  }
1654
- if (!agentParticipant) {
1644
+ if (!agentParticipant && chatClient == null) {
1655
1645
  setSendError("This thread requires an online agent that supports agent messages.");
1656
1646
  return;
1657
1647
  }
1648
+ const session = threadSessionRef.current;
1649
+ if (session == null) {
1650
+ setSendError("No thread session is open.");
1651
+ return;
1652
+ }
1658
1653
  const isSteer = status.mode === "steerable" && status.turnId != null;
1659
1654
  const normalizedAttachments = message.attachments.map(normalizeAgentAttachmentUrl).filter((attachment) => attachment !== null);
1660
1655
  const senderName = localParticipantName.trim() || void 0;
1661
- upsertAgentRow({
1662
- model: modelRef.current,
1663
- itemId: message.id,
1664
- turnId: null,
1665
- timestamp: /* @__PURE__ */ new Date(),
1666
- data: {
1667
- kind: "message",
1668
- role: "user",
1669
- text: message.text,
1670
- sender_name: senderName,
1671
- attachments: normalizedAttachments
1672
- }
1673
- });
1674
- bumpModelVersion();
1675
1656
  try {
1676
- const payload = {
1677
- type: isSteer ? agentTurnSteerType : agentTurnStartType,
1678
- thread_id: path,
1679
- message_id: message.id,
1680
- content: agentInputContent(message.text, message.attachments)
1681
- };
1682
- if (isSteer && status.turnId) {
1683
- payload.turn_id = status.turnId;
1684
- }
1685
- await room.messaging.sendMessage({
1686
- to: agentParticipant,
1687
- type: agentRoomMessageType,
1688
- message: { payload }
1657
+ await session.sendText({
1658
+ messageId: message.id,
1659
+ text: message.text,
1660
+ attachments: normalizedAttachments,
1661
+ steer: isSteer,
1662
+ turnId: status.turnId,
1663
+ senderName
1689
1664
  });
1690
1665
  setSendError(null);
1666
+ setThreadSessionVersion((current) => current + 1);
1691
1667
  } catch (error) {
1692
- modelRef.current.agentRowsByItemId.delete(message.id);
1693
- bumpModelVersion();
1694
1668
  setSendError(describeError(error));
1695
1669
  }
1696
1670
  }, [
1697
1671
  agentParticipant,
1698
- bumpModelVersion,
1672
+ chatClient,
1699
1673
  localParticipantName,
1700
- path,
1701
- room,
1702
1674
  status.mode,
1703
- status.turnId
1675
+ status.turnId,
1676
+ threadSessionVersion
1704
1677
  ]);
1705
1678
  const hasWireBackedContent = modelRef.current.agentRowsByItemId.size > 0 || status.pendingMessages.length > 0 || pendingMessages.length > 0;
1706
1679
  const model = modelRef.current;
@@ -1832,8 +1805,8 @@ function DatasetChatThread({
1832
1805
  attachments,
1833
1806
  onFilesSelected: selectAttachments,
1834
1807
  setAttachments,
1835
- disabled: agentParticipant == null,
1836
- placeholder: inputPlaceholder ?? (agentParticipant ? "Type a message" : `Waiting for ${displayParticipantName(agentName ?? "agent")}`)
1808
+ disabled: agentParticipant == null && chatClient == null,
1809
+ placeholder: inputPlaceholder ?? (agentParticipant || chatClient ? "Type a message" : `Waiting for ${displayParticipantName(agentName ?? "agent")}`)
1837
1810
  }
1838
1811
  )
1839
1812
  ] });
@@ -1,8 +1,11 @@
1
1
  import type { ReactElement } from "react";
2
2
  import { RoomClient } from "@meshagent/meshagent";
3
+ import type { BaseChatClient } from "@meshagent/meshagent-agents";
3
4
  export type MultiThreadContentBuilder = (threadPath: string) => ReactElement;
4
5
  export interface MultiThreadViewProps {
5
6
  room: RoomClient;
7
+ chatClient?: BaseChatClient;
8
+ disposeChatClient?: boolean;
6
9
  agentName: string;
7
10
  builder: MultiThreadContentBuilder;
8
11
  toolkit?: string;
@@ -15,4 +18,4 @@ export interface MultiThreadViewProps {
15
18
  emptyStateTitle?: string;
16
19
  emptyStateDescription?: string;
17
20
  }
18
- export declare function MultiThreadView({ room, agentName, builder, toolkit, tool, selectedThreadPath, onSelectedThreadPathChanged, onSelectedThreadResolved, newThreadResetVersion, centerComposer, emptyStateTitle, emptyStateDescription, }: MultiThreadViewProps): ReactElement;
21
+ export declare function MultiThreadView({ room, chatClient, disposeChatClient, agentName, builder, toolkit, tool, selectedThreadPath, onSelectedThreadPathChanged, onSelectedThreadResolved, newThreadResetVersion, centerComposer, emptyStateTitle, emptyStateDescription, }: MultiThreadViewProps): ReactElement;
@@ -30,6 +30,8 @@ function normalizeSelectedThreadPath(path) {
30
30
  }
31
31
  function MultiThreadView({
32
32
  room,
33
+ chatClient,
34
+ disposeChatClient = false,
33
35
  agentName,
34
36
  builder,
35
37
  toolkit = "chat",
@@ -68,6 +70,8 @@ function MultiThreadView({
68
70
  import_new_chat_thread.NewChatThread,
69
71
  {
70
72
  room,
73
+ chatClient,
74
+ disposeChatClient,
71
75
  agentName,
72
76
  builder,
73
77
  toolkit,
@@ -1,8 +1,11 @@
1
1
  import type { ReactElement } from "react";
2
2
  import { RoomClient } from "@meshagent/meshagent";
3
+ import type { BaseChatClient } from "@meshagent/meshagent-agents";
3
4
  export type NewChatThreadBuilder = (threadPath: string) => ReactElement;
4
5
  export interface NewChatThreadProps {
5
6
  room: RoomClient;
7
+ chatClient?: BaseChatClient;
8
+ disposeChatClient?: boolean;
6
9
  agentName: string;
7
10
  builder: NewChatThreadBuilder;
8
11
  toolkit?: string;
@@ -14,4 +17,4 @@ export interface NewChatThreadProps {
14
17
  emptyStateTitle?: string;
15
18
  emptyStateDescription?: string;
16
19
  }
17
- export declare function NewChatThread({ room, agentName, builder, toolkit, tool, selectedThreadPath, onThreadPathChanged, onThreadResolved, centerComposer, emptyStateTitle, emptyStateDescription, }: NewChatThreadProps): ReactElement;
20
+ export declare function NewChatThread({ room, chatClient, disposeChatClient, agentName, builder, selectedThreadPath, onThreadPathChanged, onThreadResolved, centerComposer, emptyStateTitle, emptyStateDescription, }: NewChatThreadProps): ReactElement;