blockmine 1.6.3 → 1.13.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 (33) hide show
  1. package/.husky/commit-msg +1 -0
  2. package/.husky/pre-commit +1 -0
  3. package/.versionrc.json +17 -0
  4. package/CHANGELOG.md +36 -0
  5. package/README.md +1 -1
  6. package/backend/package.json +1 -0
  7. package/backend/prisma/migrations/20250718181335_add_plugin_data_store/migration.sql +14 -0
  8. package/backend/prisma/schema.prisma +17 -2
  9. package/backend/src/api/routes/auth.js +140 -0
  10. package/backend/src/api/routes/bots.js +176 -0
  11. package/backend/src/api/routes/changelog.js +16 -0
  12. package/backend/src/api/routes/eventGraphs.js +11 -1
  13. package/backend/src/api/routes/plugins.js +11 -0
  14. package/backend/src/core/BotManager.js +92 -40
  15. package/backend/src/core/BotProcess.js +44 -24
  16. package/backend/src/core/EventGraphManager.js +29 -5
  17. package/backend/src/core/GraphExecutionEngine.js +54 -12
  18. package/backend/src/core/MessageQueue.js +10 -1
  19. package/backend/src/core/NodeRegistry.js +2 -1
  20. package/backend/src/core/PluginLoader.js +72 -8
  21. package/backend/src/core/PluginManager.js +19 -0
  22. package/backend/src/plugins/PluginStore.js +87 -0
  23. package/backend/src/real-time/socketHandler.js +11 -3
  24. package/backend/src/server.js +2 -0
  25. package/backend/temp_migration.sql +0 -0
  26. package/commitlint.config.js +3 -0
  27. package/frontend/dist/assets/index-CHwi1QN9.js +8331 -0
  28. package/frontend/dist/assets/index-DhU2u6V0.css +1 -0
  29. package/frontend/dist/index.html +2 -2
  30. package/frontend/package.json +6 -0
  31. package/package.json +20 -4
  32. package/frontend/dist/assets/index-CIDmlKtb.js +0 -8203
  33. package/frontend/dist/assets/index-DF3i-W3m.css +0 -1
@@ -1,6 +1,5 @@
1
1
  const { fork } = require('child_process');
2
2
  const path = require('path');
3
- const { getIO } = require('../real-time/socketHandler');
4
3
  const prisma = require('../lib/prisma');
5
4
  const pidusage = require('pidusage');
6
5
  const DependencyService = require('./DependencyService');
@@ -12,8 +11,6 @@ const crypto = require('crypto');
12
11
  const { decrypt } = require('./utils/crypto');
13
12
  const EventGraphManager = require('./EventGraphManager');
14
13
  const nodeRegistry = require('./NodeRegistry');
15
- const GraphExecutionEngine = require('./GraphExecutionEngine');
16
-
17
14
  const UserService = require('./UserService');
18
15
 
19
16
  const cooldowns = new Map();
@@ -53,8 +50,8 @@ class BotManager {
53
50
  this.nodeRegistry = nodeRegistry;
54
51
  this.pendingPlayerListRequests = new Map();
55
52
  this.playerListCache = new Map();
56
- this.graphEngine = new GraphExecutionEngine(this.nodeRegistry, this);
57
53
  this.eventGraphManager = null;
54
+ this.uiSubscriptions = new Map();
58
55
 
59
56
  getInstanceId();
60
57
  setInterval(() => this.updateAllResourceUsage(), 5000);
@@ -69,6 +66,55 @@ class BotManager {
69
66
  }
70
67
  }
71
68
 
