blockmine 1.22.0 → 1.23.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.
Files changed (102) hide show
  1. package/.claude/settings.json +5 -1
  2. package/.claude/settings.local.json +10 -1
  3. package/CHANGELOG.md +27 -3
  4. package/CLAUDE.md +284 -0
  5. package/README.md +302 -152
  6. package/backend/package-lock.json +681 -9
  7. package/backend/package.json +8 -0
  8. package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -0
  9. package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -0
  10. package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -0
  11. package/backend/prisma/migrations/migration_lock.toml +2 -2
  12. package/backend/prisma/schema.prisma +70 -1
  13. package/backend/src/__tests__/services/BotLifecycleService.test.js +9 -4
  14. package/backend/src/ai/plugin-assistant-system-prompt.md +788 -0
  15. package/backend/src/api/middleware/auth.js +27 -0
  16. package/backend/src/api/middleware/botAccess.js +7 -3
  17. package/backend/src/api/middleware/panelApiAuth.js +135 -0
  18. package/backend/src/api/routes/aiAssistant.js +995 -0
  19. package/backend/src/api/routes/auth.js +669 -633
  20. package/backend/src/api/routes/botCommands.js +107 -0
  21. package/backend/src/api/routes/botGroups.js +165 -0
  22. package/backend/src/api/routes/botHistory.js +108 -0
  23. package/backend/src/api/routes/botPermissions.js +99 -0
  24. package/backend/src/api/routes/botStatus.js +36 -0
  25. package/backend/src/api/routes/botUsers.js +162 -0
  26. package/backend/src/api/routes/bots.js +2451 -2402
  27. package/backend/src/api/routes/eventGraphs.js +4 -1
  28. package/backend/src/api/routes/logs.js +13 -3
  29. package/backend/src/api/routes/panel.js +66 -66
  30. package/backend/src/api/routes/panelApiKeys.js +179 -0
  31. package/backend/src/api/routes/pluginIde.js +1715 -135
  32. package/backend/src/api/routes/plugins.js +376 -219
  33. package/backend/src/api/routes/proxies.js +130 -0
  34. package/backend/src/api/routes/search.js +4 -0
  35. package/backend/src/api/routes/servers.js +20 -3
  36. package/backend/src/api/routes/settings.js +5 -0
  37. package/backend/src/api/routes/system.js +174 -174
  38. package/backend/src/api/routes/traces.js +131 -0
  39. package/backend/src/config/debug.config.js +36 -0
  40. package/backend/src/core/BotHistoryStore.js +180 -0
  41. package/backend/src/core/BotManager.js +14 -4
  42. package/backend/src/core/BotProcess.js +1517 -1092
  43. package/backend/src/core/EventGraphManager.js +37 -123
  44. package/backend/src/core/GraphExecutionEngine.js +977 -321
  45. package/backend/src/core/MessageQueue.js +12 -6
  46. package/backend/src/core/PluginLoader.js +99 -5
  47. package/backend/src/core/PluginManager.js +74 -13
  48. package/backend/src/core/TaskScheduler.js +1 -1
  49. package/backend/src/core/commands/whois.js +1 -1
  50. package/backend/src/core/node-registries/actions.js +70 -0
  51. package/backend/src/core/node-registries/arrays.js +18 -0
  52. package/backend/src/core/node-registries/data.js +1 -1
  53. package/backend/src/core/node-registries/events.js +14 -0
  54. package/backend/src/core/node-registries/logic.js +17 -0
  55. package/backend/src/core/node-registries/strings.js +34 -0
  56. package/backend/src/core/node-registries/type.js +25 -0
  57. package/backend/src/core/nodes/actions/bot_look_at.js +1 -1
  58. package/backend/src/core/nodes/actions/create_command.js +189 -0
  59. package/backend/src/core/nodes/actions/delete_command.js +92 -0
  60. package/backend/src/core/nodes/actions/update_command.js +133 -0
  61. package/backend/src/core/nodes/arrays/join.js +28 -0
  62. package/backend/src/core/nodes/data/cast.js +2 -1
  63. package/backend/src/core/nodes/logic/not.js +22 -0
  64. package/backend/src/core/nodes/strings/starts_with.js +1 -1
  65. package/backend/src/core/nodes/strings/to_lower.js +22 -0
  66. package/backend/src/core/nodes/strings/to_upper.js +22 -0
  67. package/backend/src/core/nodes/type/to_string.js +32 -0
  68. package/backend/src/core/services/BotLifecycleService.js +255 -16
  69. package/backend/src/core/services/CommandExecutionService.js +430 -351
  70. package/backend/src/core/services/DebugSessionManager.js +347 -0
  71. package/backend/src/core/services/GraphCollaborationManager.js +501 -0
  72. package/backend/src/core/services/MinecraftBotManager.js +259 -0
  73. package/backend/src/core/services/MinecraftViewerService.js +216 -0
  74. package/backend/src/core/services/TraceCollectorService.js +545 -0
  75. package/backend/src/core/system/RuntimeCommandRegistry.js +116 -0
  76. package/backend/src/core/system/Transport.js +0 -4
  77. package/backend/src/core/validation/nodeSchemas.js +6 -6
  78. package/backend/src/real-time/botApi/handlers/graphHandlers.js +2 -2
  79. package/backend/src/real-time/botApi/handlers/graphWebSocketHandlers.js +1 -1
  80. package/backend/src/real-time/botApi/utils.js +11 -0
  81. package/backend/src/real-time/panelNamespace.js +387 -0
  82. package/backend/src/real-time/presence.js +7 -2
  83. package/backend/src/real-time/socketHandler.js +395 -4
  84. package/backend/src/server.js +18 -0
  85. package/frontend/dist/assets/index-B1serztM.js +11210 -0
  86. package/frontend/dist/assets/index-t6K1u4OV.css +32 -0
  87. package/frontend/dist/index.html +2 -2
  88. package/frontend/package-lock.json +9437 -0
  89. package/frontend/package.json +8 -0
  90. package/package.json +2 -2
  91. package/screen/console.png +0 -0
  92. package/screen/dashboard.png +0 -0
  93. package/screen/graph_collabe.png +0 -0
  94. package/screen/graph_live_debug.png +0 -0
  95. package/screen/management_command.png +0 -0
  96. package/screen/node_debug_trace.png +0 -0
  97. package/screen/plugin_/320/276/320/261/320/267/320/276/321/200.png +0 -0
  98. package/screen/websocket.png +0 -0
  99. package/screen//320/275/320/260/321/201/321/202/321/200/320/276/320/271/320/272/320/270_/320/276/321/202/320/264/320/265/320/273/321/214/320/275/321/213/321/205_/320/272/320/276/320/274/320/260/320/275/320/264_/320/272/320/260/320/266/320/264/321/203_/320/272/320/276/320/274/320/260/320/275/320/273/320/264/321/203_/320/274/320/276/320/266/320/275/320/276_/320/275/320/260/321/201/321/202/321/200/320/260/320/270/320/262/320/260/321/202/321/214.png +0 -0
  100. package/screen//320/277/320/273/320/260/320/275/320/270/321/200/320/276/320/262/321/211/320/270/320/272_/320/274/320/276/320/266/320/275/320/276_/320/267/320/260/320/264/320/260/320/262/320/260/321/202/321/214_/320/264/320/265/320/271/321/201/321/202/320/262/320/270/321/217_/320/277/320/276_/320/262/321/200/320/265/320/274/320/265/320/275/320/270.png +0 -0
  101. package/frontend/dist/assets/index-CfTo92bP.css +0 -1
  102. package/frontend/dist/assets/index-CiFD5X9Z.js +0 -8344
