blockmine 1.22.0 → 1.23.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.
Files changed (108) hide show
  1. package/.claude/agents/code-architect.md +34 -0
  2. package/.claude/agents/code-explorer.md +51 -0
  3. package/.claude/agents/code-reviewer.md +46 -0
  4. package/.claude/commands/feature-dev.md +125 -0
  5. package/.claude/settings.json +5 -1
  6. package/.claude/settings.local.json +12 -1
  7. package/.claude/skills/frontend-design/SKILL.md +42 -0
  8. package/CHANGELOG.md +32 -1
  9. package/README.md +302 -152
  10. package/backend/package-lock.json +681 -9
  11. package/backend/package.json +8 -0
  12. package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -0
  13. package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -0
  14. package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -0
  15. package/backend/prisma/schema.prisma +70 -1
  16. package/backend/src/__tests__/services/BotLifecycleService.test.js +9 -4
  17. package/backend/src/ai/plugin-assistant-system-prompt.md +788 -0
  18. package/backend/src/api/middleware/auth.js +27 -0
  19. package/backend/src/api/middleware/botAccess.js +7 -3
  20. package/backend/src/api/middleware/panelApiAuth.js +135 -0
  21. package/backend/src/api/routes/aiAssistant.js +995 -0
  22. package/backend/src/api/routes/auth.js +90 -54
  23. package/backend/src/api/routes/botCommands.js +107 -0
  24. package/backend/src/api/routes/botGroups.js +165 -0
  25. package/backend/src/api/routes/botHistory.js +108 -0
  26. package/backend/src/api/routes/botPermissions.js +99 -0
  27. package/backend/src/api/routes/botStatus.js +36 -0
  28. package/backend/src/api/routes/botUsers.js +162 -0
  29. package/backend/src/api/routes/bots.js +108 -59
  30. package/backend/src/api/routes/eventGraphs.js +4 -1
  31. package/backend/src/api/routes/logs.js +13 -3
  32. package/backend/src/api/routes/panel.js +3 -3
  33. package/backend/src/api/routes/panelApiKeys.js +179 -0
  34. package/backend/src/api/routes/pluginIde.js +1715 -135
  35. package/backend/src/api/routes/plugins.js +170 -13
  36. package/backend/src/api/routes/proxies.js +130 -0
  37. package/backend/src/api/routes/search.js +4 -0
  38. package/backend/src/api/routes/servers.js +20 -3
  39. package/backend/src/api/routes/settings.js +5 -0
  40. package/backend/src/api/routes/system.js +3 -3
  41. package/backend/src/api/routes/traces.js +131 -0
  42. package/backend/src/config/debug.config.js +36 -0
  43. package/backend/src/core/BotHistoryStore.js +180 -0
  44. package/backend/src/core/BotManager.js +14 -4
  45. package/backend/src/core/BotProcess.js +1517 -1092
  46. package/backend/src/core/EventGraphManager.js +194 -280
  47. package/backend/src/core/GraphExecutionEngine.js +1004 -321
  48. package/backend/src/core/MessageQueue.js +12 -6
  49. package/backend/src/core/PluginLoader.js +99 -5
  50. package/backend/src/core/PluginManager.js +74 -13
  51. package/backend/src/core/TaskScheduler.js +1 -1
  52. package/backend/src/core/commands/whois.js +1 -1
  53. package/backend/src/core/node-registries/actions.js +72 -2
  54. package/backend/src/core/node-registries/arrays.js +18 -0
  55. package/backend/src/core/node-registries/data.js +1 -1
  56. package/backend/src/core/node-registries/events.js +14 -0
  57. package/backend/src/core/node-registries/logic.js +17 -0
  58. package/backend/src/core/node-registries/strings.js +34 -0
  59. package/backend/src/core/node-registries/type.js +25 -0
  60. package/backend/src/core/nodes/actions/bot_look_at.js +1 -1
  61. package/backend/src/core/nodes/actions/create_command.js +189 -0
  62. package/backend/src/core/nodes/actions/delete_command.js +92 -0
  63. package/backend/src/core/nodes/actions/http_request.js +23 -4
  64. package/backend/src/core/nodes/actions/send_message.js +2 -12
  65. package/backend/src/core/nodes/actions/update_command.js +133 -0
  66. package/backend/src/core/nodes/arrays/join.js +28 -0
  67. package/backend/src/core/nodes/data/cast.js +2 -1
  68. package/backend/src/core/nodes/data/string_literal.js +2 -13
  69. package/backend/src/core/nodes/logic/not.js +22 -0
  70. package/backend/src/core/nodes/strings/starts_with.js +1 -1
  71. package/backend/src/core/nodes/strings/to_lower.js +22 -0
  72. package/backend/src/core/nodes/strings/to_upper.js +22 -0
  73. package/backend/src/core/nodes/type/to_string.js +32 -0
  74. package/backend/src/core/services/BotLifecycleService.js +835 -596
  75. package/backend/src/core/services/CommandExecutionService.js +430 -351
  76. package/backend/src/core/services/DebugSessionManager.js +347 -0
  77. package/backend/src/core/services/GraphCollaborationManager.js +501 -0
  78. package/backend/src/core/services/MinecraftBotManager.js +259 -0
  79. package/backend/src/core/services/MinecraftViewerService.js +216 -0
  80. package/backend/src/core/services/TraceCollectorService.js +545 -0
  81. package/backend/src/core/system/RuntimeCommandRegistry.js +116 -0
  82. package/backend/src/core/system/Transport.js +0 -4
  83. package/backend/src/core/validation/nodeSchemas.js +6 -6
  84. package/backend/src/real-time/botApi/handlers/graphHandlers.js +2 -2
  85. package/backend/src/real-time/botApi/handlers/graphWebSocketHandlers.js +1 -1
  86. package/backend/src/real-time/botApi/utils.js +11 -0
  87. package/backend/src/real-time/panelNamespace.js +387 -0
  88. package/backend/src/real-time/presence.js +7 -2
  89. package/backend/src/real-time/socketHandler.js +395 -4
  90. package/backend/src/server.js +18 -0
  91. package/frontend/dist/assets/index-DqzDkFsP.js +11210 -0
  92. package/frontend/dist/assets/index-t6K1u4OV.css +32 -0
  93. package/frontend/dist/index.html +2 -2
  94. package/frontend/package-lock.json +9437 -0
  95. package/frontend/package.json +8 -0
  96. package/package.json +2 -2
  97. package/screen/console.png +0 -0
  98. package/screen/dashboard.png +0 -0
  99. package/screen/graph_collabe.png +0 -0
  100. package/screen/graph_live_debug.png +0 -0
  101. package/screen/management_command.png +0 -0
  102. package/screen/node_debug_trace.png +0 -0
  103. package/screen/plugin_/320/276/320/261/320/267/320/276/321/200.png +0 -0
  104. package/screen/websocket.png +0 -0
  105. 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
  106. 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
  107. package/frontend/dist/assets/index-CfTo92bP.css +0 -1
  108. package/frontend/dist/assets/index-CiFD5X9Z.js +0 -8344