69
+ subscribeToPluginUi(botId, pluginName, socket) {
70
+ if (!this.uiSubscriptions.has(botId)) {
71
+ this.uiSubscriptions.set(botId, new Map());
72
+ }
73
+ const botSubscriptions = this.uiSubscriptions.get(botId);
74
+
75
+ if (!botSubscriptions.has(pluginName)) {
76
+ botSubscriptions.set(pluginName, new Set());
77
+ }
78
+ const pluginSubscribers = botSubscriptions.get(pluginName);
79
+
80
+ pluginSubscribers.add(socket);
81
+ console.log(`[UI Sub] Сокет ${socket.id} подписался на ${pluginName} для бота ${botId}. Всего подписчиков: ${pluginSubscribers.size}`);
82
+
83
+ const botProcess = this.bots.get(botId);
84
+ if (botProcess && !botProcess.killed) {
85
+ botProcess.send({ type: 'plugin:ui:start-updates', pluginName });
86
+ }
87
+ }
88
+
89
+ unsubscribeFromPluginUi(botId, pluginName, socket) {
90
+ const botSubscriptions = this.uiSubscriptions.get(botId);
91
+ if (!botSubscriptions) return;
92
+
93
+ const pluginSubscribers = botSubscriptions.get(pluginName);
94
+ if (!pluginSubscribers) return;
95
+
96
+ pluginSubscribers.delete(socket);
97
+ console.log(`[UI Sub] Сокет ${socket.id} отписался от ${pluginName} для бота ${botId}. Осталось: ${pluginSubscribers.size}`);
98
+
99
+ if (pluginSubscribers.size === 0) {
100
+ const botProcess = this.bots.get(botId);
101
+ if (botProcess && !botProcess.killed) {
102
+ botProcess.send({ type: 'plugin:ui:stop-updates', pluginName });
103
+ }
104
+ botSubscriptions.delete(pluginName);
105
+ }
106
+ }
107
+
108
+ handleSocketDisconnect(socket) {
109
+ this.uiSubscriptions.forEach((botSubscriptions, botId) => {
110
+ botSubscriptions.forEach((pluginSubscribers, pluginName) => {
111
+ if (pluginSubscribers.has(socket)) {
112
+ this.unsubscribeFromPluginUi(botId, pluginName, socket);
113
+ }
114
+ });
115
+ });
116
+ }
117
+
72
118
  async loadConfigForBot(botId) {
73
119
  console.log(`[BotManager] Caching configuration for bot ID ${botId}...`);
74
120
  try {
@@ -108,6 +154,7 @@ class BotManager {
108
154
  }
109
155
 
110
156
  reloadBotConfigInRealTime(botId) {
157
+ const { getIO } = require('../real-time/socketHandler');
111
158
  this.invalidateConfigCache(botId);
112
159
  const child = this.bots.get(botId);
113
160
  if (child && !child.killed) {
@@ -214,6 +261,7 @@ class BotManager {
214
261
  }
215
262
 
216
263
  async updateAllResourceUsage() {
264
+ const { getIO } = require('../real-time/socketHandler');
217
265
  if (this.bots.size === 0) {
218
266
  if (this.resourceUsage.size > 0) {
219
267
  this.resourceUsage.clear();
@@ -270,12 +318,13 @@ class BotManager {
270
318
  }
271
319
 
272
320
  emitStatusUpdate(botId, status, message = null) {
273
- // console.log(`[BotManager] Отправка статуса для бота ${botId}: status=${status}, message=${message || 'null'}`);
321
+ const { getIO } = require('../real-time/socketHandler');
274
322
  if (message) this.appendLog(botId, `[SYSTEM] ${message}`);
275
323
  getIO().emit('bot:status', { botId, status, message });
276
324
  }
277
325
 
278
326
  appendLog(botId, logContent) {
327
+ const { getIO } = require('../real-time/socketHandler');
279
328
  const logEntry = {
280
329
  id: Date.now() + Math.random(),
281
330
  content: logContent,
@@ -333,6 +382,17 @@ class BotManager {
333
382
 
334
383
  child.botConfig = botConfig;
335
384
 
385
+ child.api = {
386
+ sendMessage: (type, message, username) => {
387
+ if (!child.killed) {
388
+ child.send({ type: 'chat', payload: { message, chatType: type, username } });
389
+ }
390
+ },
391
+ sendLog: (message) => {
392
+ this.appendLog(botConfig.id, message);
393
+ }
394
+ };
395
+
336
396
  child.on('message', async (message) => {
337
397
  const botId = botConfig.id;
338
398
  try {
@@ -342,6 +402,21 @@ class BotManager {
342
402
  this.eventGraphManager.handleEvent(botId, message.eventType, message.args);
343
403
  }
344
404
  break;
405
+ case 'plugin:data': {
406
+ const { plugin: pluginName, payload } = message;
407
+ const botSubscriptions = this.uiSubscriptions.get(botId);
408
+ if (!botSubscriptions) break;
409
+
410
+ const pluginSubscribers = botSubscriptions.get(pluginName);
411
+ if (pluginSubscribers && pluginSubscribers.size > 0) {
412
+ pluginSubscribers.forEach(socket => {
413
+ socket.emit('plugin:ui:dataUpdate', payload);
414
+ });
415
+ }
416
+ break;
417
+ }
418
+ case 'plugin:stopped':
419
+ break;
345
420
  case 'log':
346
421
  this.appendLog(botId, message.content);
347
422
  break;
@@ -444,6 +519,7 @@ class BotManager {
444
519
  await this.eventGraphManager.loadGraphsForBot(botConfig.id);
445
520
 
446
521
  this.triggerHeartbeat();
522
+ const { getIO } = require('../real-time/socketHandler');
447
523
  getIO().emit('bot:status', { botId: botConfig.id, status: 'starting' });
448
524
  return child;
449
525
  }
@@ -505,41 +581,17 @@ class BotManager {
505
581
  cooldowns.set(cooldownKey, now);
506
582
  }
507
583
 
508
- if (dbCommand.isVisual) {
509
- const graph = dbCommand.graphJson;
510
- if (graph) {
511
- const players = await this.getPlayerList(botId);
512
- console.log('[BotManager] Received player list for graph:', players);
513
-
514
- const botAPI = {
515
- sendMessage: (type, message, recipient) => {
516
- this.sendMessageToBot(botId, message, type, recipient);
517
- },
518
- executeCommand: (command) => {
519
- this.sendServerCommandToBot(botId, command);
520
- },
521
- };
522
-
523
- const graphContext = {
524
- bot: botAPI,
525
- botId: botId,
526
- user,
527
- args,
528
- typeChat,
529
- players,
530
- };
531
-
532
- try {
533
- const resultContext = await this.graphEngine.execute(graph, graphContext, 'command');
534
- } catch (e) {
535
- console.error(`[BotManager] Ошибка выполнения визуальной команды '${commandName}':`, e);
536
- this.sendMessageToBot(botId, 'Произошла внутренняя ошибка при выполнении команды.', 'private', username);
537
- }
538
- }
539
- } else {
540
- child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
584
+ if (this.eventGraphManager) {
585
+ this.eventGraphManager.handleEvent(botId, 'command', {
586
+ commandName: dbCommand.name,
587
+ user: { username },
588
+ args,
589
+ typeChat
590
+ });
541
591
  }
542
592
 
593
+ child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
594
+
543
595
  } catch (error) {
544
596
  console.error(`[BotManager] Command validation error for botId: ${botId}`, {
545
597
  command: commandName, user: username, error: error.message, stack: error.stack
@@ -685,7 +737,7 @@ class BotManager {
685
737
  sendMessageToBot(botId, message, chatType = 'command', username = null) {
686
738
  const child = this.bots.get(botId);
687
739
  if (child) {
688
- child.send({ type: 'chat', payload: { message, chatType, username } });
740
+ child.api.sendMessage(chatType, message, username);
689
741
  return { success: true };
690
742
  }
691
743
  return { success: false, message: 'Бот не найден или не запущен' };
@@ -786,4 +838,4 @@ class BotManager {
786
838
  }
787
839
  }
788
840
 
789
- module.exports = new BotManager();
841
+ module.exports = new BotManager();
@@ -16,6 +16,8 @@ const UserService = require('./UserService');
16
16
  const PermissionManager = require('./ipc/PermissionManager.stub.js');
17
17
 
18
18
  let bot = null;
19
+ const prisma = new PrismaClient();
20
+ const pluginUiState = new Map();
19
21
  const pendingRequests = new Map();
20
22
  const entityMoveThrottles = new Map();
21
23
 
@@ -34,8 +36,7 @@ function sendEvent(eventName, eventArgs) {
34
36
  }
35
37
  }
36
38
 
37
- async function fetchNewConfig(botId) {
38
- const prisma = new PrismaClient();
39
+ async function fetchNewConfig(botId, prisma) {
39
40
  try {
40
41
  const botData = await prisma.bot.findUnique({
41
42
  where: { id: botId },
@@ -55,8 +56,6 @@ async function fetchNewConfig(botId) {
55
56
  } catch (error) {
56
57
  sendLog(`[fetchNewConfig] Error: ${error.message}`);
57
58
  return null;
58
- } finally {
59
- await prisma.$disconnect();
60
59
  }
61
60
  }
62
61
 
@@ -129,7 +128,17 @@ function handleIncomingCommand(type, username, message) {
129
128
  }
130
129
 
131
130
  process.on('message', async (message) => {
132
- if (message.type === 'user_action_response') {
131
+ if (message.type === 'plugin:ui:start-updates') {
132
+ const { pluginName } = message;
133
+ const state = pluginUiState.get(pluginName);
134
+ if (state && process.send) {
135
+ process.send({
136
+ type: 'plugin:data',
137
+ plugin: pluginName,
138
+ payload: state
139
+ });
140
+ }
141
+ } else if (message.type === 'user_action_response') {
133
142
  if (pendingRequests.has(message.requestId)) {
134
143
  const { resolve, reject } = pendingRequests.get(message.requestId);
135
144
  if (message.error) {
@@ -193,6 +202,8 @@ process.on('message', async (message) => {
193
202
 
194
203
  bot = mineflayer.createBot(botOptions);
195
204
 
205
+ bot.pluginUiState = pluginUiState;
206
+
196
207
  let isReady = false;
197
208
 
198
209
  bot.events = new EventEmitter();
@@ -214,8 +225,7 @@ process.on('message', async (message) => {
214
225
  registerGroup: (groupConfig) => PermissionManager.registerGroup(bot.config.id, groupConfig),
215
226
  addPermissionsToGroup: (groupName, permissionNames) => PermissionManager.addPermissionsToGroup(bot.config.id, groupName, permissionNames),
216
227
  installedPlugins: installedPluginNames,
217
- registerCommand: async (command) => { // похуй. пляшем
218
- const prisma = new PrismaClient();
228
+ registerCommand: async (command) => {
219
229
  try {
220
230
  let permissionId = null;
221
231
  if (command.permissions) {
@@ -231,7 +241,7 @@ process.on('message', async (message) => {
231
241
  if (permission) {
232
242
  permissionId = permission.id;
233
243
  } else {
234
- sendLog(`[API] Внимание: право "${command.permissions}" не найдено для команды "${command.name}". Команда будет создана без привязанного права.`);
244
+ sendLog(`[API] Внимание: право \"${command.permissions}\" не найдено для команды \"${command.name}\". Команда будет создана без привязанного права.`);
235
245
  }
236
246
  }
237
247
 
@@ -281,7 +291,7 @@ process.on('message', async (message) => {
281
291
  }
282
292
  });
283
293
  }
284
- sendLog(`[API] Команда "${command.name}" от плагина "${command.owner}" зарегистрирована в процессе.`);
294
+ sendLog(`[API] Команда \"${command.name}\" от плагина \"${command.owner}\" зарегистрирована в процессе.`);
285
295
 
286
296
  if (!bot.commands) bot.commands = new Map();
287
297
  bot.commands.set(command.name, command);
@@ -292,8 +302,6 @@ process.on('message', async (message) => {
292
302
  }
293
303
  } catch (error) {
294
304
  sendLog(`[API] Ошибка при регистрации команды: ${error.message}`);
295
- } finally {
296
- await prisma.$disconnect();
297
305
  }
298
306
  },
299
307
  performUserAction: (username, action, data = {}) => {
@@ -331,6 +339,20 @@ process.on('message', async (message) => {
331
339
  if (bot && position) {
332
340
  bot.lookAt(position);
333
341
  }
342
+ },
343
+ sendUiUpdate: (pluginName, stateUpdate) => {
344
+ const currentState = pluginUiState.get(pluginName) || {};
345
+ const newState = { ...currentState, ...stateUpdate };
346
+ pluginUiState.set(pluginName, newState);
347
+
348
+
349
+ if (process.send) {
350
+ process.send({
351
+ type: 'plugin:data',
352
+ plugin: pluginName,
353
+ payload: newState
354
+ });
355
+ }
334
356
  }
335
357
  };
336
358
 
@@ -346,9 +368,7 @@ process.on('message', async (message) => {
346
368
 
347
369
  bot.commands = await loadCommands();
348
370
 
349
- const prisma = new PrismaClient();
350
371
  const dbCommands = await prisma.command.findMany({ where: { botId: config.id } });
351
- await prisma.$disconnect();
352
372
 
353
373
  for (const dbCommand of dbCommands) {
354
374
  if (!dbCommand.isEnabled) {
@@ -406,7 +426,7 @@ process.on('message', async (message) => {
406
426
  }
407
427
  }
408
428
 
409
- await initializePlugins(bot, config.plugins);
429
+ await initializePlugins(bot, config.plugins, prisma);
410
430
  sendLog('[System] Все системы инициализированы.');
411
431
 
412
432
  let messageHandledByCustomParser = false;
@@ -540,13 +560,13 @@ process.on('message', async (message) => {
540
560
  } else if (message.type === 'config:reload') {
541
561
  sendLog('[System] Received config:reload command. Reloading configuration...');
542
562
  try {
543
- const newConfig = await fetchNewConfig(bot.config.id);
563
+ const newConfig = await fetchNewConfig(bot.config.id, prisma);
544
564
  if (newConfig) {
545
565
  bot.config = { ...bot.config, ...newConfig };
546
566
  const newCommands = await loadCommands();
547
567
  const newPlugins = bot.config.plugins;
548
568
  bot.commands = newCommands;
549
- await initializePlugins(bot, newPlugins, true);
569
+ await initializePlugins(bot, newPlugins, prisma);
550
570
  sendLog('[System] Bot configuration and plugins reloaded successfully.');
551
571
  } else {
552
572
  sendLog('[System] Failed to fetch new configuration.');
@@ -610,14 +630,14 @@ process.on('message', async (message) => {
610
630
  }
611
631
  } else if (message.type === 'plugins:reload') {
612
632
  sendLog('[System] Получена команда на перезагрузку плагинов...');
613
- const newConfig = await fetchNewConfig(bot.config.id);
614
- if (newConfig) {
615
- bot.config.plugins = newConfig.installedPlugins;
616
- bot.commands.clear();
617
- await loadCommands(bot, newConfig.commands);
618
- await initializePlugins(bot, newConfig.installedPlugins);
619
- sendLog('[System] Плагины успешно перезагружены.');
620
- } else {
633
+ const newConfig = await fetchNewConfig(bot.config.id, prisma);
634
+ if (newConfig) {
635
+ bot.config.plugins = newConfig.installedPlugins;
636
+ bot.commands.clear();
637
+ await loadCommands(bot, newConfig.commands);
638
+ await initializePlugins(bot, newConfig.installedPlugins, prisma);
639
+ sendLog('[System] Плагины успешно перезагружены.');
640
+ } else {
621
641
  sendLog('[System] Не удалось получить новую конфигурацию для перезагрузки плагинов.');
622
642
  }
623
643
  } else if (message.type === 'server_command') {
@@ -1,13 +1,14 @@
1
1
  const { PrismaClient } = require('@prisma/client');
2
+ const GraphExecutionEngine = require('./GraphExecutionEngine');
3
+ const nodeRegistry = require('./NodeRegistry');
2
4
 
3
5
  const prisma = new PrismaClient();
4
6
 
5
7
  class EventGraphManager {
6
8
  constructor(botManager) {
7
9
  this.botManager = botManager;
8
- this.graphEngine = botManager.graphEngine;
10
+ this.graphEngine = new GraphExecutionEngine(nodeRegistry, botManager);
9
11
  this.activeGraphs = new Map();
10
- // Хранилище для состояний (переменных) каждого графа
11
12
  this.graphStates = new Map();
12
13
  }
13
14
 
@@ -26,7 +27,6 @@ class EventGraphManager {
26
27
  const parsedGraph = JSON.parse(graph.graphJson);
27
28
  if (!parsedGraph.nodes) continue;
28
29
 
29
- // Инициализация начального состояния переменных для этого графа
30
30
  const initialState = {};
31
31
  if (graph.variables) {
32
32
  try {
@@ -56,6 +56,7 @@ class EventGraphManager {
56
56
  name: graph.name,
57
57
  nodes: parsedGraph.nodes,
58
58
  connections: parsedGraph.connections,
59
+ variables: parsedGraph.variables || [],
59
60
  });
60
61
  }
61
62
  } catch (e) {
@@ -69,7 +70,6 @@ class EventGraphManager {
69
70
 
70
71
  unloadGraphsForBot(botId) {
71
72
  this.activeGraphs.delete(botId);
72
- // Также очищаем состояния при выгрузке
73
73
  for (const key of this.graphStates.keys()) {
74
74
  if (key.startsWith(`${botId}-`)) {
75
75
  this.graphStates.delete(key);
@@ -120,7 +120,25 @@ class EventGraphManager {
120
120
  initialContext.botId = botId;
121
121
  initialContext.players = players;
122
122
  initialContext.botState = eventArgs.botState || {};
123
- initialContext.variables = { ...(this.graphStates.get(stateKey) || {}) };
123
+
124
+ const savedVariables = { ...(this.graphStates.get(stateKey) || {}) };
125
+
126
+ if (graph.variables && Array.isArray(graph.variables)) {
127
+ for (const v of graph.variables) {
128
+ if (!savedVariables.hasOwnProperty(v.name)) {
129
+ let val;
130
+ switch (v.type) {
131
+ case 'number': val = Number(v.value) || 0; break;
132
+ case 'boolean': val = v.value === 'true'; break;
133
+ case 'array': try { val = Array.isArray(JSON.parse(v.value)) ? JSON.parse(v.value) : []; } catch { val = []; } break;
134
+ default: val = v.value;
135
+ }
136
+ savedVariables[v.name] = val;
137
+ }
138
+ }
139
+ }
140
+
141
+ initialContext.variables = savedVariables;
124
142
 
125
143
  try {
126
144
  const finalContext = await this.graphEngine.execute(graph, initialContext, eventType);
@@ -162,6 +180,12 @@ class EventGraphManager {
162
180
  case 'entityGone':
163
181
  context.entity = args.entity;
164
182
  break;
183
+ case 'command':
184
+ context.command_name = args.commandName;
185
+ context.user = args.user;
186
+ context.args = args.args;
187
+ context.chat_type = args.typeChat;
188
+ break;
165
189
  }
166
190
  return context;
167
191
  }
@@ -219,6 +219,34 @@ class GraphExecutionEngine {
219
219
  await this.traverse(node, 'completed');
220
220
  break;
221
221
  }
222
+ case 'flow:while': {
223
+ let iteration = 0;
224
+ const maxIterations = 1000;
225
+
226
+ try {
227
+ while (iteration < maxIterations) {
228
+ const condition = await this.resolvePinValue(node, 'condition', false);
229
+ if (!condition) break;
230
+
231
+ this.memo.set(`${node.id}:iteration`, iteration);
232
+ this.clearLoopBodyMemo(node);
233
+ await this.traverse(node, 'loop_body');
234
+ iteration++;
235
+ }
236
+
237
+ if (iteration >= maxIterations) {
238
+ console.warn(`[GraphExecutionEngine] Цикл while достиг максимального количества итераций (${maxIterations})`);
239
+ }
240
+ } catch (e) {
241
+ if (e instanceof BreakLoopSignal) {
242
+ } else {
243
+ throw e;
244
+ }
245
+ }
246
+
247
+ await this.traverse(node, 'completed');
248
+ break;
249
+ }
222
250
  case 'flow:sequence': {
223
251
  const pinCount = node.data?.pinCount || 2;
224
252
  for (let i = 0; i < pinCount; i++) {
@@ -375,7 +403,7 @@ class GraphExecutionEngine {
375
403
  case 'event:command':
376
404
  if (pinId === 'args') result = this.context.args || {};
377
405
  else if (pinId === 'user') result = this.context.user || {};
378
- else if (pinId === 'chat_type') result = this.context.typeChat || 'local';
406
+ else if (pinId === 'chat_type') result = this.context.typeChat || 'chat';
379
407
  else result = this.context[pinId];
380
408
  break;
381
409
  case 'event:chat':
@@ -399,8 +427,13 @@ class GraphExecutionEngine {
399
427
  break;
400
428
 
401
429
  case 'data:get_variable':
402
- const varName = node.data?.variableName || '';
403
- result = this.context.variables.hasOwnProperty(varName) ? this.context.variables[varName] : null;
430
+ const varName = node.data?.variableName || node.data?.selectedVariable || '';
431
+ if (!varName) {
432
+ console.warn('[GraphExecutionEngine] data:get_variable: не указано имя переменной', node.data);
433
+ result = null;
434
+ } else {
435
+ result = this.context.variables.hasOwnProperty(varName) ? this.context.variables[varName] : null;
436
+ }
404
437
  break;
405
438
 
406
439
 
@@ -471,17 +504,13 @@ class GraphExecutionEngine {
471
504
  case 'logic:operation': {
472
505
  const op = node.data?.operation || 'AND';
473
506
  const inputs = [];
474
- for (const key in node.data) {
475
- if (key.startsWith('pin_')) {
476
- inputs.push(await this.resolvePinValue(node, key, false));
477
- }
478
- }
479
- if (inputs.length === 0) {
480
- inputs.push(await this.resolvePinValue(node, 'a', false));
481
- inputs.push(await this.resolvePinValue(node, 'b', false));
507
+ const pinCount = node.data?.pinCount || 2;
508
+
509
+ for (let i = 0; i < pinCount; i++) {
510
+ const value = await this.resolvePinValue(node, `pin_${i}`, false);
511
+ inputs.push(value);
482
512
  }
483
513
 
484
-
485
514
  switch (op) {
486
515
  case 'AND': result = inputs.every(Boolean); break;
487
516
  case 'OR': result = inputs.some(Boolean); break;
@@ -738,6 +767,12 @@ class GraphExecutionEngine {
738
767
  }
739
768
  break;
740
769
  }
770
+ case 'flow:while': {
771
+ if (pinId === 'iteration') {
772
+ result = this.memo.get(`${node.id}:iteration`);
773
+ }
774
+ break;
775
+ }
741
776
 
742
777
  case 'string:equals': {
743
778
  const strA = String(await this.resolvePinValue(node, 'a', ''));
@@ -798,6 +833,13 @@ class GraphExecutionEngine {
798
833
 
799
834
  return false;
800
835
  }
836
+
837
+ hasConnection(node, pinId) {
838
+ if (!this.activeGraph || !this.activeGraph.connections) return false;
839
+ return this.activeGraph.connections.some(conn =>
840
+ conn.targetNodeId === node.id && conn.targetPinId === pinId
841
+ );
842
+ }
801
843
  }
802
844
 
803
845
  module.exports = GraphExecutionEngine;
@@ -33,7 +33,16 @@ class MessageQueue {
33
33
  enqueue(chatType, message, username = null) {
34
34
  const typeConfig = this.chatTypes[chatType];
35
35
  if (!typeConfig) return;
36
- this._enqueue({ type: 'simple', chatType, ...typeConfig, message, username });
36
+
37
+ if (Array.isArray(message)) {
38
+ for (const msg of message) {
39
+ if (typeof msg === 'string' && msg.trim().length > 0) {
40
+ this._enqueue({ type: 'simple', chatType, ...typeConfig, message: msg, username });
41
+ }
42
+ }
43
+ } else if (typeof message === 'string') {
44
+ this._enqueue({ type: 'simple', chatType, ...typeConfig, message, username });
45
+ }
37
46
  }
38
47
 
39
48
  enqueueAndWait(command, patterns, timeout) {
@@ -102,11 +102,12 @@ class NodeRegistry {
102
102
  label: '▶️ При выполнении команды',
103
103
  category: 'События',
104
104
  description: 'Стартовая точка для графа команды.',
105
- graphType: command,
105
+ graphType: 'all',
106
106
  pins: {
107
107
  inputs: [],
108
108
  outputs: [
109
109
  { id: 'exec', name: 'Выполнить', type: 'Exec' },
110
+ { id: 'command_name', name: 'Имя команды', type: 'String' },
110
111
  { id: 'user', name: 'Пользователь', type: 'User' },
111
112
  { id: 'args', name: 'Аргументы', type: 'Object' },
112
113
  { id: 'chat_type', name: 'Тип чата', type: 'String' }