@@ -0,0 +1,501 @@
1
+ /**
2
+ * GraphCollaborationManager
3
+ * Управляет совместным редактированием графов в реальном времени
4
+ */
5
+ class GraphCollaborationManager {
6
+ constructor() {
7
+ // Структура: Map<graphId, GraphRoom>
8
+ // GraphRoom = { users: Map<socketId, UserInfo>, io: Socket.IO namespace }
9
+ this.rooms = new Map();
10
+ this.io = null;
11
+ }
12
+
13
+ /**
14
+ * Инициализация с Socket.IO instance
15
+ */
16
+ initialize(io) {
17
+ this.io = io;
18
+ console.log('[GraphCollaboration] Manager initialized');
19
+ }
20
+
21
+ /**
22
+ * Пользователь присоединяется к графу
23
+ */
24
+ joinGraph(socket, { botId, graphId, username, userId, debugMode = 'normal' }) {
25
+ const roomKey = this.getRoomKey(botId, graphId);
26
+
27
+ // Создаем комнату если не существует
28
+ if (!this.rooms.has(roomKey)) {
29
+ this.rooms.set(roomKey, {
30
+ botId,
31
+ graphId,
32
+ users: new Map(),
33
+ // Храним текущее состояние графа для синхронизации при переподключении
34
+ graphState: {
35
+ nodes: [],
36
+ edges: [],
37
+ lastUpdate: null,
38
+ },
39
+ });
40
+ console.log(`[GraphCollaboration] Created room for bot ${botId}, graph ${graphId}`);
41
+ }
42
+
43
+ const room = this.rooms.get(roomKey);
44
+
45
+ // Добавляем пользователя в комнату
46
+ const userInfo = {
47
+ socketId: socket.id,
48
+ username: username || 'Anonymous',
49
+ userId: userId || null,
50
+ cursor: null, // { x, y }
51
+ selectedNodes: [], // [nodeId, ...]
52
+ color: this.generateUserColor(socket.id), // Уникальный цвет пользователя
53
+ debugMode: debugMode || 'normal', // 'normal' | 'trace' | 'live'
54
+ joinedAt: Date.now(),
55
+ };
56
+
57
+ room.users.set(socket.id, userInfo);
58
+
59
+ // Присоединяем socket к комнате
60
+ socket.join(roomKey);
61
+
62
+ console.log(`[GraphCollaboration] User ${username} (${socket.id}) joined bot ${botId}, graph ${graphId} in ${debugMode} mode. Total users: ${room.users.size}`);
63
+
64
+ // Отправляем текущее состояние новому пользователю
65
+ socket.emit('collab:state', {
66
+ users: Array.from(room.users.values()).map(u => ({
67
+ socketId: u.socketId,
68
+ username: u.username,
69
+ userId: u.userId,
70
+ color: u.color,
71
+ cursor: u.cursor,
72
+ selectedNodes: u.selectedNodes,
73
+ debugMode: u.debugMode,
74
+ })),
75
+ // Отправляем текущее состояние графа (если есть другие пользователи)
76
+ graphState: room.users.size > 1 ? room.graphState : null,
77
+ });
78
+
79
+ // Уведомляем остальных о новом пользователе
80
+ socket.to(roomKey).emit('collab:user-joined', {
81
+ user: {
82
+ socketId: userInfo.socketId,
83
+ username: userInfo.username,
84
+ userId: userInfo.userId,
85
+ color: userInfo.color,
86
+ debugMode: userInfo.debugMode,
87
+ }
88
+ });
89
+
90
+ return room;
91
+ }
92
+
93
+ /**
94
+ * Пользователь покидает граф
95
+ */
96
+ leaveGraph(socket, { botId, graphId }) {
97
+ const roomKey = this.getRoomKey(botId, graphId);
98
+ const room = this.rooms.get(roomKey);
99
+
100
+ if (!room) return;
101
+
102
+ const userInfo = room.users.get(socket.id);
103
+ if (!userInfo) return;
104
+
105
+ room.users.delete(socket.id);
106
+ socket.leave(roomKey);
107
+
108
+ console.log(`[GraphCollaboration] User ${userInfo.username} (${socket.id}) left bot ${botId}, graph ${graphId}. Remaining users: ${room.users.size}`);
109
+
110
+ // Уведомляем остальных
111
+ socket.to(roomKey).emit('collab:user-left', {
112
+ socketId: socket.id,
113
+ username: userInfo.username,
114
+ });
115
+
116
+ // Удаляем комнату если пустая
117
+ if (room.users.size === 0) {
118
+ this.rooms.delete(roomKey);
119
+ console.log(`[GraphCollaboration] Removed empty room for bot ${botId}, graph ${graphId}`);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Обновление позиции курсора
125
+ */
126
+ updateCursor(socket, { botId, graphId, x, y }) {
127
+ const roomKey = this.getRoomKey(botId, graphId);
128
+ const room = this.rooms.get(roomKey);
129
+
130
+ if (!room) return;
131
+
132
+ const userInfo = room.users.get(socket.id);
133
+ if (!userInfo) return;
134
+
135
+ userInfo.cursor = { x, y };
136
+
137
+ // Broadcast другим пользователям
138
+ socket.to(roomKey).emit('collab:cursor-move', {
139
+ socketId: socket.id,
140
+ username: userInfo.username,
141
+ color: userInfo.color,
142
+ x,
143
+ y,
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Обновление режима отладки пользователя
149
+ */
150
+ updateDebugMode(socket, { botId, graphId, debugMode }) {
151
+ const roomKey = this.getRoomKey(botId, graphId);
152
+ const room = this.rooms.get(roomKey);
153
+
154
+ if (!room) return;
155
+
156
+ const userInfo = room.users.get(socket.id);
157
+ if (!userInfo) return;
158
+
159
+ userInfo.debugMode = debugMode;
160
+
161
+ console.log(`[GraphCollaboration] User ${userInfo.username} switched to ${debugMode} mode`);
162
+
163
+ // Broadcast другим пользователям
164
+ socket.to(roomKey).emit('collab:mode-changed', {
165
+ socketId: socket.id,
166
+ username: userInfo.username,
167
+ debugMode,
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Обновление выбранных нод
173
+ */
174
+ updateSelectedNodes(socket, { botId, graphId, nodeIds }) {
175
+ const roomKey = this.getRoomKey(botId, graphId);
176
+ const room = this.rooms.get(roomKey);
177
+
178
+ if (!room) return;
179
+
180
+ const userInfo = room.users.get(socket.id);
181
+ if (!userInfo) return;
182
+
183
+ userInfo.selectedNodes = nodeIds || [];
184
+
185
+ // Broadcast другим пользователям
186
+ socket.to(roomKey).emit('collab:selection-changed', {
187
+ socketId: socket.id,
188
+ username: userInfo.username,
189
+ color: userInfo.color,
190
+ nodeIds: userInfo.selectedNodes,
191
+ });
192
+ }
193
+
194
+ /**
195
+ * Начало создания соединения
196
+ */
197
+ startConnection(socket, { botId, graphId, fromX, fromY, fromNodeId, fromHandleId }) {
198
+ const roomKey = this.getRoomKey(botId, graphId);
199
+ const userInfo = this.getUserInfo(socket, botId, graphId);
200
+
201
+ if (!userInfo) return;
202
+
203
+ // Broadcast начало соединения
204
+ socket.to(roomKey).emit('collab:connection-start', {
205
+ socketId: socket.id,
206
+ username: userInfo.username,
207
+ color: userInfo.color,
208
+ fromX,
209
+ fromY,
210
+ fromNodeId,
211
+ fromHandleId,
212
+ });
213
+ }
214
+
215
+ /**
216
+ * Обновление позиции соединения во время тяжения
217
+ */
218
+ updateConnection(socket, { botId, graphId, toX, toY }) {
219
+ const roomKey = this.getRoomKey(botId, graphId);
220
+ const userInfo = this.getUserInfo(socket, botId, graphId);
221
+
222
+ if (!userInfo) return;
223
+
224
+ // Broadcast обновление позиции
225
+ socket.to(roomKey).emit('collab:connection-update', {
226
+ socketId: socket.id,
227
+ toX,
228
+ toY,
229
+ });
230
+ }
231
+
232
+ /**
233
+ * Завершение создания соединения
234
+ */
235
+ endConnection(socket, { botId, graphId }) {
236
+ const roomKey = this.getRoomKey(botId, graphId);
237
+
238
+ // Broadcast завершение соединения
239
+ socket.to(roomKey).emit('collab:connection-end', {
240
+ socketId: socket.id,
241
+ });
242
+ }
243
+
244
+ /**
245
+ * Инициализировать состояние графа (вызывается первым пользователем после загрузки из БД)
246
+ */
247
+ initializeGraphState(socket, { botId, graphId, nodes, edges }) {
248
+ const roomKey = this.getRoomKey(botId, graphId);
249
+ const room = this.rooms.get(roomKey);
250
+
251
+ if (!room) return;
252
+
253
+ room.graphState = {
254
+ nodes: nodes || [],
255
+ edges: edges || [],
256
+ lastUpdate: Date.now(),
257
+ };
258
+
259
+ console.log(`[GraphCollaboration] Graph state initialized for bot ${botId}, graph ${graphId}: ${nodes?.length} nodes, ${edges?.length} edges`);
260
+ }
261
+
262
+ /**
263
+ * Broadcast изменения нод (движение, создание, удаление)
264
+ */
265
+ broadcastNodeChange(socket, { botId, graphId, type, data }) {
266
+ const roomKey = this.getRoomKey(botId, graphId);
267
+ const room = this.rooms.get(roomKey);
268
+
269
+ if (room) {
270
+ // Обновляем состояние графа в комнате
271
+ switch (type) {
272
+ case 'create':
273
+ // data = { node }
274
+ if (data.node) {
275
+ room.graphState.nodes.push(data.node);
276
+ }
277
+ break;
278
+ case 'delete':
279
+ // data = { nodeIds: [] }
280
+ if (data.nodeIds && Array.isArray(data.nodeIds)) {
281
+ room.graphState.nodes = room.graphState.nodes.filter(n => !data.nodeIds.includes(n.id));
282
+ // Удаляем связанные edges
283
+ room.graphState.edges = room.graphState.edges.filter(
284
+ c => !data.nodeIds.includes(c.source) && !data.nodeIds.includes(c.target)
285
+ );
286
+ }
287
+ break;
288
+ case 'update':
289
+ // data = { nodeId, nodeData }
290
+ if (data.nodeId && data.nodeData) {
291
+ const nodeIndex = room.graphState.nodes.findIndex(n => n.id === data.nodeId);
292
+ if (nodeIndex !== -1) {
293
+ room.graphState.nodes[nodeIndex].data = {
294
+ ...room.graphState.nodes[nodeIndex].data,
295
+ ...data.nodeData
296
+ };
297
+ }
298
+ }
299
+ break;
300
+ case 'move':
301
+ // data = [{ id, position }]
302
+ if (Array.isArray(data)) {
303
+ data.forEach(change => {
304
+ const nodeIndex = room.graphState.nodes.findIndex(n => n.id === change.id);
305
+ if (nodeIndex !== -1) {
306
+ room.graphState.nodes[nodeIndex].position = change.position;
307
+ }
308
+ });
309
+ }
310
+ break;
311
+ }
312
+ room.graphState.lastUpdate = Date.now();
313
+ }
314
+
315
+ // Отправляем всем кроме отправителя
316
+ socket.to(roomKey).emit('collab:node-changed', {
317
+ type, // 'move' | 'create' | 'delete' | 'update'
318
+ data,
319
+ username: this.getUserInfo(socket, botId, graphId)?.username,
320
+ });
321
+ }
322
+
323
+ /**
324
+ * Broadcast изменения связей (создание, удаление)
325
+ */
326
+ broadcastEdgeChange(socket, { botId, graphId, type, data }) {
327
+ const roomKey = this.getRoomKey(botId, graphId);
328
+ const room = this.rooms.get(roomKey);
329
+
330
+ if (room) {
331
+ // Обновляем состояние графа в комнате
332
+ switch (type) {
333
+ case 'create':
334
+ // data = { edge }
335
+ if (data.edge) {
336
+ room.graphState.edges.push(data.edge);
337
+ }
338
+ break;
339
+ case 'delete':
340
+ // data = { edgeIds: [] }
341
+ if (data.edgeIds && Array.isArray(data.edgeIds)) {
342
+ room.graphState.edges = room.graphState.edges.filter(
343
+ c => !data.edgeIds.includes(c.id)
344
+ );
345
+ }
346
+ break;
347
+ }
348
+ room.graphState.lastUpdate = Date.now();
349
+ }
350
+
351
+ socket.to(roomKey).emit('collab:edge-changed', {
352
+ type, // 'create' | 'delete'
353
+ data,
354
+ username: this.getUserInfo(socket, botId, graphId)?.username,
355
+ });
356
+ }
357
+
358
+ /**
359
+ * Broadcast hot reload графа
360
+ */
361
+ broadcastGraphReloaded(botId, graphId) {
362
+ const roomKey = this.getRoomKey(botId, graphId);
363
+
364
+ if (this.io) {
365
+ this.io.to(roomKey).emit('collab:graph-reloaded', {
366
+ botId,
367
+ graphId,
368
+ timestamp: Date.now(),
369
+ });
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Получить информацию о пользователе
375
+ */
376
+ getUserInfo(socket, botId, graphId) {
377
+ const roomKey = this.getRoomKey(botId, graphId);
378
+ const room = this.rooms.get(roomKey);
379
+ return room?.users.get(socket.id);
380
+ }
381
+
382
+ /**
383
+ * Получить всех пользователей в комнате
384
+ */
385
+ getRoomUsers(botId, graphId) {
386
+ const roomKey = this.getRoomKey(botId, graphId);
387
+ const room = this.rooms.get(roomKey);
388
+
389
+ if (!room) return [];
390
+
391
+ return Array.from(room.users.values()).map(u => ({
392
+ socketId: u.socketId,
393
+ username: u.username,
394
+ userId: u.userId,
395
+ color: u.color,
396
+ cursor: u.cursor,
397
+ selectedNodes: u.selectedNodes,
398
+ debugMode: u.debugMode,
399
+ }));
400
+ }
401
+
402
+ /**
403
+ * Удалить пользователя из всех комнат (при disconnect)
404
+ */
405
+ removeUserFromAllRooms(socket) {
406
+ for (const [roomKey, room] of this.rooms.entries()) {
407
+ const userInfo = room.users.get(socket.id);
408
+ if (userInfo) {
409
+ room.users.delete(socket.id);
410
+
411
+ socket.to(roomKey).emit('collab:user-left', {
412
+ socketId: socket.id,
413
+ username: userInfo.username,
414
+ });
415
+
416
+ console.log(`[GraphCollaboration] User ${userInfo.username} (${socket.id}) disconnected from room ${roomKey}`);
417
+
418
+ if (room.users.size === 0) {
419
+ this.rooms.delete(roomKey);
420
+ console.log(`[GraphCollaboration] Removed empty room ${roomKey}`);
421
+ }
422
+ }
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Генерация ключа комнаты
428
+ */
429
+ getRoomKey(botId, graphId) {
430
+ return `graph:${botId}:${graphId}`;
431
+ }
432
+
433
+ /**
434
+ * Генерация уникального цвета для пользователя
435
+ */
436
+ generateUserColor(socketId) {
437
+ const colors = [
438
+ '#FF6B6B', // Red
439
+ '#4ECDC4', // Cyan
440
+ '#45B7D1', // Blue
441
+ '#FFA07A', // Light Salmon
442
+ '#98D8C8', // Mint
443
+ '#F7DC6F', // Yellow
444
+ '#BB8FCE', // Purple
445
+ '#85C1E2', // Sky Blue
446
+ '#F8B739', // Orange
447
+ '#52C785', // Green
448
+ ];
449
+
450
+ // Простой хеш для консистентности цвета
451
+ const hash = socketId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
452
+ return colors[hash % colors.length];
453
+ }
454
+
455
+ /**
456
+ * Получить статистику
457
+ */
458
+ getStats() {
459
+ const stats = {
460
+ totalRooms: this.rooms.size,
461
+ totalUsers: 0,
462
+ rooms: [],
463
+ };
464
+
465
+ for (const [roomKey, room] of this.rooms.entries()) {
466
+ stats.totalUsers += room.users.size;
467
+ stats.rooms.push({
468
+ roomKey,
469
+ botId: room.botId,
470
+ graphId: room.graphId,
471
+ userCount: room.users.size,
472
+ users: Array.from(room.users.values()).map(u => u.username),
473
+ });
474
+ }
475
+
476
+ return stats;
477
+ }
478
+ }
479
+
480
+ let globalCollaborationManager = null;
481
+
482
+ function initializeCollaborationManager(io) {
483
+ if (!globalCollaborationManager) {
484
+ globalCollaborationManager = new GraphCollaborationManager();
485
+ globalCollaborationManager.initialize(io);
486
+ }
487
+ return globalCollaborationManager;
488
+ }
489
+
490
+ function getGlobalCollaborationManager() {
491
+ if (!globalCollaborationManager) {
492
+ throw new Error('GraphCollaborationManager not initialized! Call initializeCollaborationManager(io) first.');
493
+ }
494
+ return globalCollaborationManager;
495
+ }
496
+
497
+ module.exports = {
498
+ GraphCollaborationManager,
499
+ initializeCollaborationManager,
500
+ getGlobalCollaborationManager,
501
+ };