@@ -96,7 +96,7 @@ async function handleCallGraph(socket, payload) {
96
96
  }, 30000);
97
97
 
98
98
  try {
99
- await botManager.eventGraphManager.executeGraph(
99
+ await botManager.eventGraphManager.executeGraphInChildProcess(
100
100
  socket.botId,
101
101
  'websocket_call',
102
102
  {
@@ -7,8 +7,19 @@
7
7
  */
8
8
  function broadcastToApiClients(io, botId, eventName, data) {
9
9
  try {
10
+ // Broadcast to bot-api namespace
10
11
  const apiNamespace = io.of('/bot-api');
11
12
  apiNamespace.to(`bot_${botId}`).emit(eventName, data);
13
+
14
+ // Also broadcast to Panel namespace subscribers
15
+ const panelNamespace = io.of('/panel');
16
+ const panelChannel = `bots:${botId}:events`;
17
+ panelNamespace.to(panelChannel).emit(panelChannel, {
18
+ botId,
19
+ eventType: eventName,
20
+ data,
21
+ timestamp: new Date().toISOString()
22
+ });
12
23
  } catch (error) {
13
24
  console.error('[Bot API] Ошибка broadcast:', error);
14
25
  }
@@ -0,0 +1,387 @@
1
+ const bcrypt = require('bcryptjs');
2
+ const prisma = require('../lib/prisma');
3
+ const { botManager } = require('../core/services');
4
+
5
+ /**
6
+ * Аутентификация Panel API Key для WebSocket
7
+ */
8
+ async function authenticateSocket(socket, next) {
9
+ const token = socket.handshake.auth?.token;
10
+
11
+ if (!token) {
12
+ return next(new Error('API ключ не предоставлен'));
13
+ }
14
+
15
+ try {
16
+ const allKeys = await prisma.panelApiKey.findMany({
17
+ where: {
18
+ isActive: true,
19
+ OR: [
20
+ { expiresAt: null },
21
+ { expiresAt: { gt: new Date() } }
22
+ ]
23
+ },
24
+ include: {
25
+ user: {
26
+ include: {
27
+ role: true,
28
+ botAccess: {
29
+ include: {
30
+ bot: true
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ });
37
+
38
+ let matchedKey = null;
39
+ for (const keyRecord of allKeys) {
40
+ if (await bcrypt.compare(token, keyRecord.keyHash)) {
41
+ matchedKey = keyRecord;
42
+ break;
43
+ }
44
+ }
45
+
46
+ if (!matchedKey) {
47
+ return next(new Error('Неверный API ключ'));
48
+ }
49
+
50
+ await prisma.panelApiKey.update({
51
+ where: { id: matchedKey.id },
52
+ data: { lastUsedAt: new Date() }
53
+ });
54
+
55
+ let permissions;
56
+ try {
57
+ if (matchedKey.customScopes) {
58
+ permissions = JSON.parse(matchedKey.customScopes);
59
+ } else {
60
+ permissions = JSON.parse(matchedKey.user.role.permissions);
61
+ }
62
+ } catch (parseError) {
63
+ console.error('Ошибка парсинга прав доступа:', parseError);
64
+ return next(new Error('Ошибка обработки прав доступа'));
65
+ }
66
+
67
+ let availableBots;
68
+ if (matchedKey.user.allBots) {
69
+ availableBots = await prisma.bot.findMany({
70
+ select: { id: true, username: true }
71
+ });
72
+ } else {
73
+ availableBots = matchedKey.user.botAccess.map(access => ({
74
+ id: access.bot.id,
75
+ username: access.bot.username
76
+ }));
77
+ }
78
+
79
+ socket.user = {
80
+ id: matchedKey.user.id,
81
+ userId: matchedKey.user.id,
82
+ uuid: matchedKey.user.uuid,
83
+ username: matchedKey.user.username,
84
+ roleId: matchedKey.user.roleId,
85
+ roleName: matchedKey.user.role.name,
86
+ permissions,
87
+ allBots: matchedKey.user.allBots,
88
+ availableBotIds: availableBots.map(b => b.id)
89
+ };
90
+
91
+ socket.apiKey = {
92
+ id: matchedKey.id,
93
+ name: matchedKey.name,
94
+ prefix: matchedKey.prefix
95
+ };
96
+
97
+ next();
98
+ } catch (err) {
99
+ console.error('Ошибка аутентификации Panel WebSocket:', err);
100
+ next(new Error('Ошибка аутентификации'));
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Проверка доступа к боту
106
+ */
107
+ function canAccessBot(socket, botId) {
108
+ if (socket.user.allBots) return true;
109
+ return socket.user.availableBotIds.includes(botId);
110
+ }
111
+
112
+ /**
113
+ * Проверка прав доступа
114
+ */
115
+ function hasPermission(socket, permission) {
116
+ if (!socket.user || !Array.isArray(socket.user.permissions)) {
117
+ return false;
118
+ }
119
+ return socket.user.permissions.includes('*') || socket.user.permissions.includes(permission);
120
+ }
121
+
122
+ /**
123
+ * Инициализация Panel namespace
124
+ */
125
+ function initializePanelNamespace(io) {
126
+ const panelNamespace = io.of('/panel');
127
+
128
+ panelNamespace.use(authenticateSocket);
129
+
130
+ panelNamespace.on('connection', (socket) => {
131
+ console.log(`[Panel WS] Connected: ${socket.user.username} (${socket.apiKey.name})`);
132
+
133
+ socket.emit('authenticated', {
134
+ user: {
135
+ id: socket.user.id,
136
+ username: socket.user.username,
137
+ role: socket.user.roleName,
138
+ permissions: socket.user.permissions
139
+ },
140
+ apiKey: {
141
+ id: socket.apiKey.id,
142
+ name: socket.apiKey.name,
143
+ prefix: socket.apiKey.prefix
144
+ },
145
+ availableBots: socket.user.availableBotIds
146
+ });
147
+
148
+ // ========== SUBSCRIPTIONS ==========
149
+
150
+ /**
151
+ * Подписка на каналы событий
152
+ */
153
+ socket.on('subscribe', async ({ channel }, callback) => {
154
+ try {
155
+ // Парсим канал: "bots:status" или "bots:1:events"
156
+ const parts = channel.split(':');
157
+ const resource = parts[0];
158
+
159
+ if (resource === 'bots') {
160
+ if (parts.length === 2 && parts[1] === 'status') {
161
+ socket.join('bots:status');
162
+ if (callback) callback({ success: true, channel });
163
+ } else if (parts.length >= 3) {
164
+ const botId = parseInt(parts[1]);
165
+ const eventType = parts[2];
166
+
167
+ if (!canAccessBot(socket, botId)) {
168
+ if (callback) callback({
169
+ success: false,
170
+ error: 'Нет доступа к этому боту'
171
+ });
172
+ return;
173
+ }
174
+
175
+ socket.join(channel);
176
+ if (callback) callback({ success: true, channel });
177
+ }
178
+ } else if (resource === 'system') {
179
+ socket.join(channel);
180
+ if (callback) callback({ success: true, channel });
181
+ } else {
182
+ if (callback) callback({
183
+ success: false,
184
+ error: 'Неизвестный канал'
185
+ });
186
+ }
187
+ } catch (error) {
188
+ console.error('[Panel WS] Ошибка подписки:', error);
189
+ if (callback) callback({
190
+ success: false,
191
+ error: error.message
192
+ });
193
+ }
194
+ });
195
+
196
+ /**
197
+ * Отписка от каналов
198
+ */
199
+ socket.on('unsubscribe', ({ channel }, callback) => {
200
+ socket.leave(channel);
201
+ if (callback) callback({ success: true, channel });
202
+ });
203
+
204
+ // ========== ACTIONS ==========
205
+
206
+ /**
207
+ * Отправка сообщения от бота
208
+ */
209
+ socket.on('action:bot:send_message', async ({ botId, message, chatType, recipient }, callback) => {
210
+ try {
211
+ if (!canAccessBot(socket, botId)) {
212
+ if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
213
+ return;
214
+ }
215
+
216
+ if (!hasPermission(socket, 'bot:interact')) {
217
+ if (callback) callback({ success: false, error: 'Нет прав: bot:interact' });
218
+ return;
219
+ }
220
+
221
+ const bot = botManager.getBotInstance(botId);
222
+ if (!bot) {
223
+ if (callback) callback({ success: false, error: 'Бот не найден или оффлайн' });
224
+ return;
225
+ }
226
+
227
+ const payload = {
228
+ chatType: chatType || 'chat',
229
+ message,
230
+ ...(recipient && { recipient })
231
+ };
232
+
233
+ await bot.sendMessage(payload);
234
+
235
+ if (callback) callback({ success: true });
236
+ } catch (error) {
237
+ console.error('[Panel WS] Ошибка отправки сообщения:', error);
238
+ if (callback) callback({ success: false, error: error.message });
239
+ }
240
+ });
241
+
242
+ /**
243
+ * Выполнение команды
244
+ */
245
+ socket.on('action:bot:execute_command', async ({ botId, username, command, args }, callback) => {
246
+ try {
247
+ if (!canAccessBot(socket, botId)) {
248
+ if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
249
+ return;
250
+ }
251
+
252
+ if (!hasPermission(socket, 'bot:interact')) {
253
+ if (callback) callback({ success: false, error: 'Нет прав: bot:interact' });
254
+ return;
255
+ }
256
+
257
+ const bot = botManager.getBotInstance(botId);
258
+ if (!bot) {
259
+ if (callback) callback({ success: false, error: 'Бот не найден или оффлайн' });
260
+ return;
261
+ }
262
+
263
+ const result = await bot.executeCommand(username, command, args || {});
264
+
265
+ if (callback) callback({ success: true, result });
266
+ } catch (error) {
267
+ console.error('[Panel WS] Ошибка выполнения команды:', error);
268
+ if (callback) callback({ success: false, error: error.message });
269
+ }
270
+ });
271
+
272
+ /**
273
+ * Запуск бота
274
+ */
275
+ socket.on('action:bot:start', async ({ botId }, callback) => {
276
+ try {
277
+ if (!canAccessBot(socket, botId)) {
278
+ if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
279
+ return;
280
+ }
281
+
282
+ if (!hasPermission(socket, 'bot:start_stop')) {
283
+ if (callback) callback({ success: false, error: 'Нет прав: bot:start_stop' });
284
+ return;
285
+ }
286
+
287
+ await botManager.startBot(botId);
288
+
289
+ if (callback) callback({ success: true, message: 'Бот запущен' });
290
+ } catch (error) {
291
+ console.error('[Panel WS] Ошибка запуска бота:', error);
292
+ if (callback) callback({ success: false, error: error.message });
293
+ }
294
+ });
295
+
296
+ /**
297
+ * Остановка бота
298
+ */
299
+ socket.on('action:bot:stop', async ({ botId }, callback) => {
300
+ try {
301
+ if (!canAccessBot(socket, botId)) {
302
+ if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
303
+ return;
304
+ }
305
+
306
+ if (!hasPermission(socket, 'bot:start_stop')) {
307
+ if (callback) callback({ success: false, error: 'Нет прав: bot:start_stop' });
308
+ return;
309
+ }
310
+
311
+ await botManager.stopBot(botId);
312
+
313
+ if (callback) callback({ success: true, message: 'Бот остановлен' });
314
+ } catch (error) {
315
+ console.error('[Panel WS] Ошибка остановки бота:', error);
316
+ if (callback) callback({ success: false, error: error.message });
317
+ }
318
+ });
319
+
320
+ /**
321
+ * Перезапуск бота
322
+ */
323
+ socket.on('action:bot:restart', async ({ botId }, callback) => {
324
+ try {
325
+ if (!canAccessBot(socket, botId)) {
326
+ if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
327
+ return;
328
+ }
329
+
330
+ if (!hasPermission(socket, 'bot:start_stop')) {
331
+ if (callback) callback({ success: false, error: 'Нет прав: bot:start_stop' });
332
+ return;
333
+ }
334
+
335
+ await botManager.restartBot(botId);
336
+
337
+ if (callback) callback({ success: true, message: 'Бот перезапущен' });
338
+ } catch (error) {
339
+ console.error('[Panel WS] Ошибка перезапуска бота:', error);
340
+ if (callback) callback({ success: false, error: error.message });
341
+ }
342
+ });
343
+
344
+ /**
345
+ * Получение статуса бота
346
+ */
347
+ socket.on('action:bot:get_status', async ({ botId }, callback) => {
348
+ try {
349
+ if (!canAccessBot(socket, botId)) {
350
+ if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
351
+ return;
352
+ }
353
+
354
+ const status = botManager.getBotStatus(botId);
355
+
356
+ if (callback) callback({
357
+ success: true,
358
+ status: status || { online: false, connected: false, status: 'offline' }
359
+ });
360
+ } catch (error) {
361
+ console.error('[Panel WS] Ошибка получения статуса:', error);
362
+ if (callback) callback({ success: false, error: error.message });
363
+ }
364
+ });
365
+
366
+ // ========== DISCONNECT ==========
367
+
368
+ socket.on('disconnect', () => {
369
+ console.log(`[Panel WS] Disconnected: ${socket.user.username}`);
370
+ });
371
+ });
372
+
373
+ return panelNamespace;
374
+ }
375
+
376
+ /**
377
+ * Broadcast события в Panel namespace
378
+ */
379
+ function broadcastToPanelNamespace(io, channel, data) {
380
+ const panelNamespace = io.of('/panel');
381
+ panelNamespace.to(channel).emit(channel, data);
382
+ }
383
+
384
+ module.exports = {
385
+ initializePanelNamespace,
386
+ broadcastToPanelNamespace
387
+ };
@@ -27,6 +27,7 @@ function broadcast(io) {
27
27
  username: info.username,
28
28
  path: info.path || '/',
29
29
  lastSeen: info.lastSeen,
30
+ metadata: info.metadata || {},
30
31
  }));
31
32
  io.emit('presence:list', list);
32
33
  }
@@ -38,7 +39,9 @@ function handleConnection(io, socket) {
38
39
  return socket.disconnect(true);
39
40
  }
40
41
  const { userId, username } = decoded;
41
- presenceMap.set(userId, { username, socketId: socket.id, lastSeen: Date.now(), path: '/' });
42
+ const initialPath = socket.handshake?.query?.initialPath || '/';
43
+ console.log(`[Presence] User ${username} (${userId}) connected from ${initialPath}`);
44
+ presenceMap.set(userId, { username, socketId: socket.id, lastSeen: Date.now(), path: initialPath });
42
45
  broadcast(io);
43
46
 
44
47
  socket.on('presence:heartbeat', () => {
@@ -50,11 +53,13 @@ function handleConnection(io, socket) {
50
53
  }
51
54
  });
52
55
 
53
- socket.on('presence:update', ({ path }) => {
56
+ socket.on('presence:update', ({ path, metadata }) => {
54
57
  const info = presenceMap.get(userId) || { username, socketId: socket.id };
55
58
  info.lastSeen = Date.now();
56
59
  info.path = typeof path === 'string' ? path : '/';
60
+ info.metadata = metadata || {};
57
61
  presenceMap.set(userId, info);
62
+ console.log(`[Presence] User ${username} updated location: ${info.path}`, metadata);
58
63
  broadcast(io);
59
64
  });
60
65