blockmine 1.25.0 → 1.27.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 (165) hide show
  1. package/CHANGELOG.md +46 -1
  2. package/backend/cli.js +1 -1
  3. package/backend/package.json +2 -2
  4. package/backend/prisma/migrations/20260328173000_add_plugin_source_ref/migration.sql +2 -0
  5. package/backend/prisma/migrations/migration_lock.toml +2 -2
  6. package/backend/prisma/schema.prisma +2 -0
  7. package/backend/src/api/routes/apiKeys.js +8 -0
  8. package/backend/src/api/routes/bots.js +258 -9
  9. package/backend/src/api/routes/eventGraphs.js +151 -1
  10. package/backend/src/api/routes/health.js +38 -0
  11. package/backend/src/api/routes/nodeRegistry.js +63 -0
  12. package/backend/src/api/routes/plugins.js +254 -29
  13. package/backend/src/container.js +11 -8
  14. package/backend/src/core/BotCommandLoader.js +161 -0
  15. package/backend/src/core/BotConnection.js +125 -0
  16. package/backend/src/core/BotEventHandlers.js +234 -0
  17. package/backend/src/core/BotIPCHandler.js +445 -0
  18. package/backend/src/core/BotManager.js +15 -7
  19. package/backend/src/core/BotProcess.js +75 -142
  20. package/backend/src/core/EventGraphManager.js +7 -3
  21. package/backend/src/core/GraphDebugHandler.js +229 -0
  22. package/backend/src/core/GraphDebugIPC.js +117 -0
  23. package/backend/src/core/GraphExecutionEngine.js +545 -978
  24. package/backend/src/core/GraphTraversal.js +80 -0
  25. package/backend/src/core/GraphValidation.js +73 -0
  26. package/backend/src/core/NodeDefinition.js +138 -0
  27. package/backend/src/core/NodeRegistry.js +153 -141
  28. package/backend/src/core/PluginManager.js +272 -31
  29. package/backend/src/core/RewindSignal.js +9 -0
  30. package/backend/src/core/config/ConfigValidator.js +72 -0
  31. package/backend/src/core/config/FeatureFlags.js +52 -0
  32. package/backend/src/core/config/__tests__/ConfigValidator.test.js +232 -0
  33. package/backend/src/core/domain/entities/Bot.js +39 -0
  34. package/backend/src/core/domain/entities/Command.js +41 -0
  35. package/backend/src/core/domain/entities/EventGraph.js +39 -0
  36. package/backend/src/core/domain/entities/Plugin.js +45 -0
  37. package/backend/src/core/domain/entities/User.js +40 -0
  38. package/backend/src/core/domain/services/DependencyResolver.js +168 -0
  39. package/backend/src/core/domain/services/GraphValidator.js +117 -0
  40. package/backend/src/core/domain/services/PermissionChecker.js +34 -0
  41. package/backend/src/core/domain/services/__tests__/DependencyResolver.test.js +126 -0
  42. package/backend/src/core/domain/valueObjects/BotConfig.js +27 -0
  43. package/backend/src/core/domain/valueObjects/DependencyGraph.js +86 -0
  44. package/backend/src/core/domain/valueObjects/PluginManifest.js +36 -0
  45. package/backend/src/core/errors/BaseError.js +29 -0
  46. package/backend/src/core/errors/ErrorHandler.js +81 -0
  47. package/backend/src/core/errors/__tests__/ErrorHandler.test.js +188 -0
  48. package/backend/src/core/errors/index.js +68 -0
  49. package/backend/src/core/infrastructure/BatchingUtility.js +66 -0
  50. package/backend/src/core/infrastructure/CircuitBreaker.js +103 -0
  51. package/backend/src/core/infrastructure/ConnectionPool.js +81 -0
  52. package/backend/src/core/infrastructure/RateLimiter.js +64 -0
  53. package/backend/src/core/infrastructure/__tests__/BatchingUtility.test.js +86 -0
  54. package/backend/src/core/infrastructure/__tests__/CircuitBreaker.test.js +156 -0
  55. package/backend/src/core/infrastructure/__tests__/ConnectionPool.test.js +146 -0
  56. package/backend/src/core/infrastructure/__tests__/RateLimiter.test.js +171 -0
  57. package/backend/src/core/ipc/botApiFactory.js +72 -0
  58. package/backend/src/core/ipc/ipcMessageTypes.js +115 -0
  59. package/backend/src/core/logging/AuditLogger.js +61 -0
  60. package/backend/src/core/logging/StructuredLogger.js +80 -0
  61. package/backend/src/core/logging/__tests__/StructuredLogger.test.js +213 -0
  62. package/backend/src/core/logging/index.js +7 -0
  63. package/backend/src/core/metrics/MetricsCollector.js +104 -0
  64. package/backend/src/core/metrics/__tests__/MetricsCollector.test.js +131 -0
  65. package/backend/src/core/node-registries/actionsNodes.js +191 -0
  66. package/backend/src/core/node-registries/arraysNodes.js +152 -0
  67. package/backend/src/core/node-registries/botNodes.js +48 -0
  68. package/backend/src/core/node-registries/containerNodes.js +141 -0
  69. package/backend/src/core/node-registries/dataNodes.js +284 -0
  70. package/backend/src/core/node-registries/debugNodes.js +23 -0
  71. package/backend/src/core/node-registries/eventsNodes.js +223 -0
  72. package/backend/src/core/node-registries/flowNodes.js +151 -0
  73. package/backend/src/core/node-registries/furnaceNodes.js +123 -0
  74. package/backend/src/core/node-registries/index.js +108 -0
  75. package/backend/src/core/node-registries/inventory.js +102 -106
  76. package/backend/src/core/node-registries/logicNodes.js +54 -0
  77. package/backend/src/core/node-registries/mathNodes.js +38 -0
  78. package/backend/src/core/node-registries/navigationNodes.js +109 -0
  79. package/backend/src/core/node-registries/objectsNodes.js +90 -0
  80. package/backend/src/core/node-registries/stringsNodes.js +165 -0
  81. package/backend/src/core/node-registries/timeNodes.js +105 -0
  82. package/backend/src/core/node-registries/typeNodes.js +22 -0
  83. package/backend/src/core/node-registries/usersNodes.js +126 -0
  84. package/backend/src/core/nodes/arrays/shuffle.js +14 -0
  85. package/backend/src/core/nodes/bot/get_name.js +8 -0
  86. package/backend/src/core/nodes/bot/stop_bot.js +5 -0
  87. package/backend/src/core/nodes/container/open.js +101 -111
  88. package/backend/src/core/nodes/data/store_read.js +26 -0
  89. package/backend/src/core/nodes/data/store_write.js +23 -0
  90. package/backend/src/core/nodes/event/call_event.js +31 -0
  91. package/backend/src/core/nodes/event/custom_event.js +8 -0
  92. package/backend/src/core/nodes/flow/timer.js +35 -0
  93. package/backend/src/core/nodes/inventory/drop.js +73 -65
  94. package/backend/src/core/nodes/inventory/equip.js +54 -45
  95. package/backend/src/core/nodes/inventory/select_slot.js +48 -46
  96. package/backend/src/core/nodes/navigation/follow.js +54 -51
  97. package/backend/src/core/nodes/navigation/go_to.js +41 -53
  98. package/backend/src/core/nodes/navigation/go_to_entity.js +65 -69
  99. package/backend/src/core/nodes/navigation/go_to_player.js +65 -70
  100. package/backend/src/core/nodes/navigation/stop.js +17 -26
  101. package/backend/src/core/nodes/users/add_to_group.js +24 -0
  102. package/backend/src/core/nodes/users/check_permission.js +26 -0
  103. package/backend/src/core/nodes/users/remove_from_group.js +24 -0
  104. package/backend/src/core/services/BotIPCMessageRouter.js +337 -0
  105. package/backend/src/core/services/BotLifecycleService.js +41 -632
  106. package/backend/src/core/services/CacheManager.js +83 -23
  107. package/backend/src/core/services/CrashRestartManager.js +42 -0
  108. package/backend/src/core/services/DebugSessionManager.js +114 -12
  109. package/backend/src/core/services/EventGraphService.js +69 -0
  110. package/backend/src/core/services/MinecraftBotManager.js +9 -1
  111. package/backend/src/core/services/PluginManagementService.js +84 -0
  112. package/backend/src/core/services/TestModeContext.js +65 -0
  113. package/backend/src/core/services/__tests__/CacheManager.test.js +168 -0
  114. package/backend/src/core/services.js +1 -11
  115. package/backend/src/core/validation/InputValidator.js +167 -0
  116. package/backend/src/core/validation/__tests__/InputValidator.test.js +296 -0
  117. package/backend/src/real-time/botApi/index.js +1 -1
  118. package/backend/src/real-time/socketHandler.js +26 -0
  119. package/backend/src/server.js +10 -5
  120. package/frontend/dist/assets/{browser-ponyfill-DN7pwmHT.js → browser-ponyfill-D8y0Ty7C.js} +1 -1
  121. package/frontend/dist/assets/index-CFJLS0dk.css +32 -0
  122. package/frontend/dist/assets/{index-LSy71uwm.js → index-D91UGNMG.js} +1880 -1881
  123. package/frontend/dist/index.html +2 -2
  124. package/frontend/dist/locales/en/bots.json +4 -1
  125. package/frontend/dist/locales/en/common.json +7 -1
  126. package/frontend/dist/locales/en/login.json +2 -0
  127. package/frontend/dist/locales/en/management.json +79 -1
  128. package/frontend/dist/locales/en/nodes.json +59 -4
  129. package/frontend/dist/locales/en/plugin-detail.json +24 -4
  130. package/frontend/dist/locales/en/plugins.json +226 -7
  131. package/frontend/dist/locales/en/setup.json +2 -0
  132. package/frontend/dist/locales/en/sidebar.json +171 -3
  133. package/frontend/dist/locales/en/visual-editor.json +230 -31
  134. package/frontend/dist/locales/ru/bots.json +4 -1
  135. package/frontend/dist/locales/ru/login.json +2 -0
  136. package/frontend/dist/locales/ru/management.json +79 -1
  137. package/frontend/dist/locales/ru/minecraft-viewer.json +3 -0
  138. package/frontend/dist/locales/ru/nodes.json +105 -51
  139. package/frontend/dist/locales/ru/plugins.json +103 -4
  140. package/frontend/dist/locales/ru/setup.json +2 -0
  141. package/frontend/dist/locales/ru/sidebar.json +171 -3
  142. package/frontend/dist/locales/ru/visual-editor.json +232 -33
  143. package/frontend/package.json +2 -0
  144. package/nul +12 -0
  145. package/package.json +3 -3
  146. package/scripts/postinstall.js +38 -0
  147. package/backend/package-lock.json +0 -6801
  148. package/backend/src/core/node-registries/actions.js +0 -202
  149. package/backend/src/core/node-registries/arrays.js +0 -155
  150. package/backend/src/core/node-registries/bot.js +0 -23
  151. package/backend/src/core/node-registries/container.js +0 -162
  152. package/backend/src/core/node-registries/data.js +0 -290
  153. package/backend/src/core/node-registries/debug.js +0 -26
  154. package/backend/src/core/node-registries/events.js +0 -201
  155. package/backend/src/core/node-registries/flow.js +0 -139
  156. package/backend/src/core/node-registries/furnace.js +0 -143
  157. package/backend/src/core/node-registries/logic.js +0 -62
  158. package/backend/src/core/node-registries/math.js +0 -42
  159. package/backend/src/core/node-registries/navigation.js +0 -111
  160. package/backend/src/core/node-registries/objects.js +0 -98
  161. package/backend/src/core/node-registries/strings.js +0 -187
  162. package/backend/src/core/node-registries/time.js +0 -113
  163. package/backend/src/core/node-registries/type.js +0 -25
  164. package/backend/src/core/node-registries/users.js +0 -79
  165. package/frontend/dist/assets/index-SfhKxI4-.css +0 -32
