@elizaos/client 1.5.5-alpha.10

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 (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +350 -0
  3. package/dist/assets/empty-module-CLMscLYw.js +1 -0
  4. package/dist/assets/main-BBZ_3lkn.css +5999 -0
  5. package/dist/assets/main-C5zNUkXH.js +7 -0
  6. package/dist/assets/main-Dz64ENQg.js +614 -0
  7. package/dist/assets/react-vendor-DM5m98rr.js +545 -0
  8. package/dist/assets/ui-vendor-BQCqNqg0.js +1 -0
  9. package/dist/elizaos-avatar.png +0 -0
  10. package/dist/elizaos-icon.png +0 -0
  11. package/dist/elizaos-logo-light.png +0 -0
  12. package/dist/elizaos.webp +0 -0
  13. package/dist/favicon.ico +0 -0
  14. package/dist/images/agents/agent1.png +0 -0
  15. package/dist/images/agents/agent2.png +0 -0
  16. package/dist/images/agents/agent3.png +0 -0
  17. package/dist/images/agents/agent4.png +0 -0
  18. package/dist/images/agents/agent5.png +0 -0
  19. package/dist/index.html +14 -0
  20. package/index.html +24 -0
  21. package/package.json +159 -0
  22. package/postcss.config.js +3 -0
  23. package/public/elizaos-avatar.png +0 -0
  24. package/public/elizaos-icon.png +0 -0
  25. package/public/elizaos-logo-light.png +0 -0
  26. package/public/elizaos.webp +0 -0
  27. package/public/favicon.ico +0 -0
  28. package/public/images/agents/agent1.png +0 -0
  29. package/public/images/agents/agent2.png +0 -0
  30. package/public/images/agents/agent3.png +0 -0
  31. package/public/images/agents/agent4.png +0 -0
  32. package/public/images/agents/agent5.png +0 -0
  33. package/src/App.tsx +222 -0
  34. package/src/components/AgentDetailsPanel.tsx +147 -0
  35. package/src/components/ChatInputArea.tsx +196 -0
  36. package/src/components/ChatMessageListComponent.tsx +139 -0
  37. package/src/components/actionTool.tsx +186 -0
  38. package/src/components/add-agent-card.tsx +77 -0
  39. package/src/components/agent-action-viewer.tsx +816 -0
  40. package/src/components/agent-avatar-stack.tsx +121 -0
  41. package/src/components/agent-card.cy.tsx +259 -0
  42. package/src/components/agent-card.tsx +177 -0
  43. package/src/components/agent-creator.tsx +142 -0
  44. package/src/components/agent-log-viewer.tsx +645 -0
  45. package/src/components/agent-memory-edit-overlay.tsx +461 -0
  46. package/src/components/agent-memory-viewer.tsx +504 -0
  47. package/src/components/agent-settings.tsx +270 -0
  48. package/src/components/agent-sidebar.tsx +178 -0
  49. package/src/components/api-key-dialog.tsx +113 -0
  50. package/src/components/app-sidebar.tsx +685 -0
  51. package/src/components/array-input.tsx +116 -0
  52. package/src/components/audio-recorder.tsx +292 -0
  53. package/src/components/avatar-panel.tsx +141 -0
  54. package/src/components/character-form.tsx +1138 -0
  55. package/src/components/chat.tsx +1813 -0
  56. package/src/components/combobox.tsx +187 -0
  57. package/src/components/confirmation-dialog.tsx +59 -0
  58. package/src/components/connection-error-banner.tsx +101 -0
  59. package/src/components/connection-status.cy.tsx +73 -0
  60. package/src/components/connection-status.tsx +155 -0
  61. package/src/components/copy-button.tsx +35 -0
  62. package/src/components/delete-button.tsx +24 -0
  63. package/src/components/env-settings.tsx +261 -0
  64. package/src/components/group-card.tsx +160 -0
  65. package/src/components/group-panel.tsx +543 -0
  66. package/src/components/input-copy.tsx +21 -0
  67. package/src/components/logs-page.tsx +41 -0
  68. package/src/components/media-content.tsx +385 -0
  69. package/src/components/memory-graph.tsx +170 -0
  70. package/src/components/missing-secrets-dialog.tsx +72 -0
  71. package/src/components/onboarding-tour.tsx +247 -0
  72. package/src/components/page-title.tsx +8 -0
  73. package/src/components/plugins-panel.tsx +383 -0
  74. package/src/components/profile-card.tsx +66 -0
  75. package/src/components/profile-overlay.tsx +283 -0
  76. package/src/components/retry-button.tsx +28 -0
  77. package/src/components/secret-panel.tsx +1505 -0
  78. package/src/components/server-management.tsx +264 -0
  79. package/src/components/split-button.tsx +148 -0
  80. package/src/components/stop-agent-button.tsx +99 -0
  81. package/src/components/ui/alert-dialog.cy.tsx +333 -0
  82. package/src/components/ui/alert-dialog.tsx +115 -0
  83. package/src/components/ui/alert.tsx +49 -0
  84. package/src/components/ui/avatar.cy.tsx +180 -0
  85. package/src/components/ui/avatar.tsx +57 -0
  86. package/src/components/ui/badge.cy.tsx +146 -0
  87. package/src/components/ui/badge.tsx +43 -0
  88. package/src/components/ui/button.cy.tsx +177 -0
  89. package/src/components/ui/button.tsx +56 -0
  90. package/src/components/ui/card.cy.tsx +160 -0
  91. package/src/components/ui/card.tsx +73 -0
  92. package/src/components/ui/chat/animated-markdown.tsx +59 -0
  93. package/src/components/ui/chat/chat-bubble.tsx +178 -0
  94. package/src/components/ui/chat/chat-container.tsx +51 -0
  95. package/src/components/ui/chat/chat-input.cy.tsx +169 -0
  96. package/src/components/ui/chat/chat-input.tsx +47 -0
  97. package/src/components/ui/chat/chat-message-list.tsx +61 -0
  98. package/src/components/ui/chat/chat-tts-button.tsx +199 -0
  99. package/src/components/ui/chat/code-block.tsx +79 -0
  100. package/src/components/ui/chat/expandable-chat.tsx +131 -0
  101. package/src/components/ui/chat/hooks/useAutoScroll.ts +86 -0
  102. package/src/components/ui/chat/markdown.tsx +209 -0
  103. package/src/components/ui/chat/message-loading.tsx +48 -0
  104. package/src/components/ui/checkbox.cy.tsx +170 -0
  105. package/src/components/ui/checkbox.tsx +30 -0
  106. package/src/components/ui/collapsible.cy.tsx +283 -0
  107. package/src/components/ui/collapsible.tsx +9 -0
  108. package/src/components/ui/command.cy.tsx +313 -0
  109. package/src/components/ui/command.tsx +143 -0
  110. package/src/components/ui/dialog.cy.tsx +279 -0
  111. package/src/components/ui/dialog.tsx +104 -0
  112. package/src/components/ui/dropdown-menu.cy.tsx +273 -0
  113. package/src/components/ui/dropdown-menu.tsx +281 -0
  114. package/src/components/ui/input.cy.tsx +82 -0
  115. package/src/components/ui/input.tsx +27 -0
  116. package/src/components/ui/label.cy.tsx +157 -0
  117. package/src/components/ui/label.tsx +19 -0
  118. package/src/components/ui/resizable.tsx +42 -0
  119. package/src/components/ui/scroll-area.cy.tsx +242 -0
  120. package/src/components/ui/scroll-area.tsx +46 -0
  121. package/src/components/ui/select.cy.tsx +277 -0
  122. package/src/components/ui/select.tsx +155 -0
  123. package/src/components/ui/separator.cy.tsx +145 -0
  124. package/src/components/ui/separator.tsx +29 -0
  125. package/src/components/ui/sheet.cy.tsx +324 -0
  126. package/src/components/ui/sheet.tsx +119 -0
  127. package/src/components/ui/sidebar.tsx +734 -0
  128. package/src/components/ui/skeleton.cy.tsx +149 -0
  129. package/src/components/ui/skeleton.tsx +17 -0
  130. package/src/components/ui/split-button.cy.tsx +274 -0
  131. package/src/components/ui/split-button.tsx +112 -0
  132. package/src/components/ui/switch.tsx +28 -0
  133. package/src/components/ui/tabs.cy.tsx +271 -0
  134. package/src/components/ui/tabs.tsx +53 -0
  135. package/src/components/ui/textarea.cy.tsx +136 -0
  136. package/src/components/ui/textarea.tsx +26 -0
  137. package/src/components/ui/toast.cy.tsx +209 -0
  138. package/src/components/ui/toast.tsx +126 -0
  139. package/src/components/ui/toaster.tsx +29 -0
  140. package/src/components/ui/tooltip.cy.tsx +244 -0
  141. package/src/components/ui/tooltip.tsx +30 -0
  142. package/src/config/agent-templates.ts +349 -0
  143. package/src/config/voice-models.ts +181 -0
  144. package/src/constants.ts +23 -0
  145. package/src/context/AuthContext.tsx +44 -0
  146. package/src/context/ConnectionContext.tsx +194 -0
  147. package/src/entry.tsx +9 -0
  148. package/src/hooks/__tests__/use-agent-tab-state.test.ts +137 -0
  149. package/src/hooks/__tests__/use-agent-update.test.tsx +250 -0
  150. package/src/hooks/__tests__/use-character-convert.test.ts +102 -0
  151. package/src/hooks/__tests__/use-panel-width-state.test.ts +243 -0
  152. package/src/hooks/__tests__/use-sidebar-state.test.ts +117 -0
  153. package/src/hooks/use-agent-management.ts +130 -0
  154. package/src/hooks/use-agent-tab-state.ts +74 -0
  155. package/src/hooks/use-agent-update.ts +469 -0
  156. package/src/hooks/use-character-convert.ts +138 -0
  157. package/src/hooks/use-confirmation.ts +55 -0
  158. package/src/hooks/use-delete-agent.ts +123 -0
  159. package/src/hooks/use-dm-channels.ts +198 -0
  160. package/src/hooks/use-elevenlabs-voices.ts +83 -0
  161. package/src/hooks/use-file-upload.ts +224 -0
  162. package/src/hooks/use-mobile.tsx +19 -0
  163. package/src/hooks/use-onboarding.tsx +49 -0
  164. package/src/hooks/use-panel-width-state.ts +147 -0
  165. package/src/hooks/use-partial-update.ts +288 -0
  166. package/src/hooks/use-plugin-details.ts +462 -0
  167. package/src/hooks/use-plugins.ts +119 -0
  168. package/src/hooks/use-query-hooks.ts +1263 -0
  169. package/src/hooks/use-server-agents.ts +62 -0
  170. package/src/hooks/use-server-version.tsx +47 -0
  171. package/src/hooks/use-sidebar-state.ts +50 -0
  172. package/src/hooks/use-socket-chat.ts +264 -0
  173. package/src/hooks/use-toast.ts +260 -0
  174. package/src/hooks/use-version.tsx +64 -0
  175. package/src/index.css +146 -0
  176. package/src/lib/api-client-config.ts +53 -0
  177. package/src/lib/api-type-mappers.ts +196 -0
  178. package/src/lib/export-utils.ts +123 -0
  179. package/src/lib/logger.ts +19 -0
  180. package/src/lib/media-utils.ts +170 -0
  181. package/src/lib/pca.test.ts +17 -0
  182. package/src/lib/pca.ts +52 -0
  183. package/src/lib/socketio-manager.ts +664 -0
  184. package/src/lib/utils.ts +168 -0
  185. package/src/main.tsx +16 -0
  186. package/src/mocks/empty-module.ts +12 -0
  187. package/src/mocks/node-module.ts +57 -0
  188. package/src/polyfills.ts +37 -0
  189. package/src/routes/agent-detail.tsx +30 -0
  190. package/src/routes/agent-list.tsx +27 -0
  191. package/src/routes/agent-settings.tsx +48 -0
  192. package/src/routes/character-detail.tsx +52 -0
  193. package/src/routes/character-form.tsx +79 -0
  194. package/src/routes/character-list.tsx +38 -0
  195. package/src/routes/chat.tsx +128 -0
  196. package/src/routes/createAgent.tsx +13 -0
  197. package/src/routes/group-new.tsx +50 -0
  198. package/src/routes/group.tsx +29 -0
  199. package/src/routes/home.tsx +218 -0
  200. package/src/routes/not-found.tsx +71 -0
  201. package/src/test/setup.ts +154 -0
  202. package/src/types/crypto-browserify.d.ts +4 -0
  203. package/src/types/index.ts +13 -0
  204. package/src/types/rooms.ts +8 -0
  205. package/src/types.ts +84 -0
  206. package/src/vite-env.d.ts +40 -0
  207. package/tailwind.config.ts +90 -0
  208. package/tsconfig.json +10 -0
  209. package/vite.config.ts +102 -0
@@ -0,0 +1,664 @@
1
+ import { USER_NAME } from '@/constants';
2
+ import { SOCKET_MESSAGE_TYPE } from '@elizaos/core';
3
+ import { Evt } from 'evt';
4
+ import { io, type Socket } from 'socket.io-client';
5
+ import { randomUUID } from './utils';
6
+ import clientLogger from './logger';
7
+
8
+ // Define types for the events
9
+ export type MessageBroadcastData = {
10
+ senderId: string;
11
+ senderName: string;
12
+ text: string;
13
+ channelId: string;
14
+ roomId?: string; // Deprecated - for backward compatibility only
15
+ createdAt: number;
16
+ source: string;
17
+ name: string; // Required for ContentWithUser compatibility
18
+ attachments?: any[];
19
+ thought?: string; // Agent's thought process
20
+ actions?: string[]; // Actions taken by the agent
21
+ prompt?: string; // The LLM prompt used to generate this message
22
+ [key: string]: any;
23
+ };
24
+
25
+ export type MessageCompleteData = {
26
+ channelId: string;
27
+ roomId?: string; // Deprecated - for backward compatibility only
28
+ [key: string]: any;
29
+ };
30
+
31
+ // Define type for control messages
32
+ export type ControlMessageData = {
33
+ action: 'enable_input' | 'disable_input';
34
+ target?: string;
35
+ channelId: string;
36
+ roomId?: string; // Deprecated - for backward compatibility only
37
+ [key: string]: any;
38
+ };
39
+
40
+ // Define type for message deletion events
41
+ export type MessageDeletedData = {
42
+ messageId: string;
43
+ channelId: string;
44
+ roomId?: string; // Deprecated - for backward compatibility only
45
+ [key: string]: any;
46
+ };
47
+
48
+ // Define type for channel cleared events
49
+ export type ChannelClearedData = {
50
+ channelId: string;
51
+ roomId?: string; // Deprecated - for backward compatibility only
52
+ [key: string]: any;
53
+ };
54
+
55
+ // Define type for channel deleted events
56
+ export type ChannelDeletedData = {
57
+ channelId: string;
58
+ roomId?: string; // Deprecated - for backward compatibility only
59
+ [key: string]: any;
60
+ };
61
+
62
+ // Define type for log stream messages
63
+ export type LogStreamData = {
64
+ level: number;
65
+ time: number;
66
+ msg: string;
67
+ agentId?: string;
68
+ agentName?: string;
69
+ channelId?: string;
70
+ roomId?: string; // Deprecated - for backward compatibility only
71
+ [key: string]: string | number | boolean | null | undefined;
72
+ };
73
+
74
+ // A simple class that provides EventEmitter-like interface using Evt internally
75
+ class EventAdapter {
76
+ private events: Record<string, Evt<any>> = {};
77
+
78
+ constructor() {
79
+ // Initialize common events
80
+ this.events.messageBroadcast = Evt.create<MessageBroadcastData>();
81
+ this.events.messageComplete = Evt.create<MessageCompleteData>();
82
+ this.events.controlMessage = Evt.create<ControlMessageData>();
83
+ this.events.messageDeleted = Evt.create<MessageDeletedData>();
84
+ this.events.channelCleared = Evt.create<ChannelClearedData>();
85
+ this.events.channelDeleted = Evt.create<ChannelDeletedData>();
86
+ this.events.logStream = Evt.create<LogStreamData>();
87
+ }
88
+
89
+ on(eventName: string, listener: (...args: any[]) => void) {
90
+ if (!this.events[eventName]) {
91
+ this.events[eventName] = Evt.create();
92
+ }
93
+
94
+ this.events[eventName].attach(listener);
95
+ return this;
96
+ }
97
+
98
+ off(eventName: string, listener: (...args: any[]) => void) {
99
+ if (this.events[eventName]) {
100
+ const handlers = this.events[eventName].getHandlers();
101
+ for (const handler of handlers) {
102
+ if (handler.callback === listener) {
103
+ handler.detach();
104
+ }
105
+ }
106
+ }
107
+ return this;
108
+ }
109
+
110
+ emit(eventName: string, ...args: any[]) {
111
+ if (this.events[eventName]) {
112
+ this.events[eventName].post(args.length === 1 ? args[0] : args);
113
+ }
114
+ return this;
115
+ }
116
+
117
+ once(eventName: string, listener: (...args: any[]) => void) {
118
+ if (!this.events[eventName]) {
119
+ this.events[eventName] = Evt.create();
120
+ }
121
+
122
+ this.events[eventName].attachOnce(listener);
123
+ return this;
124
+ }
125
+
126
+ // For checking if EventEmitter has listeners
127
+ listenerCount(eventName: string): number {
128
+ if (!this.events[eventName]) return 0;
129
+ return this.events[eventName].getHandlers().length;
130
+ }
131
+
132
+ // Used only for internal access to the Evt instances
133
+ _getEvt(eventName: string): Evt<any> | undefined {
134
+ return this.events[eventName];
135
+ }
136
+ }
137
+
138
+ /**
139
+ * SocketIOManager handles real-time communication between the client and server
140
+ * using Socket.io. It maintains a single connection to the server and allows
141
+ * joining and messaging in multiple rooms.
142
+ */
143
+ export class SocketIOManager extends EventAdapter {
144
+ private static instance: SocketIOManager | null = null;
145
+ private socket: Socket | null = null;
146
+ private isConnected = false;
147
+ private connectPromise: Promise<void> | null = null;
148
+ private resolveConnect: (() => void) | null = null;
149
+ private activeChannelIds: Set<string> = new Set();
150
+ private clientEntityId: string | null = null;
151
+ private logStreamSubscribed: boolean = false;
152
+
153
+ // Public accessor for EVT instances (for advanced usage)
154
+ public get evtMessageBroadcast() {
155
+ return this._getEvt('messageBroadcast') as Evt<MessageBroadcastData>;
156
+ }
157
+
158
+ public get evtMessageComplete() {
159
+ return this._getEvt('messageComplete') as Evt<MessageCompleteData>;
160
+ }
161
+
162
+ public get evtControlMessage() {
163
+ return this._getEvt('controlMessage') as Evt<ControlMessageData>;
164
+ }
165
+
166
+ public get evtMessageDeleted() {
167
+ return this._getEvt('messageDeleted') as Evt<MessageDeletedData>;
168
+ }
169
+
170
+ public get evtChannelCleared() {
171
+ return this._getEvt('channelCleared') as Evt<ChannelClearedData>;
172
+ }
173
+
174
+ public get evtChannelDeleted() {
175
+ return this._getEvt('channelDeleted') as Evt<ChannelDeletedData>;
176
+ }
177
+
178
+ public get evtLogStream() {
179
+ return this._getEvt('logStream') as Evt<LogStreamData>;
180
+ }
181
+
182
+ private constructor() {
183
+ super();
184
+ }
185
+
186
+ public static getInstance(): SocketIOManager {
187
+ if (!SocketIOManager.instance) {
188
+ SocketIOManager.instance = new SocketIOManager();
189
+ }
190
+ return SocketIOManager.instance;
191
+ }
192
+
193
+ public static isConnected(): boolean {
194
+ return SocketIOManager.instance?.isConnected || false;
195
+ }
196
+
197
+ public isChannelActive(channelId: string): boolean {
198
+ return this.activeChannelIds.has(channelId);
199
+ }
200
+
201
+ /**
202
+ * Initialize the Socket.io connection to the server
203
+ * @param clientEntityId The client entity ID (central user ID)
204
+ */
205
+ public initialize(clientEntityId: string): void {
206
+ this.clientEntityId = clientEntityId;
207
+
208
+ if (this.socket) {
209
+ clientLogger.debug('[SocketIO] Socket already initialized');
210
+ return;
211
+ }
212
+
213
+ // Create a single socket connection
214
+ const fullURL = window.location.origin + '/';
215
+ clientLogger.info('connecting to', fullURL);
216
+ this.socket = io(fullURL, {
217
+ autoConnect: true,
218
+ reconnection: true,
219
+ });
220
+
221
+ // Set up connection promise for async operations that depend on connection
222
+ this.connectPromise = new Promise<void>((resolve) => {
223
+ this.resolveConnect = resolve;
224
+ });
225
+
226
+ this.socket.on('connect', () => {
227
+ clientLogger.info('[SocketIO] Connected to server');
228
+ this.isConnected = true;
229
+ this.resolveConnect?.();
230
+
231
+ // Add debug listener for all incoming events
232
+ if (process.env.NODE_ENV === 'development' && this.socket) {
233
+ this.socket.onAny((event, ...args) => {
234
+ clientLogger.debug(`[SocketIO DEBUG] Received event '${event}':`, args);
235
+ });
236
+ }
237
+
238
+ this.emit('connect');
239
+
240
+ // CRITICAL: Ensure this loop remains commented out or removed.
241
+ // this.activeChannelIds.forEach((channelId) => {
242
+ // clientLogger.info(`[SocketIO] 'connect' event: Attempting to re-join active channel ${channelId} (THIS SHOULD NOT HAPPEN AUTOMATICALLY)`);
243
+ // this.joinChannel(channelId);
244
+ // });
245
+ });
246
+
247
+ this.socket.on('unauthorized', (reason: string) => {
248
+ this.emit('unauthorized', reason);
249
+ });
250
+
251
+ this.socket.on('messageBroadcast', (raw: any) => {
252
+ // Server sends objects to others and JSON strings back to the sender.
253
+ const data: MessageBroadcastData =
254
+ typeof raw === 'string'
255
+ ? (() => {
256
+ try {
257
+ return JSON.parse(raw);
258
+ } catch (e) {
259
+ clientLogger.warn(
260
+ '[SocketIO] Failed to parse string messageBroadcast payload:',
261
+ raw
262
+ );
263
+ return {} as any;
264
+ }
265
+ })()
266
+ : (raw as MessageBroadcastData);
267
+
268
+ clientLogger.info(`[SocketIO] Message broadcast received:`, data);
269
+
270
+ // Log the full data structure to understand formats
271
+ const isObj = data && typeof data === 'object';
272
+ const keys = isObj ? Object.keys(data as any) : [];
273
+ clientLogger.debug('[SocketIO] Message broadcast data structure:', {
274
+ keys,
275
+ senderId: (data as any).senderId,
276
+ senderNameType: typeof (data as any).senderName,
277
+ textType: typeof (data as any).text,
278
+ textLength: (data as any).text ? (data as any).text.length : 0,
279
+ hasThought: isObj && 'thought' in (data as any),
280
+ hasActions: isObj && 'actions' in (data as any),
281
+ additionalKeys: keys.filter(
282
+ (k) =>
283
+ ![
284
+ 'senderId',
285
+ 'senderName',
286
+ 'text',
287
+ 'roomId',
288
+ 'createdAt',
289
+ 'source',
290
+ 'thought',
291
+ 'actions',
292
+ ].includes(k)
293
+ ),
294
+ });
295
+
296
+ // Check if this is a message for one of our active channels
297
+ const channelId = (data as any).channelId || (data as any).roomId; // Handle both new and old message format
298
+ if (channelId && this.activeChannelIds.has(channelId)) {
299
+ clientLogger.info(`[SocketIO] Handling message for active channel ${channelId}`);
300
+ // Post the message to the event for UI updates
301
+ this.emit('messageBroadcast', {
302
+ ...(data as any),
303
+ channelId: channelId, // Ensure channelId is always set
304
+ roomId: channelId, // Keep roomId for backward compatibility
305
+ name: (data as any).senderName, // Required for ContentWithUser compatibility
306
+ });
307
+ } else {
308
+ clientLogger.warn(
309
+ `[SocketIO] Received message for inactive channel ${channelId}, active channels:`,
310
+ Array.from(this.activeChannelIds)
311
+ );
312
+ }
313
+ });
314
+
315
+ this.socket.on('messageComplete', (data) => {
316
+ this.emit('messageComplete', data);
317
+ });
318
+
319
+ // Listen for control messages
320
+ this.socket.on('controlMessage', (data) => {
321
+ clientLogger.info(`[SocketIO] Control message received:`, data);
322
+
323
+ // Check if this is for one of our active channels
324
+ const channelId = data.channelId || data.roomId; // Handle both new and old message format
325
+ if (channelId && this.activeChannelIds.has(channelId)) {
326
+ clientLogger.info(`[SocketIO] Handling control message for active channel ${channelId}`);
327
+
328
+ // Emit the control message event
329
+ this.emit('controlMessage', {
330
+ ...data,
331
+ channelId: channelId, // Ensure channelId is always set
332
+ roomId: channelId, // Keep roomId for backward compatibility
333
+ });
334
+ } else {
335
+ clientLogger.warn(
336
+ `[SocketIO] Received control message for inactive channel ${channelId}, active channels:`,
337
+ Array.from(this.activeChannelIds)
338
+ );
339
+ }
340
+ });
341
+
342
+ // Listen for message deletion events
343
+ this.socket.on('messageDeleted', (data) => {
344
+ clientLogger.debug(`[SocketIO] Message deleted event received:`, data);
345
+
346
+ // Check if this is for one of our active channels
347
+ const channelId = data.channelId || data.roomId; // Handle both new and old message format
348
+ if (channelId && this.activeChannelIds.has(channelId)) {
349
+ clientLogger.info(`[SocketIO] Handling message deletion for active channel ${channelId}`);
350
+
351
+ // Emit the message deleted event
352
+ this.emit('messageDeleted', {
353
+ ...data,
354
+ channelId: channelId, // Ensure channelId is always set
355
+ roomId: channelId, // Deprecated: Retained for backward compatibility with older clients
356
+ });
357
+ } else {
358
+ clientLogger.warn(
359
+ `[SocketIO] Received message deleted event for inactive channel ${channelId}, active channels:`,
360
+ Array.from(this.activeChannelIds)
361
+ );
362
+ }
363
+ });
364
+
365
+ // Listen for channel cleared events
366
+ this.socket.on('channelCleared', (data) => {
367
+ clientLogger.info(`[SocketIO] Channel cleared event received:`, data);
368
+
369
+ // Check if this is for one of our active channels
370
+ const channelId = data.channelId || data.roomId; // Handle both new and old message format
371
+ if (channelId && this.activeChannelIds.has(channelId)) {
372
+ clientLogger.info(`[SocketIO] Handling channel cleared for active channel ${channelId}`);
373
+
374
+ // Emit the channel cleared event
375
+ this.emit('channelCleared', {
376
+ ...data,
377
+ channelId: channelId, // Ensure channelId is always set
378
+ roomId: channelId, // Keep roomId for backward compatibility
379
+ });
380
+ } else {
381
+ clientLogger.warn(
382
+ `[SocketIO] Received channel cleared event for inactive channel ${channelId}, active channels:`,
383
+ Array.from(this.activeChannelIds)
384
+ );
385
+ }
386
+ });
387
+
388
+ // Listen for channel deleted events
389
+ this.socket.on('channelDeleted', (data) => {
390
+ clientLogger.info(`[SocketIO] Channel deleted event received:`, data);
391
+
392
+ // Check if this is for one of our active channels
393
+ const channelId = data.channelId || data.roomId; // Handle both new and old message format
394
+ if (channelId && this.activeChannelIds.has(channelId)) {
395
+ clientLogger.info(`[SocketIO] Handling channel deleted for active channel ${channelId}`);
396
+
397
+ // Emit the channel deleted event (same as cleared for now)
398
+ this.emit('channelDeleted', {
399
+ ...data,
400
+ channelId: channelId, // Ensure channelId is always set
401
+ roomId: channelId, // Keep roomId for backward compatibility
402
+ });
403
+ } else {
404
+ clientLogger.warn(
405
+ `[SocketIO] Received channel deleted event for inactive channel ${channelId}, active channels:`,
406
+ Array.from(this.activeChannelIds)
407
+ );
408
+ }
409
+ });
410
+
411
+ this.socket.on('disconnect', (reason) => {
412
+ clientLogger.info(`[SocketIO] Disconnected. Reason: ${reason}`);
413
+ this.isConnected = false;
414
+
415
+ this.emit('disconnect', reason);
416
+
417
+ // Reset connect promise for next connection
418
+ this.connectPromise = new Promise<void>((resolve) => {
419
+ this.resolveConnect = resolve;
420
+ });
421
+
422
+ if (reason === 'io server disconnect') {
423
+ this.socket?.connect();
424
+ }
425
+ });
426
+
427
+ this.socket.on('reconnect_attempt', (attempt) => {
428
+ clientLogger.info('[SocketIO] Reconnect attempt', attempt);
429
+ this.emit('reconnect_attempt', attempt);
430
+ });
431
+
432
+ this.socket.on('reconnect', (attempt) => {
433
+ clientLogger.info(`[SocketIO] Reconnected after ${attempt} attempts`);
434
+ this.emit('reconnect', attempt);
435
+ });
436
+
437
+ this.socket.on('connect_error', (error) => {
438
+ clientLogger.error('[SocketIO] Connection error:', error);
439
+ this.emit('connect_error', error);
440
+ });
441
+
442
+ // Handle log stream events
443
+ this.socket.on('log_stream', (data) => {
444
+ clientLogger.debug('[SocketIO] Log stream data received:', data);
445
+ if (data.type === 'log_entry' && data.payload) {
446
+ this.emit('logStream', data.payload);
447
+ }
448
+ });
449
+
450
+ this.socket.on('log_subscription_confirmed', (data) => {
451
+ clientLogger.info('[SocketIO] Log subscription confirmed:', data);
452
+ this.logStreamSubscribed = data.subscribed;
453
+ });
454
+ }
455
+
456
+ /**
457
+ * Join a channel to receive messages from it
458
+ * @param channelId Channel ID to join
459
+ */
460
+ public async joinChannel(channelId: string): Promise<void> {
461
+ if (!this.socket) {
462
+ clientLogger.error('[SocketIO] Cannot join channel: socket not initialized');
463
+ return;
464
+ }
465
+
466
+ // Wait for connection if needed
467
+ if (!this.isConnected) {
468
+ await this.connectPromise;
469
+ }
470
+
471
+ clientLogger.info(
472
+ `[SocketIO] joinChannel: Attempting to join ${channelId}. Current activeChannelIds before add:`,
473
+ new Set(this.activeChannelIds)
474
+ );
475
+ this.activeChannelIds.add(channelId);
476
+ clientLogger.info(
477
+ `[SocketIO] joinChannel: Joined ${channelId}. Current activeChannelIds after add:`,
478
+ new Set(this.activeChannelIds)
479
+ );
480
+
481
+ this.socket.emit('message', {
482
+ type: SOCKET_MESSAGE_TYPE.ROOM_JOINING,
483
+ payload: {
484
+ channelId: channelId,
485
+ roomId: channelId, // Keep for backward compatibility
486
+ entityId: this.clientEntityId,
487
+ },
488
+ });
489
+
490
+ clientLogger.info(`[SocketIO] Emitted ROOM_JOINING for ${channelId}`);
491
+ }
492
+
493
+ /**
494
+ * @deprecated Use joinChannel instead
495
+ */
496
+ public async joinRoom(channelId: string): Promise<void> {
497
+ return this.joinChannel(channelId);
498
+ }
499
+
500
+ /**
501
+ * Leave a channel to stop receiving messages from it
502
+ * @param channelId Channel ID to leave
503
+ */
504
+ public leaveChannel(channelId: string): void {
505
+ if (!this.socket || !this.isConnected) {
506
+ clientLogger.warn(`[SocketIO] Cannot leave channel ${channelId}: not connected`);
507
+ return;
508
+ }
509
+
510
+ clientLogger.info(
511
+ `[SocketIO] leaveChannel: Attempting to leave ${channelId}. Current activeChannelIds before delete:`,
512
+ new Set(this.activeChannelIds)
513
+ );
514
+ this.activeChannelIds.delete(channelId);
515
+ clientLogger.info(
516
+ `[SocketIO] leaveChannel: Left ${channelId}. Current activeChannelIds after delete:`,
517
+ new Set(this.activeChannelIds)
518
+ );
519
+ // No server-side message for leaving a room in this client's protocol,
520
+ // client just stops listening / tracking.
521
+ }
522
+
523
+ /**
524
+ * @deprecated Use leaveChannel instead
525
+ */
526
+ public leaveRoom(channelId: string): void {
527
+ return this.leaveChannel(channelId);
528
+ }
529
+
530
+ /**
531
+ * Send a message to a specific channel
532
+ * @param message Message text to send
533
+ * @param channelId Channel ID to send the message to
534
+ * @param serverId Server ID to send the message to
535
+ * @param source Source identifier (e.g., 'client_chat')
536
+ * @param attachments Optional media attachments
537
+ * @param messageId Optional message ID for tracking optimistic updates
538
+ */
539
+ public async sendMessage(
540
+ message: string,
541
+ channelId: string,
542
+ serverId: string,
543
+ source: string,
544
+ attachments?: any[],
545
+ messageId?: string,
546
+ metadata?: Record<string, any>
547
+ ): Promise<void> {
548
+ if (!this.socket) {
549
+ clientLogger.error('[SocketIO] Cannot send message: socket not initialized');
550
+ return;
551
+ }
552
+
553
+ // Wait for connection if needed
554
+ if (!this.isConnected) {
555
+ await this.connectPromise;
556
+ }
557
+
558
+ // Use provided messageId or generate a new one
559
+ const finalMessageId = messageId || randomUUID();
560
+
561
+ clientLogger.info(
562
+ `[SocketIO] Sending message to central channel ${channelId} on server ${serverId}`
563
+ );
564
+
565
+ // Emit message to server
566
+ this.socket.emit('message', {
567
+ type: SOCKET_MESSAGE_TYPE.SEND_MESSAGE,
568
+ payload: {
569
+ senderId: this.clientEntityId,
570
+ senderName: USER_NAME,
571
+ message,
572
+ channelId: channelId,
573
+ roomId: channelId, // Keep for backward compatibility
574
+ serverId: serverId, // Client uses serverId, not worldId
575
+ messageId: finalMessageId,
576
+ source,
577
+ attachments,
578
+ metadata,
579
+ },
580
+ });
581
+
582
+ // Note: We no longer broadcast locally - the server will send the message back with the proper ID
583
+ }
584
+
585
+ /**
586
+ * Subscribe to log streaming
587
+ */
588
+ public async subscribeToLogStream(): Promise<void> {
589
+ if (!this.socket) {
590
+ clientLogger.error('[SocketIO] Cannot subscribe to logs: socket not initialized');
591
+ return;
592
+ }
593
+
594
+ // Wait for connection if needed
595
+ if (!this.isConnected) {
596
+ await this.connectPromise;
597
+ }
598
+
599
+ this.socket.emit('subscribe_logs');
600
+ clientLogger.info('[SocketIO] Subscribed to log stream');
601
+ }
602
+
603
+ /**
604
+ * Unsubscribe from log streaming
605
+ */
606
+ public async unsubscribeFromLogStream(): Promise<void> {
607
+ if (!this.socket) {
608
+ clientLogger.error('[SocketIO] Cannot unsubscribe from logs: socket not initialized');
609
+ return;
610
+ }
611
+
612
+ // Wait for connection if needed
613
+ if (!this.isConnected) {
614
+ await this.connectPromise;
615
+ }
616
+
617
+ this.socket.emit('unsubscribe_logs');
618
+ clientLogger.info('[SocketIO] Unsubscribed from log stream');
619
+ }
620
+
621
+ /**
622
+ * Update log stream filters
623
+ */
624
+ public async updateLogStreamFilters(filters: {
625
+ agentName?: string;
626
+ level?: string;
627
+ }): Promise<void> {
628
+ if (!this.socket) {
629
+ clientLogger.error('[SocketIO] Cannot update log filters: socket not initialized');
630
+ return;
631
+ }
632
+
633
+ // Wait for connection if needed
634
+ if (!this.isConnected) {
635
+ await this.connectPromise;
636
+ }
637
+
638
+ this.socket.emit('update_log_filters', filters);
639
+ clientLogger.info('[SocketIO] Updated log stream filters:', filters);
640
+ }
641
+
642
+ /**
643
+ * Check if subscribed to log streaming
644
+ */
645
+ public isLogStreamSubscribed(): boolean {
646
+ return this.logStreamSubscribed;
647
+ }
648
+
649
+ /**
650
+ * Disconnect from the server
651
+ */
652
+ public disconnect(): void {
653
+ if (this.socket) {
654
+ this.socket.disconnect();
655
+ this.socket = null;
656
+ this.isConnected = false;
657
+ this.activeChannelIds.clear();
658
+ this.logStreamSubscribed = false;
659
+ clientLogger.info('[SocketIO] Disconnected from server');
660
+ }
661
+ }
662
+ }
663
+
664
+ export default SocketIOManager;