@@ -1,7 +1,13 @@
1
- const DependencyService = require('../DependencyService');
1
+ const DependencyResolver = require('../domain/services/DependencyResolver');
2
2
  const { decrypt } = require('../utils/crypto');
3
3
  const UserService = require('../UserService');
4
4
  const PermissionManager = require('../PermissionManager');
5
+ const CrashRestartManager = require('./CrashRestartManager');
6
+ const BotIPCMessageRouter = require('./BotIPCMessageRouter');
7
+ const ErrorHandler = require('../errors/ErrorHandler');
8
+
9
+ const dependencyResolver = new DependencyResolver();
10
+ const errorHandler = new ErrorHandler({ logger: console });
5
11
 
6
12
  class BotLifecycleService {
7
13
  constructor({
@@ -30,7 +36,20 @@ class BotLifecycleService {
30
36
  this.logger = logger;
31
37
 
32
38
  this.logCache = new Map();
33
- this.crashCounters = new Map();
39
+ this.crashRestartManager = new CrashRestartManager(5, 60000);
40
+
41
+ this.ipcRouter = new BotIPCMessageRouter({
42
+ eventGraphManager: this.eventGraphManager,
43
+ commandExecutionService: this.commandExecutionService,
44
+ processManager: this.processManager,
45
+ logger: this.logger,
46
+ crashRestartManager: this.crashRestartManager,
47
+ appendLog: this.appendLog.bind(this),
48
+ emitStatusUpdate: this.emitStatusUpdate.bind(this),
49
+ restartBot: this.restartBot.bind(this),
50
+ stopBot: this.stopBot.bind(this),
51
+ getBotConfig: (botId) => this.processManager.getProcess(botId)?.botConfig,
52
+ });
34
53
  }
35
54
 
36
55
  async startBot(botConfig) {
@@ -47,7 +66,7 @@ class BotLifecycleService {
47
66
  this.emitStatusUpdate(botId, 'starting', '');
48
67
 
49
68
  const allPluginsForBot = await this.pluginRepository.findEnabledByBotId(botId);
50
- const { sortedPlugins, hasCriticalIssues, pluginInfo } = DependencyService.resolveDependencies(allPluginsForBot, allPluginsForBot);
69
+ const { sortedPlugins, hasCriticalIssues, pluginInfo } = dependencyResolver.resolve(allPluginsForBot, allPluginsForBot);
51
70
 
52
71
  if (hasCriticalIssues) {
53
72
  this.appendLog(botId, '[DependencyManager] Обнаружены критические проблемы с зависимостями, запуск отменен.');
@@ -63,7 +82,8 @@ class BotLifecycleService {
63
82
  if (criticalIssues.length > 0) {
64
83
  this.appendLog(botId, `* Плагин "${info.name}":`);
65
84
  for (const issue of criticalIssues) {
66
- this.appendLog(botId, ` - ${issue.message}`);
85
+ const msg = issue.message || `${issue.messageKey} ${JSON.stringify(issue.context || {})}`;
86
+ this.appendLog(botId, ` - ${msg}`);
67
87
  }
68
88
  }
69
89
  }
@@ -100,8 +120,7 @@ class BotLifecycleService {
100
120
  }
101
121
  };
102
122
 
103
- // Регистрируем обработчики сообщений от child process
104
- this._setupChildProcessHandlers(child, botConfig);
123
+ this.ipcRouter.attachToChild(child, botConfig);
105
124
 
106
125
  child.send({ type: 'start', config: fullBotConfig });
107
126
 
@@ -118,14 +137,12 @@ class BotLifecycleService {
118
137
  if (child) {
119
138
  this.eventGraphManager.unloadGraphsForBot(botId);
120
139
 
121
- // Очищаем traces для этого бота
122
140
  const { getTraceCollector } = require('./TraceCollectorService');
123
141
  const traceCollector = getTraceCollector();
124
142
  traceCollector.clearForBot(botId);
125
143
 
126
144
  child.send({ type: 'stop' });
127
145
 
128
- // Принудительное завершение через 5 секунд
129
146
  setTimeout(() => {
130
147
  if (!child.killed) {
131
148
  this.logger.warn({ botId }, 'Принудительное завершение процесса');
@@ -154,309 +171,11 @@ class BotLifecycleService {
154
171
  }
155
172
 
156
173
  await this.stopBot(botId);
157
-
158
- // Ждём завершения процесса
159
174
  await new Promise(resolve => setTimeout(resolve, 1000));
160
175
 
161
176
  return this.startBot(botConfig);
162
177
  }
163
178
 
164
- _setupChildProcessHandlers(child, botConfig) {
165
- const botId = botConfig.id;
166
-
167
- child.on('message', async (message) => {
168
- try {
169
- switch (message.type) {
170
- case 'event':
171
- await this._handleEventMessage(botId, message);
172
- break;
173
- case 'plugin:data':
174
- this._handlePluginDataMessage(botId, message);
175
- break;
176
- case 'send_websocket_message':
177
- this._handleWebSocketMessage(message);
178
- break;
179
- case 'log':
180
- this.appendLog(botId, message.content);
181
- break;
182
- case 'plugin-log':
183
- this._handlePluginLog(message.log);
184
- break;
185
- case 'status':
186
- this.emitStatusUpdate(botId, message.status);
187
- break;
188
- case 'bot_ready':
189
- this._handleBotReady(botId);
190
- break;
191
- case 'validate_and_run_command':
192
- if (this.commandExecutionService) {
193
- const botConfig = child.botConfig;
194
- if (botConfig) {
195
- await this.commandExecutionService.handleCommandValidation(botConfig, message);
196
- }
197
- }
198
- break;
199
- case 'request_user_action':
200
- await this._handleUserAction(botId, child, message);
201
- break;
202
- case 'get_player_list_response':
203
- this.processManager.resolvePlayerListRequest(message.requestId, message.payload.players);
204
- break;
205
- case 'get_nearby_entities_response':
206
- this.processManager.resolveNearbyEntitiesRequest(message.requestId, message.payload.entities);
207
- break;
208
- case 'execute_command_response':
209
- this.processManager.resolveCommandRequest(message.requestId, message.result, message.error);
210
- break;
211
- case 'register_command':
212
- await this._handleCommandRegistration(botId, message.commandConfig);
213
- break;
214
- case 'register_permissions':
215
- await this._handlePermissionsRegistration(botId, message);
216
- break;
217
- case 'register_group':
218
- await this._handleGroupRegistration(botId, message);
219
- break;
220
- case 'add_permissions_to_group':
221
- await this._handleAddPermissionsToGroup(botId, message);
222
- break;
223
- case 'trace:completed':
224
- await this._handleTraceCompleted(botId, message.trace);
225
- break;
226
- case 'debug:check_breakpoint':
227
- await this._handleDebugBreakpointCheck(botId, child, message);
228
- break;
229
- case 'debug:check_step_mode':
230
- await this._handleDebugStepModeCheck(botId, child, message);
231
- break;
232
- case 'update_credentials':
233
- await this._handleUpdateCredentials(botId, child, message);
234
- break;
235
- case 'restart_bot':
236
- await this._handleRestartBot(botId, child, message);
237
- break;
238
- case 'change_credentials':
239
- await this._handleChangeCredentials(botId, child, message);
240
- break;
241
- }
242
- } catch (error) {
243
- this.appendLog(botId, `[SYSTEM-ERROR] Критическая ошибка в обработчике: ${error.stack}`);
244
- this.logger.error({ botId, error }, 'Критическая ошибка в обработчике сообщений');
245
- }
246
- });
247
-
248
- child.on('error', (err) => this.appendLog(botId, `[PROCESS FATAL] ${err.stack}`));
249
- child.stdout.on('data', (data) => console.log(data.toString()));
250
- child.stderr.on('data', (data) => this.appendLog(botId, `[STDERR] ${data.toString()}`));
251
-
252
- child.on('exit', (code, signal) => {
253
- this._handleProcessExit(botId, botConfig, code, signal);
254
- });
255
- }
256
-
257
- async _handleEventMessage(botId, message) {
258
- if (message.eventType === 'raw_message') {
259
- try {
260
- const { getIOSafe } = require('../../real-time/socketHandler');
261
- const { broadcastToApiClients } = require('../../real-time/botApi');
262
- broadcastToApiClients(getIOSafe(), botId, 'chat:raw_message', {
263
- raw_message: message.args.rawText || message.args.raw_message,
264
- json: message.args.json
265
- });
266
- } catch (e) { /* Socket.IO может быть не инициализирован */ }
267
- }
268
-
269
- try {
270
- const { broadcastToPanelNamespace } = require('../../real-time/panelNamespace');
271
- broadcastToPanelNamespace(botId, 'bot:event', {
272
- botId,
273
- eventType: message.eventType,
274
- data: message.args || {},
275
- timestamp: new Date().toISOString()
276
- });
277
- } catch (e) { /* Socket.IO может быть не инициализирован */ }
278
-
279
- if (this.eventGraphManager) {
280
- this.eventGraphManager.handleEvent(botId, message.eventType, message.args);
281
- }
282
- }
283
-
284
- _handlePluginDataMessage(botId, message) {
285
- const { plugin: pluginName, payload } = message;
286
- const pluginSubscribers = this.processManager.getPluginSubscribers(botId, pluginName);
287
-
288
- if (pluginSubscribers && pluginSubscribers.size > 0) {
289
- pluginSubscribers.forEach(socket => {
290
- socket.emit('plugin:ui:dataUpdate', payload);
291
- });
292
- }
293
- }
294
-
295
- _handlePluginLog(logData) {
296
- const { getIOSafe, addPluginLogToBuffer } = require('../../real-time/socketHandler');
297
- const { botId, pluginName } = logData;
298
-
299
- // Добавляем лог в буфер
300
- addPluginLogToBuffer(botId, pluginName, logData);
301
-
302
- // Отправляем через Socket.IO в комнату плагина
303
- const io = getIOSafe();
304
- if (io) {
305
- const room = `plugin-logs:${botId}:${pluginName}`;
306
- io.to(room).emit('plugin-log', logData);
307
- }
308
- }
309
-
310
- _handleWebSocketMessage(message) {
311
- const { getIOSafe } = require('../../real-time/socketHandler');
312
- const { botId, message: msg } = message.payload;
313
- getIOSafe().to(`bot_${botId}`).emit('bot:message', { message: msg });
314
- }
315
-
316
- _handleBotReady(botId) {
317
- this.emitStatusUpdate(botId, 'running', 'Бот успешно подключился к серверу.');
318
- this.crashCounters.delete(botId);
319
-
320
- try {
321
- const { getIOSafe } = require('../../real-time/socketHandler');
322
- const { broadcastBotStatus } = require('../../real-time/botApi');
323
- broadcastBotStatus(getIOSafe(), botId, true);
324
- } catch (e) { /* Socket.IO может быть не инициализирован */ }
325
-
326
- // Триггерим событие запуска бота
327
- if (this.eventGraphManager) {
328
- this.eventGraphManager.handleEvent(botId, 'botStartup', {});
329
- }
330
- }
331
-
332
- async _handleCommandRegistration(botId, commandConfig) {
333
- if (this.commandExecutionService) {
334
- await this.commandExecutionService.handleCommandRegistration(botId, commandConfig);
335
- // this.logger.debug({ botId, commandName: commandConfig.name }, 'Команда зарегистрирована');
336
- } else {
337
- this.logger.warn({ botId }, 'CommandExecutionService не доступен для регистрации команды');
338
- }
339
- }
340
-
341
- async _handlePermissionsRegistration(botId, message) {
342
- try {
343
- await PermissionManager.registerPermissions(botId, message.permissions);
344
- this.logger.debug({ botId, count: message.permissions.length }, 'Права зарегистрированы');
345
- } catch (error) {
346
- this.logger.error({ botId, error }, 'Ошибка регистрации прав');
347
- }
348
- }
349
-
350
- async _handleGroupRegistration(botId, message) {
351
- try {
352
- await PermissionManager.registerGroup(botId, message.groupConfig);
353
- this.logger.debug({ botId, groupName: message.groupConfig.name }, 'Группа зарегистрирована');
354
- } catch (error) {
355
- this.logger.error({ botId, error }, 'Ошибка регистрации группы');
356
- }
357
- }
358
-
359
- async _handleAddPermissionsToGroup(botId, message) {
360
- try {
361
- await PermissionManager.addPermissionsToGroup(botId, message.groupName, message.permissionNames);
362
- this.logger.debug({ botId, groupName: message.groupName, count: message.permissionNames.length }, 'Права добавлены в группу');
363
- } catch (error) {
364
- this.logger.error({ botId, error }, 'Ошибка добавления прав в группу');
365
- }
366
- }
367
-
368
- async _handleUserAction(botId, child, message) {
369
- const { requestId, payload } = message;
370
- const { targetUsername, action, data } = payload;
371
-
372
- try {
373
- const botConfig = child.botConfig;
374
- const user = await UserService.getUser(targetUsername, botId, botConfig);
375
- if (!user) throw new Error(`Пользователь ${targetUsername} не найден.`);
376
-
377
- let result;
378
-
379
- switch (action) {
380
- case 'addGroup':
381
- result = await user.addGroup(data.group);
382
- break;
383
- case 'removeGroup':
384
- result = await user.removeGroup(data.group);
385
- break;
386
- case 'getGroups':
387
- result = user.groups ? user.groups.map(g => g.group.name) : [];
388
- break;
389
- case 'getPermissions':
390
- result = Array.from(user.permissionsSet);
391
- break;
392
- case 'isBlacklisted':
393
- result = user.isBlacklisted;
394
- break;
395
- case 'setBlacklisted':
396
- result = await user.setBlacklist(data.value);
397
- break;
398
- default:
399
- throw new Error(`Неизвестное действие: ${action}`);
400
- }
401
-
402
- child.send({ type: 'user_action_response', requestId, payload: result });
403
- } catch (error) {
404
- this.logger.error({ botId, action, username: targetUsername, error }, 'Ошибка действия пользователя');
405
- child.send({ type: 'user_action_response', requestId, error: error.message });
406
- }
407
- }
408
-
409
- _handleProcessExit(botId, botConfig, code, signal) {
410
- this.processManager.remove(botId);
411
- this.resourceMonitor.clearResourceUsage(botId);
412
- this.cache.clearBotCache(botId);
413
-
414
- this.emitStatusUpdate(botId, 'stopped', `Процесс завершился с кодом ${code} (сигнал: ${signal || 'none'}).`);
415
-
416
- try {
417
- const { getIOSafe } = require('../../real-time/socketHandler');
418
- const { broadcastBotStatus } = require('../../real-time/botApi');
419
- broadcastBotStatus(getIOSafe(), botId, false);
420
- } catch (e) { /* Socket.IO может быть не инициализирован */ }
421
-
422
- // Автоперезапуск при критических ошибках
423
- if (code === 1) {
424
- this._handleCrashRestart(botId, botConfig);
425
- }
426
- }
427
-
428
- _handleCrashRestart(botId, botConfig) {
429
- const MAX_RESTART_ATTEMPTS = 5;
430
- const RESTART_COOLDOWN = 60000;
431
-
432
- const counter = this.crashCounters.get(botId) || { count: 0, firstCrash: Date.now() };
433
- const timeSinceFirstCrash = Date.now() - counter.firstCrash;
434
-
435
- if (timeSinceFirstCrash > RESTART_COOLDOWN) {
436
- counter.count = 0;
437
- counter.firstCrash = Date.now();
438
- }
439
-
440
- counter.count++;
441
- this.crashCounters.set(botId, counter);
442
-
443
- if (counter.count >= MAX_RESTART_ATTEMPTS) {
444
- this.logger.warn({ botId, attempts: counter.count }, 'Автоперезапуск остановлен');
445
- this.appendLog(botId, `[SYSTEM] Обнаружено ${counter.count} критических ошибок подряд.`);
446
- this.appendLog(botId, `[SYSTEM] Исправьте проблему и запустите бота вручную.`);
447
- this.crashCounters.delete(botId);
448
- return;
449
- }
450
-
451
- this.logger.info({ botId, attempt: counter.count, max: MAX_RESTART_ATTEMPTS }, 'Перезапуск через 5 секунд');
452
- this.appendLog(botId, `[SYSTEM] Обнаружена критическая ошибка, перезапуск через 5 секунд... (попытка ${counter.count}/${MAX_RESTART_ATTEMPTS})`);
453
-
454
- setTimeout(() => {
455
- this.logger.info({ botId }, 'Выполняется перезапуск');
456
- this.startBot(botConfig);
457
- }, 5000);
458
- }
459
-
460
179
  async loadConfigForBot(botId) {
461
180
  this.logger.info({ botId }, 'Загрузка конфигурации');
462
181
 
@@ -484,6 +203,7 @@ class BotLifecycleService {
484
203
  return config;
485
204
  } catch (error) {
486
205
  this.logger.error({ botId, error }, 'Ошибка загрузки конфигурации');
206
+ const handled = errorHandler.handle(error, { botId });
487
207
  throw new Error(`Failed to load/cache bot configuration for botId ${botId}: ${error.message}`);
488
208
  }
489
209
  }
@@ -520,14 +240,12 @@ class BotLifecycleService {
520
240
  for (const perm of systemPermissions) {
521
241
  const existing = await this.permissionRepository.findByName(botId, perm.name);
522
242
  if (existing) {
523
- // Обновляем описание если изменилось
524
243
  if (existing.description !== perm.description) {
525
244
  await this.permissionRepository.update(existing.id, {
526
245
  description: perm.description
527
246
  });
528
247
  }
529
248
  } else {
530
- // Создаем новое системное право
531
249
  await this.permissionRepository.create({
532
250
  botId,
533
251
  name: perm.name,
@@ -553,7 +271,9 @@ class BotLifecycleService {
553
271
  const newLogs = [...currentLogs.slice(-199), logEntry];
554
272
  this.logCache.set(botId, newLogs);
555
273
 
556
- getIOSafe().emit('bot:log', { botId, log: logEntry });
274
+ try {
275
+ getIOSafe().emit('bot:log', { botId, log: logEntry });
276
+ } catch (e) {}
557
277
  }
558
278
 
559
279
  getBotLogs(botId) {
@@ -564,14 +284,15 @@ class BotLifecycleService {
564
284
  const { getIOSafe, broadcastToPanelNamespace } = require('../../real-time/socketHandler');
565
285
  if (message) this.appendLog(botId, `[SYSTEM] ${message}`);
566
286
 
567
- getIOSafe().emit('bot:status', { botId, status, message });
568
-
569
- broadcastToPanelNamespace(getIOSafe(), 'bots:status', {
570
- botId,
571
- status,
572
- message,
573
- timestamp: new Date().toISOString()
574
- });
287
+ try {
288
+ getIOSafe().emit('bot:status', { botId, status, message });
289
+ broadcastToPanelNamespace(getIOSafe(), 'bots:status', {
290
+ botId,
291
+ status,
292
+ message,
293
+ timestamp: new Date().toISOString()
294
+ });
295
+ } catch (e) {}
575
296
  }
576
297
 
577
298
  getFullState() {
@@ -625,8 +346,6 @@ class BotLifecycleService {
625
346
  }
626
347
 
627
348
  async getPlayerList(botId) {
628
- const PLAYER_LIST_CACHE_TTL = 2000;
629
-
630
349
  if (!this.processManager.isRunning(botId)) {
631
350
  return [];
632
351
  }
@@ -678,8 +397,8 @@ class BotLifecycleService {
678
397
  timeout
679
398
  });
680
399
 
681
- this.processManager.sendMessage(botId, {
682
- type: 'system:get_nearby_entities',
400
+ this.processManager.sendMessage(botId, {
401
+ type: 'system:get_nearby_entities',
683
402
  requestId,
684
403
  payload: { position, radius }
685
404
  });
@@ -704,316 +423,6 @@ class BotLifecycleService {
704
423
 
705
424
  return { success: true };
706
425
  }
707
-
708
- async _handleTraceCompleted(botId, trace) {
709
- try {
710
- const { getTraceCollector } = require('../services/TraceCollectorService');
711
- const traceCollector = getTraceCollector();
712
-
713
- // Сохраняем трассировку в главном TraceCollectorService
714
- await traceCollector._storeCompletedTrace(trace);
715
- } catch (error) {
716
- this.logger.error({ botId, error }, 'Ошибка обработки завершённой трассировки');
717
- }
718
- }
719
-
720
- async _handleDebugBreakpointCheck(botId, child, message) {
721
- const { requestId, payload } = message;
722
- const { graphId, nodeId, nodeType, inputs, executedSteps, context } = payload;
723
-
724
- try {
725
- const { getGlobalDebugManager } = require('../services/DebugSessionManager');
726
- const debugManager = getGlobalDebugManager();
727
-
728
- const debugState = debugManager.get(graphId);
729
- if (!debugState) {
730
- // Нет debug сессии для этого графа - просто продолжаем выполнение
731
- child.send({
732
- type: 'debug:breakpoint_response',
733
- requestId,
734
- overrides: null
735
- });
736
- return;
737
- }
738
-
739
- const breakpoint = debugState.breakpoints.get(nodeId);
740
- if (!breakpoint || !breakpoint.enabled) {
741
- // Нет брейкпоинта для этой ноды или он отключен
742
- child.send({
743
- type: 'debug:breakpoint_response',
744
- requestId,
745
- overrides: null
746
- });
747
- return;
748
- }
749
-
750
- // Проверяем условие брейкпоинта (пока всегда срабатывает)
751
- // TODO: добавить evaluateBreakpointCondition
752
-
753
- breakpoint.hitCount++;
754
-
755
- // Приостанавливаем выполнение и ждём действий от пользователя
756
- const overrides = await debugState.pause({
757
- nodeId,
758
- nodeType,
759
- inputs,
760
- executedSteps,
761
- context,
762
- breakpoint: {
763
- condition: breakpoint.condition,
764
- hitCount: breakpoint.hitCount
765
- }
766
- });
767
-
768
- // Отправляем результат обратно в дочерний процесс
769
- child.send({
770
- type: 'debug:breakpoint_response',
771
- requestId,
772
- overrides: overrides || null
773
- });
774
-
775
- } catch (error) {
776
- this.logger.error({ botId, error }, 'Ошибка обработки debug breakpoint check');
777
- // В случае ошибки отправляем null чтобы продолжить выполнение
778
- child.send({
779
- type: 'debug:breakpoint_response',
780
- requestId,
781
- overrides: null
782
- });
783
- }
784
- }
785
-
786
- async _handleDebugStepModeCheck(botId, child, message) {
787
- const { requestId, payload } = message;
788
- const { graphId, nodeId, nodeType, inputs, executedSteps, context } = payload;
789
-
790
- try {
791
- const { getGlobalDebugManager } = require('../services/DebugSessionManager');
792
- const debugManager = getGlobalDebugManager();
793
-
794
- const debugState = debugManager.get(graphId);
795
- if (!debugState) {
796
- // Нет debug сессии - продолжаем выполнение
797
- child.send({
798
- type: 'debug:breakpoint_response', // Используем тот же тип ответа
799
- requestId,
800
- overrides: null
801
- });
802
- return;
803
- }
804
-
805
- // Проверяем, нужно ли остановиться в step mode
806
- if (!debugState.shouldStepPause(nodeId)) {
807
- // Step mode не активен или не нужно останавливаться на этой ноде
808
- child.send({
809
- type: 'debug:breakpoint_response',
810
- requestId,
811
- overrides: null
812
- });
813
- return;
814
- }
815
-
816
- // Приостанавливаем выполнение и ждём действий от пользователя
817
- const overrides = await debugState.pause({
818
- nodeId,
819
- nodeType,
820
- inputs,
821
- executedSteps,
822
- context
823
- });
824
-
825
- // Отправляем результат обратно в дочерний процесс
826
- child.send({
827
- type: 'debug:breakpoint_response',
828
- requestId,
829
- overrides: overrides || null
830
- });
831
-
832
- } catch (error) {
833
- this.logger.error({ botId, error }, 'Ошибка обработки debug step mode check');
834
- // В случае ошибки отправляем null чтобы продолжить выполнение
835
- child.send({
836
- type: 'debug:breakpoint_response',
837
- requestId,
838
- overrides: null
839
- });
840
- }
841
- }
842
-
843
- /**
844
- * Обработчик обновления credentials в БД (без рестарта)
845
- */
846
- async _handleUpdateCredentials(botId, child, message) {
847
- const { requestId, payload } = message;
848
- const { username, password } = payload;
849
-
850
- try {
851
- // Валидация входных данных
852
- if (!username || username.trim().length === 0) {
853
- throw new Error('Username не может быть пустым');
854
- }
855
-
856
- const existingBot = await this.botRepository.findByUsername(username);
857
- if (existingBot && existingBot.id !== botId) {
858
- throw new Error(`Username "${username}" уже используется другим ботом (ID: ${existingBot.id})`);
859
- }
860
-
861
- const { encrypt } = require('../utils/crypto');
862
- const updateData = { username };
863
-
864
- if (password !== undefined && password !== null) {
865
- if (password.trim().length === 0) {
866
- throw new Error('Password не может быть пустым');
867
- }
868
- updateData.password = encrypt(password);
869
- }
870
-
871
- await this.botRepository.update(botId, updateData);
872
-
873
- if (child.botConfig) {
874
- child.botConfig.username = username;
875
- if (password !== undefined && password !== null) {
876
- child.botConfig.password = updateData.password;
877
- }
878
- }
879
-
880
- this.logger.info({ botId, username }, 'Credentials обновлены в БД');
881
- this.appendLog(botId, `[API] Credentials обновлены: username="${username}"`);
882
-
883
- child.send({
884
- type: 'credentials_operation_response',
885
- requestId,
886
- payload: {
887
- success: true,
888
- message: 'Credentials успешно обновлены в БД'
889
- }
890
- });
891
-
892
- } catch (error) {
893
- this.logger.error({ botId, error }, 'Ошибка обновления credentials');
894
- this.appendLog(botId, `[API ERROR] Не удалось обновить credentials: ${error.message}`);
895
-
896
- child.send({
897
- type: 'credentials_operation_response',
898
- requestId,
899
- error: error.message
900
- });
901
- }
902
- }
903
-
904
- /**
905
- * Обработчик рестарта бота
906
- */
907
- async _handleRestartBot(botId, child, message) {
908
- const { requestId } = message;
909
-
910
- try {
911
- this.logger.info({ botId }, 'Запрос на рестарт бота от плагина');
912
- this.appendLog(botId, '[API] Получен запрос на рестарт бота от плагина');
913
-
914
- const savedBotConfig = { ...child.botConfig };
915
-
916
- child.send({
917
- type: 'credentials_operation_response',
918
- requestId,
919
- payload: {
920
- success: true,
921
- message: 'Рестарт инициирован'
922
- }
923
- });
924
-
925
- setTimeout(async () => {
926
- try {
927
- await this.restartBot(botId, savedBotConfig);
928
- this.logger.info({ botId }, 'Бот успешно перезапущен');
929
- } catch (error) {
930
- this.logger.error({ botId, error }, 'Ошибка при рестарте бота');
931
- this.appendLog(botId, `[API ERROR] Ошибка при рестарте: ${error.message}`);
932
- }
933
- }, 3000);
934
-
935
- } catch (error) {
936
- this.logger.error({ botId, error }, 'Ошибка инициализации рестарта');
937
-
938
- child.send({
939
- type: 'credentials_operation_response',
940
- requestId,
941
- error: error.message
942
- });
943
- }
944
- }
945
-
946
- /**
947
- * Обработчик изменения credentials с автоматическим рестартом
948
- */
949
- async _handleChangeCredentials(botId, child, message) {
950
- const { requestId, payload } = message;
951
- const { username, password } = payload;
952
-
953
- try {
954
- if (!username || username.trim().length === 0) {
955
- throw new Error('Username не может быть пустым');
956
- }
957
-
958
- const existingBot = await this.botRepository.findByUsername(username);
959
- if (existingBot && existingBot.id !== botId) {
960
- throw new Error(`Username "${username}" уже используется другим ботом (ID: ${existingBot.id})`);
961
- }
962
-
963
- const { encrypt } = require('../utils/crypto');
964
- const updateData = { username };
965
-
966
- if (password !== undefined && password !== null) {
967
- if (password.trim().length === 0) {
968
- throw new Error('Password не может быть пустым');
969
- }
970
- updateData.password = encrypt(password);
971
- }
972
-
973
- await this.botRepository.update(botId, updateData);
974
-
975
- if (child.botConfig) {
976
- child.botConfig.username = username;
977
- if (password !== undefined && password !== null) {
978
- child.botConfig.password = updateData.password;
979
- }
980
- }
981
-
982
- const savedBotConfig = { ...child.botConfig };
983
-
984
- this.logger.info({ botId, username }, 'Credentials обновлены, инициирован рестарт');
985
- this.appendLog(botId, `[API] Credentials изменены: username="${username}". Выполняется рестарт...`);
986
-
987
- child.send({
988
- type: 'credentials_operation_response',
989
- requestId,
990
- payload: {
991
- success: true,
992
- message: 'Credentials обновлены, рестарт инициирован'
993
- }
994
- });
995
-
996
- setTimeout(async () => {
997
- try {
998
- await this.restartBot(botId, savedBotConfig);
999
- this.logger.info({ botId }, 'Бот успешно перезапущен с новыми credentials');
1000
- } catch (error) {
1001
- this.logger.error({ botId, error }, 'Ошибка при рестарте бота после изменения credentials');
1002
- this.appendLog(botId, `[API ERROR] Ошибка при рестарте: ${error.message}`);
1003
- }
1004
- }, 3000);
1005
-
1006
- } catch (error) {
1007
- this.logger.error({ botId, error }, 'Ошибка изменения credentials');
1008
- this.appendLog(botId, `[API ERROR] Не удалось изменить credentials: ${error.message}`);
1009
-
1010
- child.send({
1011
- type: 'credentials_operation_response',
1012
- requestId,
1013
- error: error.message
1014
- });
1015
- }
1016
- }
1017
426
  }
1018
427
 
1019
- module.exports = BotLifecycleService;
428
+ module.exports = BotLifecycleService;